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.