Физика SpriteKit в Swift - шарики скользят по стене вместо отражения

Я создаю свою собственную очень простую тестовую игру, основанную на Breakout, при изучении SpriteKit (с помощью игр iOS от Tutorials от Ray Wenderlich и др.), чтобы узнать, могу ли я применять концепции, которые я узнал. Я решил упростить свой код, используя файл .sks для создания узлов спрайтов и замены проверки ручных привязок и столкновений с физическими телами.

Тем не менее, мой шар продолжает работать параллельно стенам/другим прямоугольникам (как, например, просто сдвигая их вверх и вниз) в любое время, когда он сталкивается с ними под крутым углом. Вот соответствующий код. Я переместил свойства тела физики в код, чтобы сделать их более заметными:

import SpriteKit

struct PhysicsCategory {
  static let None:        UInt32 = 0      //  0
  static let Edge:        UInt32 = 0b1    //  1
  static let Paddle:      UInt32 = 0b10   //  2
  static let Ball:        UInt32 = 0b100  //  4
}

var paddle: SKSpriteNode!
var ball: SKSpriteNode!

class GameScene: SKScene, SKPhysicsContactDelegate {

  override func didMoveToView(view: SKView) {

    physicsWorld.gravity = CGVector.zeroVector

    let edge = SKNode()
    edge.physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
    edge.physicsBody!.usesPreciseCollisionDetection = true
    edge.physicsBody!.categoryBitMask = PhysicsCategory.Edge
    edge.physicsBody!.friction = 0
    edge.physicsBody!.restitution = 1
    edge.physicsBody!.angularDamping = 0
    edge.physicsBody!.linearDamping = 0
    edge.physicsBody!.dynamic = false
    addChild(edge)

    ball = childNodeWithName("ball") as SKSpriteNode
    ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.size))
    ball.physicsBody!.usesPreciseCollisionDetection = true
    ball.physicsBody!.categoryBitMask = PhysicsCategory.Ball
    ball.physicsBody!.collisionBitMask = PhysicsCategory.Edge | PhysicsCategory.Paddle
    ball.physicsBody!.allowsRotation = false
    ball.physicsBody!.friction = 0
    ball.physicsBody!.restitution = 1
    ball.physicsBody!.angularDamping = 0
    ball.physicsBody!.linearDamping = 0

    physicsWorld.contactDelegate = self
}

Забыл упомянуть об этом раньше, но я добавил простую функцию touchhesBegan для отладки отскоков - она ​​просто регулирует скорость, чтобы указать мяч в точке касания:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
  let touch = touches.anyObject() as UITouch
  let moveToward = touch.locationInNode(self)
  let targetVector = (moveToward - ball.position).normalized() * 300.0
  ball.physicsBody!.velocity = CGVector(point: targetVector)
}

Функция normalized() просто уменьшает дельта треугольника шага/касания до единичного вектора, и существует переопределение оператора minus, который позволяет вычитать CGPoint.

Столкновения мяча/края всегда должны отражать шар на точно противоположном угле, но по какой-то причине у шарика действительно есть вещь для прямых углов. Я могу, конечно, реализовать некоторое обходное решение, чтобы отразить угол шара вручную, но дело в том, что я хочу сделать все это, используя встроенную физическую функциональность в SpriteKit. Есть ли что-то очевидное, что мне не хватает?

Ответы

Ответ 1

Это, по-видимому, проблема с обнаружением конфликтов. Большинство из них нашли решения, используя didBeginContact и повторно применяя силу в противоположном направлении. Обратите внимание, что он говорит didMoveToView, но исправляет себя в более позднем комментарии к didBeginContact.

См. комментарии внизу учебника Ray Wenderlich здесь

У меня есть исправление проблемы с мячом "верхом по рельсу", если он удары на мелкий угол (@aziz76 и @colinf). Я добавил еще один категории, "BorderCategory" и назначил его на границу PhysBody мы создаем в didMoveToView.

и аналогичный SO вопрос здесь, объясняющий, почему это происходит.

Даже если вы это сделаете, многие физические двигатели (в том числе SpriteKit's) имеют проблемы с ситуациями, подобными этому из-за ошибки округления с плавающей запятой. Я обнаружил, что, когда я хочу, чтобы тело сохраняйте постоянную скорость после столкновения, лучше всего заставить ее - используйте обработчик didEndContact: или didSimulatePhysics для reset движущегося скорость тела, так что она идет той же скоростью, что и до столкновение (но в противоположном направлении).

Также я заметил, что вы используете квадрат вместо круга для своего шара, и вы можете рассмотреть возможность использования...

ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)

Итак, получается, что вы не сумасшедший, который всегда хорошо слышать от кого-то другого, и, надеюсь, это поможет вам найти решение, которое наилучшим образом подходит для вашего приложения.

Ответ 2

Я придумал временное решение, которое работает на удивление хорошо. Просто примените очень маленький импульс, противоположный границе. Возможно, вам придется изменить strength на основе масс в вашей системе.

func didBeginContact(contact: SKPhysicsContact) {

    let otherNode = contact.bodyA.node == ball.sprite ? contact.bodyB.node : contact.bodyA.node

    if let obstacle = otherNode as? Obstacle {
        ball.onCollision(obstacle)
    }
    else if let border = otherNode as? SKSpriteNode {

        assert(border.name == "border", "Bad assumption")

        let strength = 1.0 * (ball.sprite.position.x < frame.width / 2 ? 1 : -1)
        let body = ball.sprite.physicsBody!
        body.applyImpulse(CGVector(dx: strength, dy: 0))
    }
}

В действительности это не обязательно, поскольку, как описано в вопросе, без трения, полностью упругое столкновение диктует, что мяч должен отскочить, инвертируя скорость x (при условии боковых границ), независимо от того, насколько мал угол столкновения.

Вместо этого то, что происходит в игре, похоже на то, что комплект спрайтов игнорирует скорость X, если он меньше определенного значения, заставляя мяч скользить по стене без отскока.

Заключительная записка

После чтения этого и this, это очевидно для что реальный ответ для любой серьезной игры физики у вас есть, вы должны использовать Box2D вместо этого. Вы получаете слишком много льгот из миграции.

Ответ 3

Эта проблема возникает, только когда скорость мала в любом направлении. Однако для уменьшения эффекта можно уменьшить скорость физического мира, например,

physicsWorld.speed = 0.1

а затем увеличьте скорость физики, например,

let targetVector = (moveToward - ball.position).normalized() * 300.0 * 10
ball.physicsBody!.velocity = CGVector(point: targetVector)

Ответ 4

Я видел точно такую ​​же проблему, но исправление для меня не было связано с проблемами обнаружения столкновений, упомянутыми в других ответах. Оказывается, я приводил мяч в движение, используя SKAction, который повторяется навсегда. В конце концов я обнаружил, что это противоречит физическому моделированию SpriteKit, ведущему к node/шару, перемещающемуся вдоль стены, вместо того, чтобы отскакивать от него.

Я предполагаю, что повторяющийся SKAction продолжает применяться и переопределяет/конфликтует с автоматической настройкой физического моделирования свойства шара physicsBody.velocity.

Исправить это было, чтобы установить мяч в движение, установив velocity в свой physicsBody. Как только я это сделал, мяч начал прыгать правильно. Я предполагаю, что манипулирование своим положением через physicsBody с помощью сил и импульсов также будет работать, учитывая, что они являются частью моделирования физики.

Мне потребовалось некоторое время, чтобы понять эту проблему, поэтому я публикую это здесь, если я смогу кого-нибудь сэкономить. Спасибо 0x141e! Ваш comment поместит меня (и мой мяч) на правильный путь.

Ответ 5

Добавьте код ниже:

let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
self.physicsBody = border

что заставит ваш мяч отскочить назад, когда он столкнется со стеной. Реституция - это бодрость физического тела, поэтому установка его в 1 отскочит мяч назад.