swift sprite-kit box2d game-physics

La física de SpriteKit en Swift: la pelota se desliza contra la pared en lugar de reflejarse



sprite-kit box2d (4)

He estado creando mi propio juego de prueba muy simple basado en Breakout mientras aprendía SpriteKit (usando iOS Games por Tutorials de Ray Wenderlich y otros) para ver si puedo aplicar los conceptos que he aprendido. He decidido simplificar mi código utilizando un archivo .sks para crear los nodos de sprites y reemplazar mi comprobación manual de límites y colisión con cuerpos de física.

Sin embargo, mi bola sigue corriendo paralela a las paredes / otros rectángulos (como en, simplemente deslizándolos hacia arriba y hacia abajo) cada vez que colisiona con ellos en un ángulo pronunciado. Aquí está el código relevante: he movido las propiedades físicas del cuerpo al código para hacerlas más visibles:

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 }

Olvidé mencionar esto antes, pero agregué una función touchesBegan para depurar los rebotes, simplemente ajusta la velocidad para apuntar la pelota en el punto de contacto:

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) }

La función normalizada () solo reduce la delta de la posición pelota / táctil a un vector unitario, y hay una anulación del operador menos que permite la resta CGPoint.

Las colisiones de bola / borde siempre deben reflejar la bola en un ángulo exactamente opuesto, pero por alguna razón la bola realmente parece tener una cosa para ángulos rectos. Por supuesto, puedo implementar alguna solución para reflejar el ángulo de la bola manualmente, pero el punto es que quiero hacer todo esto usando la funcionalidad de física incorporada en SpriteKit. ¿Hay algo obvio que me estoy perdiendo?


Se me ocurrió una solución temporal que funciona sorprendentemente bien. Simplemente aplique un impulso muy pequeño opuesto al borde. Es posible que deba cambiar la strength según las masas de su sistema.

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)) } }

En realidad, esto no debería ser necesario, ya que como se describe en la pregunta, la colisión totalmente elástica y sin fricción dicta que la bola debería rebotar invirtiendo la velocidad x (asumiendo los bordes laterales) sin importar cuán pequeño sea el ángulo de colisión.

En cambio, lo que está sucediendo en el juego es como si el kit de sprite ignorase la velocidad X si es menor que un cierto valor, haciendo que la pelota se deslice contra la pared sin rebote.

Nota final

Después de leer esto y esto , es obvio para mí que la verdadera respuesta es que para cualquier juego de física serio que tengas, deberías usar Box2D. Obtienes demasiadas ventajas de la migración.


Este problema solo parece ocurrir cuando la velocidad es pequeña en cualquier dirección. Sin embargo, para reducir el efecto, es posible disminuir la velocidad de physicsWorld, por ejemplo,

physicsWorld.speed = 0.1

y luego aumentar la velocidad de la física Cuerpo, por ejemplo,

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


Estaba viendo exactamente el mismo problema, pero la solución para mí no estaba relacionada con los problemas de detección de colisión mencionados en las otras respuestas. Resulta que estaba poniendo la pelota en movimiento mediante el uso de una SKAction que se repite para siempre. Finalmente descubrí que esto entra en conflicto con la simulación de física de SpriteKit conduce al nodo / bola que viaja a lo largo de la pared en lugar de rebotar en ella.

Supongo que la SKAction repetitiva continúa aplicándose y anula / entra en conflicto con el ajuste automático de la física de la pelota physicsBody.velocity la physicsBody.velocity .

La solución para esto fue poner la bola en movimiento al establecer la velocity en su propiedad physicsBody . Una vez que hice esto, la pelota comenzó a rebotar correctamente. Supongo que manipular su posición a través de physicsBody usando fuerzas e impulsos también funcionará dado que son parte de la simulación física.

Me tomó un tiempo vergonzoso darme cuenta de este problema, así que estoy publicando esto aquí en caso de que pueda salvar a alguien más en algún momento. ¡Gracias a 0x141e ! Tu comentario me puso a mí (y a mi pelota) en el camino correcto.


Esto parece ser un problema con la detección de colisión. La mayoría ha encontrado soluciones al usar didBeginContact y volver a aplicar la fuerza en una dirección opuesta. Tenga en cuenta que dice didMoveToView, pero se corrige en un comentario posterior a didBeginContact.

Ver comentarios en la parte inferior del tutorial de Ray Wenderlich aquí

Tengo una solución para el problema con la pelota "montada en la barandilla" si golpea en un ángulo poco profundo (@ aziz76 y @colinf). Agregué otra categoría, "BorderCategory" y la asigné al borde PhysicsBody que creamos en didMoveToView.

y una pregunta SO similar aquí explicando por qué está sucediendo.

Sin embargo, incluso si haces eso, muchos motores de física (incluyendo SpriteKit) tienen problemas con situaciones como esta debido a los errores de redondeo en coma flotante. He descubierto que cuando quiero que un cuerpo mantenga una velocidad constante después de una colisión, lo mejor es forzarlo a usar un controlador didEndContact: o didSimulatePhysics para restablecer la velocidad de la parte móvil de modo que vaya a la misma velocidad que antes. colisión (pero en la dirección opuesta).

Otra cosa que noté es que estás usando un cuadrado en lugar de un círculo para tu bola y es posible que quieras considerar usar ...

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

Así que resulta que no estás loco, lo que siempre es bueno escuchar de alguien más y con suerte esto te ayudará a encontrar la solución que funcione mejor para tu aplicación.