Swift + Physics: Wrong angle calculation on collis

2020-07-10 07:15发布

问题:

I have a simple SpriteKit App with walls and a ball. Both setup with a SKPhysicsBody.

When I apply a force in one direction, I expect the ball to reflect at the wall with the same angle when it collide but in opposite direction.

But sometimes I see the angle is weird. I played a lot with all the physicsBody properties, but was not able to fix it. Sometimes the first reflections look good, but then the third or sixth and sometimes the first reflection is at wrong angle.

I read from different posts, people are kinda self-calculating the "correct direction". But I can't imagine SpriteKits physic engine is not capable to do so.

Check my attached image to understand what I mean. Can anybody help on this? I don't want to start playing around with Box2d for Swift, since this looks like it will be too hard for me to integrate into Swift.

This is the physicsWorld init on my GameScene.swift file where all my elements are added to:

self.physicsWorld.gravity = CGVectorMake(0, 0)

Adding all my code would be way too much, so I add the important pieces. Hope its enough for analyze. All elements like the ball, walls are SKSpriteNode's

This is the physicsBody code for the ball:

self.physicsBody = SKPhysicsBody(circleOfRadius: Constants.Config.playersize+self.node.lineWidth)
self.physicsBody?.restitution = 1
self.physicsBody?.friction = 0
self.physicsBody?.linearDamping = 1
self.physicsBody?.allowsRotation = false
self.physicsBody?.categoryBitMask = Constants.Config.PhysicsCategory.Player
self.physicsBody?.collisionBitMask = Constants.Config.PhysicsCategory.Wall | Constants.Config.PhysicsCategory.Enemy
self.physicsBody?.contactTestBitMask = Constants.Config.PhysicsCategory.Wall | Constants.Config.PhysicsCategory.Enemy

This is the physicsBody for the walls:

el = SKSpriteNode(color: UIColor.blueColor(), size: CGSize(width: Constants.Config.wallsize, height: Constants.Config.wallsize))
el.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: Constants.Config.wallsize, height: Constants.Config.wallsize))
el.physicsBody?.dynamic = false
el.physicsBody?.categoryBitMask = Constants.Config.PhysicsCategory.Wall
el.physicsBody?.collisionBitMask = Constants.Config.PhysicsCategory.Player
el.physicsBody?.contactTestBitMask = Constants.Config.PhysicsCategory.Player

At the end I just call the applyImpulse function on the balls physicsBody to make it moving in the physics simulation.

Also check my attached second image/gif. It shows the edge collision problem with a simple skphysics app without any special parameterization. just a rectangle with a ball in it and one vector applied as impulse.

Heres my full non-working code using the solution from appzYourLife.

class GameScene: SKScene {
private var ball:SKShapeNode!
override func didMoveToView(view: SKView) {
    let screen = UIScreen.mainScreen().bounds
    let p = UIBezierPath()
    p.moveToPoint(CGPointMake(0,0))
    p.addLineToPoint(CGPointMake(screen.width,0))
    p.addLineToPoint(CGPointMake(screen.width, screen.height))
    p.addLineToPoint(CGPointMake(0,screen.height))
    p.closePath()
    let shape = SKShapeNode(path: p.CGPath)
    shape.physicsBody = SKPhysicsBody(edgeLoopFromPath: p.CGPath)
    shape.physicsBody?.affectedByGravity = false
    shape.physicsBody?.dynamic = false
    shape.strokeColor = UIColor.blackColor()

    self.addChild(shape)

    ball = SKShapeNode(circleOfRadius: 17)
    ball.name = "player"
    ball.position = CGPoint(x: 20, y: 20)
    ball.fillColor = UIColor.yellowColor()
    ball.physicsBody = SKPhysicsBody(circleOfRadius: 17)
    ball.physicsBody?.angularDamping = 0
    ball.physicsBody?.linearDamping = 0
    ball.physicsBody?.restitution = 1
    ball.physicsBody?.friction = 0
    ball.physicsBody?.allowsRotation = false

    self.addChild(ball)
    self.ball.physicsBody?.applyImpulse(CGVector(dx: 2.4, dy: 9.7))
}

I just realized the mass and density of my ball sprite is nil. Why that? Even setting it keeps in nil.

回答1:

Since i cannot imagine this is some unknown bug in SpriteKits physics engine, 
i very hope someone can help me here as this is a full stopper for me.

It is a (known) bug in SpriteKits physic engine. But sometimes just playing with the values will make this bug disappear - for example, try changing the ball's speed (even by small value) every time it's hitting the wall.

Another solutions are listed here: SpriteKit physics in Swift - Ball slides against wall instead of reflecting

(The same problem as yours, already from 2014, still not fixed..)



回答2:

SpriteKit is working fine

I created a simple project and it is working fine.

The problem

As @Sulthan already suggested in a comment, I think the problem in your code is this line

el.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: Constants.Config.wallsize, height: Constants.Config.wallsize))

Here instead of creating the physical border for your scene, you are creating a rectangular physics body which overlap the physics body of the Ball producing unrealistic behaviours.

To create the physics border of my scene I simply used this

physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)

Full code

By the way, this is the full code for my project

class GameScene: SKScene {
    override func didMoveToView(view: SKView) {
        let ball = Ball()
        ball.position = CGPoint(x:frame.midX, y:frame.midY)
        addChild(ball)
        physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
        ball.physicsBody!.applyImpulse(CGVector(dx:50, dy:70))
        physicsWorld.gravity = CGVector(dx:0, dy:0)
    }
}

class Ball: SKSpriteNode {

    init() {
        let texture = SKTexture(imageNamed: "ball")
        super.init(texture: texture, color: .clearColor(), size: texture.size())

        let physicsBody = SKPhysicsBody(circleOfRadius: texture.size().width/2)
        physicsBody.restitution = 1
        physicsBody.friction = 0
        physicsBody.linearDamping = 0
        physicsBody.allowsRotation = false
        self.physicsBody = physicsBody
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

I also used scene.scaleMode = .ResizeFill inside GameViewController.swift.