SpriteKit physics in Swift - Ball slides against wall instead of reflecting

我的未来我决定 提交于 2019-11-27 08:24:17
Skyler Lauren

This appears to be an issue with collision detection. Most have found solutions by using the didBeginContact and reapplying the force at an opposite direction. Note he says didMoveToView but corrects himself in a later comment to didBeginContact.

See comments at the bottom of the Ray Wenderlich tutorial here

I have a fix for the problem with the ball "riding the rail" if it strikes at a shallow angle (@aziz76 and @colinf). I added another category, "BorderCategory" and assigned it to the border PhysicsBody we create in didMoveToView.

and a similar SO question here explaining why it is happening.

Even if you do that, though, many physics engines (including SpriteKit's) have trouble with situations like this because of floating point rounding errors. I've found that when I want a body to keep a constant speed after a collision, it's best to force it to -- use a didEndContact: or didSimulatePhysics handler to reset the moving body's velocity so it's going the same speed it was before the collision (but in the opposite direction).

Also another thing I noticed is you are using a square instead of a circle for your ball and you may want to consider using...

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

So turns out you aren't crazy which is always good to hear from someone else and hopefully this will help you find a solution that works best for your application.

I came up with a temporary solution that is working surprisingly well. Simply apply a very small impulse opposite of the border. You may need to change the strength based on the masses in your system.

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

In reality, this should not be necessary, since as described in the question, frictionless, fully elastic collision dictates that the ball should rebound by inverting the x velocity (assuming side borders) no matter how small the collision angle is.

Instead, what is happening in the game is as if sprite kit ignores the X velocity if it is smaller than a certain value, making the ball slide against the wall without rebound.

Final Note

After reading this and this, it's obvious to me that the real answer is for any serious physics game you have, you should be using Box2D instead. You get way too many perks from the migration.

This problem only seems to occur when the velocity is small in either direction. However to reduce the effect it is possible to decrease the speed of the physicsWorld, e.g.,

physicsWorld.speed = 0.1

and then increase the velocity of the physicsBody, e.g.,

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

Add code below:

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

which will make your ball bounce back when it collides with wall. Restitution is the bounciness of the physics body so setting it to 1 will bounce ball back.

Barjavel

I was seeing exactly the same issue, but the fix for me was not related to the collision detection issues mentioned in the other answers. Turns out I was setting the ball into motion by using an SKAction that repeats forever. I eventually discovered that this conflicts with SpriteKit's physics simulation leading to the node/ball travelling along the wall instead of bouncing off it.

I'm assuming that the repeating SKAction continues to be applied and overrides/conflicts with the physics simulation's auto-adjustment of the the ball's physicsBody.velocity property.

The fix for this was to set the ball into motion by setting the velocity on its physicsBody property. Once I'd done this the ball began bouncing correctly. I'm guessing that manipulating its position via physicsBody by using forces and impulses will also work given that they are a part of the physics simulation.

It took me an embarrassing amount of time to realise this issue, so I'm posting this here in case I can save anyone else some time. Thank you to 0x141e! Your comment put me (and my ball) on the right path.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!