Физика 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 отскочит мяч назад.