7

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.

SpriteKit expexted and what is actually happening

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.

enter image description here

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.

NovumCoder
  • 4,349
  • 9
  • 43
  • 58
  • Can you provide any code? – Asdrubal Aug 07 '16 at 15:49
  • just added some code, adding all would be way too much, all the other parts are my game and level layer handling (as SKNode's) where i put the elements and finally on the GameScene. – NovumCoder Aug 07 '16 at 17:06
  • I wonder whether the collision is not done to the external side of the wall of the rectangle because the wall is a volume rectangle. Maybe use `bodyWithEdgeLoopFromRect` instead of `rectangleOfSize`? – Sulthan Aug 07 '16 at 19:06
  • Ok changed from rectangleOfSize to edgeLoopFromRect for the walls. Still the same behavior for the ball. This shouldn't affect the way of calculation since both types are solid bodies the collision should work with. – NovumCoder Aug 07 '16 at 19:23

2 Answers2

5
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..)

Community
  • 1
  • 1
Witterquick
  • 6,048
  • 3
  • 26
  • 50
5

SpriteKit is working fine

I created a simple project and it is working fine.

enter image description here

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.

Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • 1
    Well, this is what I told him a week ago and he said that nothing has changed :) I will give you the point for actually creating a demo that shows it. – Sulthan Aug 12 '16 at 12:48
  • @Sulthan: Hi, you're right, I didn't note your comment. I added a reference to you comment into my answer. – Luca Angeletti Aug 12 '16 at 12:56
  • Thanks, i will try that soon. Some people told me its a bug. Anyways, i will try and mark it as a solution once it works. – NovumCoder Aug 12 '16 at 13:58
  • I added my full GameScene.swift code using appzYourLife solution. Still not working. What is wrong here? By the way, the rectangle block i used in my code before is OK, because i used this draw wall blocks on the scene in kind of a level, so there was definitely no overlapping. but anyway even my second block of code using your idea is not working for me, same behavior. – NovumCoder Aug 13 '16 at 05:16
  • @NovumCoder: It's seems to happen when the applied impulse on the x is below `6.0`. You can change it to `self.ball.physicsBody?.applyImpulse(CGVector(dx: 6.5, dy: 9.7))` and don't forget to remove the gravity `physicsWorld.gravity = CGVector(dx:0, dy:0)` – Luca Angeletti Aug 13 '16 at 11:52
  • @NovumCoder: another solution si reducing the density of ball: `ball.physicsBody?.density = 0.3` – Luca Angeletti Aug 13 '16 at 12:03
  • @appzYourLife: both true, setting x=6.1 fixed it, and also setting density=0.3 also fixed it. well, now im a bit confused, is this some physics based issue (which means its kinda correct) or is this is a bug like Roee84 mentioned below? – NovumCoder Aug 13 '16 at 19:39
  • BUT, if my dx is less than 1 with density=0.3, again the ball sticks to the wall. This must be some crazy bug or? – NovumCoder Aug 13 '16 at 20:02
  • I did a lot of ball testing with a couple hundred small balls, and a couple of big ones and found that they want to cling (slightly) to the big balls and ever so slightly to each other (the little ones), just a little. Like they have a tiny amount of their own gravity. I shit you not. And this is with very high restitution numbers (over 1). – Confused Oct 01 '16 at 22:59