swiftxcodesprite-kitplaygrounds

SpriteKit node going through wall


I'm working on an Xcode Playground and I'm having some problems with SpriteKit collisions.

GIF of Playground

The first box has a mass of 1 and the second box has a mass of 100000 (when the latter is 100, for example, everything works fine). The collisions are elastic (restitution was set to 1) and there is no friction or damping at all.

Here is the code for my scene (ColliderType is just an enum for the categories):

class GameScene: SKScene {
    private var wall: SKSpriteNode!
    private var floor: SKSpriteNode!
    private var box1: SKSpriteNode!
    private var box2: SKSpriteNode!

    override func didMove(to view: SKView) {
        self.backgroundColor = .white

        wall = SKSpriteNode(color: SKColor.black, size: CGSize(width: 10, height: self.frame.height))
        wall.position = CGPoint(x: 100, y: self.frame.height/2)
        wall.physicsBody = SKPhysicsBody(rectangleOf: wall.frame.size)
        wall.physicsBody?.isDynamic = false

        floor = SKSpriteNode(color: SKColor.black, size: CGSize(width: self.frame.width, height: 10))
        floor.position = CGPoint(x: self.frame.width/2, y: 100)
        floor.physicsBody = SKPhysicsBody(rectangleOf: floor.frame.size)
        floor.physicsBody?.isDynamic = false

        box1 = SKSpriteNode(color: SKColor.black, size: CGSize(width: 100, height: 100))
        box1.position = CGPoint(x: 300, y: floor.position.y+box1.size.height/2)
        box1.physicsBody = SKPhysicsBody(circleOfRadius: box1.frame.size.width/2)

        box2 = SKSpriteNode(color: SKColor.black, size: CGSize(width: 100, height: 100))
        box2.position = CGPoint(x: 750, y: floor.position.y+box2.size.height/2)
        box2.physicsBody = SKPhysicsBody(circleOfRadius: box2.frame.size.width/2)

        self.addChild(wall)
        self.addChild(floor)
        self.addChild(box1)
        self.addChild(box2)

        box1.physicsBody?.allowsRotation = false
        box2.physicsBody?.allowsRotation = false

        box1.physicsBody?.restitution = 1
        box2.physicsBody?.restitution = 1

        box1.physicsBody?.mass = 1
        box2.physicsBody?.mass = 100000

        box1.physicsBody?.friction = 0
        box2.physicsBody?.friction = 0

        box1.physicsBody?.linearDamping = 0
        box2.physicsBody?.linearDamping = 0

        wall.physicsBody?.categoryBitMask = ColliderType.Wall.rawValue
        box1.physicsBody?.categoryBitMask = ColliderType.Box1.rawValue
        box2.physicsBody?.categoryBitMask = ColliderType.Box2.rawValue

        box1.physicsBody?.collisionBitMask = ColliderType.Wall.rawValue | ColliderType.Floor.rawValue | ColliderType.Box2.rawValue
        box2.physicsBody?.collisionBitMask = ColliderType.Floor.rawValue | ColliderType.Box1.rawValue

        box1.physicsBody?.contactTestBitMask = ColliderType.Box2.rawValue | ColliderType.Wall.rawValue

        box1.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
        box2.physicsBody?.velocity = CGVector(dx: -50, dy: 0)
    }
}

Things I've tried:


Solution

  • I think in general you're probably expecting too much. SpriteKit is a game engine, designed to give qualitatively reasonable behavior for scenarios involving normal-ish sorts of physics. It's not a high-precision physics simulation, and when you push it into a region where it's got to do something extreme, it's probably going to fail.

    In this case, you're expecting it to simulate a huge number of perfectly elastic collisions within a small amount of time and distance (many collisions per animation frame of the simulation as the heavy block mashes the light one into a tiny gap). It's going to find the first collision in the frame, figure the impulses to apply to the blocks, and then advance to the next frame, with the effect that the big block happily mashes into the space where the small one resides. All the subsequent collisions that would prevent that are being missed in search of 60 fps.