1

Recently I was wondering if I can simulate a perfectly elastic collision with a wall and two balls with different masses with Sprite Kit because my teacher said that it's physics engine performs well in 2D games. And the code I written looks like the following:

This is the file named GameScene.swift.

//
//  GameScene.swift
//  elasticCollisionTest
//
//  Created by harumoto on 2021/11/13.
//

import SpriteKit
import GameplayKit

class GameScene: SKScene, SKPhysicsContactDelegate {
    
    private var collisionCounts: Int = 0
    private let greatestMass: Int = 100
    private let radiusOfBalls: CGFloat = 50
  
    struct categoryBitMaps {
        static let lighterBall: UInt32 = 0x01
        static let heavierBall: UInt32 = 0x02
        static let bottomGround: UInt32 = 0x03
        static let leftWall: UInt32 = 0x04
    }
    
    override func didMove(to view: SKView) {
        self.backgroundColor = .white
        self.physicsWorld.gravity = CGVector.zero
        self.physicsWorld.contactDelegate = self
        let createdLeftWall = createLeftWall()
        let createdBottomGround = createBottomGround()
        let createdLighterBall = createLighterBall()
        let createdHevierBall = createHeavierBall()
        self.addChild(createdLeftWall)
        self.addChild(createdBottomGround)
        self.addChild(createdLighterBall)
        self.addChild(createdHevierBall)
        createdHevierBall.physicsBody!.applyImpulse(CGVector(dx: -greatestMass*30, dy: 0))
    }
    
    func createLighterBall() -> SKShapeNode {
        let lighterBall = SKShapeNode(circleOfRadius: radiusOfBalls)
        lighterBall.position = CGPoint(x: lighterBall.frame.width / 2 + 100, y: lighterBall.frame.height / 2 + 30)
        lighterBall.lineWidth = 0
        lighterBall.fillColor = .blue
        lighterBall.physicsBody = SKPhysicsBody(circleOfRadius: radiusOfBalls)
        lighterBall.physicsBody!.categoryBitMask = categoryBitMaps.lighterBall
        lighterBall.physicsBody!.collisionBitMask = categoryBitMaps.heavierBall | categoryBitMaps.leftWall
        lighterBall.physicsBody!.contactTestBitMask = categoryBitMaps.heavierBall | categoryBitMaps.leftWall
        lighterBall.physicsBody!.linearDamping = 0
        lighterBall.physicsBody!.angularDamping = 0
        lighterBall.physicsBody!.restitution = 1
        lighterBall.physicsBody!.mass = 1
        lighterBall.physicsBody!.usesPreciseCollisionDetection = true
        return lighterBall
    }
    
    func createHeavierBall() -> SKShapeNode {
        let heavierBall = SKShapeNode(circleOfRadius: radiusOfBalls)
        heavierBall.position = CGPoint(x: heavierBall.frame.width / 2 + 250, y: heavierBall.frame.height / 2 + 30)
        heavierBall.lineWidth = 0
        heavierBall.fillColor = .red
        heavierBall.physicsBody = SKPhysicsBody(circleOfRadius: radiusOfBalls)
        heavierBall.physicsBody!.categoryBitMask = categoryBitMaps.heavierBall
        heavierBall.physicsBody!.collisionBitMask = categoryBitMaps.lighterBall
        heavierBall.physicsBody!.contactTestBitMask = categoryBitMaps.lighterBall
        heavierBall.physicsBody!.linearDamping = 0
        heavierBall.physicsBody!.angularDamping = 0
        heavierBall.physicsBody!.restitution = 1
        heavierBall.physicsBody!.mass = CGFloat(greatestMass)
        heavierBall.physicsBody!.usesPreciseCollisionDetection = true
        return heavierBall
    }
    
    func createLeftWall() -> SKShapeNode {
        let leftWall = SKShapeNode(rectOf: CGSize(width: 30, height: frame.height))
        leftWall.position = CGPoint(x: leftWall.frame.width / 2, y: frame.midY)
        leftWall.lineWidth = 0
        leftWall.fillColor = .gray
        leftWall.physicsBody = SKPhysicsBody(rectangleOf: leftWall.frame.size)
        leftWall.physicsBody!.categoryBitMask = categoryBitMaps.leftWall
        leftWall.physicsBody!.collisionBitMask = categoryBitMaps.lighterBall
        leftWall.physicsBody!.contactTestBitMask = categoryBitMaps.lighterBall
        leftWall.physicsBody!.linearDamping = 0
        leftWall.physicsBody!.angularDamping = 0
        leftWall.physicsBody!.restitution = 1
        leftWall.physicsBody!.isDynamic = false
        leftWall.physicsBody!.usesPreciseCollisionDetection = true
        return leftWall
    }
    
    func createBottomGround() -> SKShapeNode {
        let bottomGround = SKShapeNode(rectOf: CGSize(width: frame.width * frame.width, height: 30))
        bottomGround.position = CGPoint(x: frame.midX, y: bottomGround.frame.height / 2)
        bottomGround.lineWidth = 0
        bottomGround.fillColor = .gray
        return bottomGround
    }
    
    func didBegin(_ contact: SKPhysicsContact) {
        let nodeA = contact.bodyA.node!
        let nodeB = contact.bodyB.node!
        if(((nodeA.physicsBody!.categoryBitMask == categoryBitMaps.lighterBall) && (nodeB.physicsBody!.categoryBitMask == categoryBitMaps.heavierBall)) || ((nodeA.physicsBody!.categoryBitMask == categoryBitMaps.heavierBall) && (nodeB.physicsBody!.categoryBitMask == categoryBitMaps.lighterBall))) {
            collisionCounts += 1
        }else if(((nodeA.physicsBody!.categoryBitMask == categoryBitMaps.lighterBall) && (nodeB.physicsBody!.categoryBitMask == categoryBitMaps.leftWall)) || ((nodeA.physicsBody!.categoryBitMask == categoryBitMaps.leftWall) && (nodeB.physicsBody!.categoryBitMask == categoryBitMaps.lighterBall))) {
            collisionCounts += 1
        }
        print(collisionCounts)
    }
}

It worked perfectly with greatestMass=100, but when it goes up to 1,000 or 1,0000, things happened to be weird. Two balls did not go as expected, one of them covered half of another one. In a worse case, one of them just stopped reacting the collision after it reached the wall. This is my first time tried to make something with .physcisBody, is there anything I set wrongly here? And by 3Blue1Brown's video, it is expected to pop out digits of π as the collision count.

  • 1
    The lighter ball appears to check for collisions between the heavier ball and the wall, but the heavier ball only checks for the lighter ball. And the wall only checks for the lighter ball... The bitmasks seem a little inconsistent, maybe the issue lies somewhere in there. – JohnL Nov 15 '21 at 20:22
  • In addition to what JohnL pointed out, you have `static let bottomGround: UInt32 = 0x03`. 3 in binary is 0011 (I'll just show 4 bits for clarity) which means that anything checking for contacts or collisions with `lighterBall` or `heavierBall` (0001 and 0010 respectively) will also match `bottomGround`. More detail here: https://stackoverflow.com/a/40596890/1430420 – Steve Ives Nov 17 '21 at 09:27

0 Answers0