preface
In the last article, we had a little hands-on experience with SpriteKit using a few small demo examples. In this article, we focused on the collision interaction between rigid bodies, namely balls and squares, which is the core of the game.
Collision detection
Collision detection between rigidbodies in SpriteKit requires the SKPhysicsContactDelegate protocol to be set to the SKScene of both rigidbodies. This way our GameScene can receive “notifications” of collisions between the rigid-bodies within it.
class GameScene: SKScene {
override init(size: CGSize) {
super.init(size: size)
physicsWorld.contactDelegate = self
}
// ...
}
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact){}func didEnd(_ contact: SKPhysicsContact){}}Copy the code
To make a collision between two rigid bodies, which we learned in the last article, we now need to make a structure that our GameScene will receive notification of a collision between two rigid bodies.
struct BitMask {
static let Ball = UInt32(0x00001)
static let Box = UInt32(0x00002)
static let Ground = UInt32(0x00003)}Copy the code
Three binary constant values are defined in this structure, BitMask. These values are called “collision detection masks” and the object type is defined using physicsBody’s contactTestBitMask. By default, if we do not set contactTestBitMask for the rigidbody, this value is 0, which means that the rigidbody is not of any collision detection type.
The SKPhysicsContact object parameter in the SKPhysicsContactDelegate protocol method contains information about the collision, such as the collision point and the force.
open class SKPhysicsContact : NSObject {
open var bodyA: SKPhysicsBody { get }
open var bodyB: SKPhysicsBody { get }
open var contactPoint: CGPoint { get }
open var contactNormal: CGVector { get }
open var collisionImpulse: CGFloat { get}}Copy the code
With the “collision detection mask”, we can determine what rigidbody is currently colliding with what rigidbody in the collision detection callback.
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact){}func didEnd(_ contact: SKPhysicsContact) {
print(contact.bodyA.contactTestBitMask)
print(contact.bodyB.contactTestBitMask)
}
}
Copy the code
bitMaks
PhysicsBody contains three property values,
categoryBitMask
, defines the rigid body category for collision detection to distinguish, default belongs to all types.collisionBitMask
Defines which class of rigid bodies can occur with other rigid bodiesThe collision, can collide with all types by default.contactTestBitMask
Defines which class of rigid bodies can occur with other rigid bodiescontact, does not touch all types by default and is called in the proxy implementation.
These three property values control the collision relationship between objects and objects in SpriteKit, respectively. In our game, balls do not collide with balls, but balls do collide with squares, and we need to use SpriteKit’s contact proxy method to figure out what the two objects currently colliding are.
class GameScene: SKScene {
// ...
private func createContent(a) {
for row in 0..<10 {
let ball = Ball(circleOfRadius: 10)
ball.fillColor = .red
addChild(ball)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 10)
// Set the initial speed
ball.physicsBody?.velocity = CGVector(dx: 300 + CGFloat(row) * 0.1, dy: 300)
ball.position = CGPoint(x: size.width / 2, y: 400)
ball.physicsBody?.categoryBitMask = BitMask.Ball
ball.physicsBody?.contactTestBitMask = BitMask.Box
ball.physicsBody?.collisionBitMask = BitMask.Box
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.restitution = 1
}
let box = Box(rectOf: CGSize(width: 50, height: 50))
box.position = CGPoint(x: 300, y: 800)
box.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 50, height: 50))
box.physicsBody?.categoryBitMask = BitMask.Box
box.physicsBody?.collisionBitMask = BitMask.Box
box.physicsBody?.contactTestBitMask = BitMask.Ball
box.fillColor = .blue
// Static object
box.physicsBody?.isDynamic = false
box.physicsBody?.restitution = 1
addChild(box)
}
// ...
}
Copy the code
At this point, running the project, our ball is ready to collide with the cube. Further, we need to remove the cube from the view when the ball makes contact with it. Have GameScene comply with SKPhysicsContactDelegate.
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
switch contact.bodyA.categoryBitMask {
case BitMask.Box:
checkNodeIsBox(contact.bodyA.node)
default:
break
}
switch contact.bodyB.categoryBitMask {
case BitMask.Box:
checkNodeIsBox(contact.bodyB.node)
default:
break}}}extension GameScene {
private func checkNodeIsBox(_ node: SKNode?). {
guard let box = node else { return }
if box.physicsBody?.categoryBitMask = = BitMask.Box {
box.removeFromParent()
}
}
}
Copy the code
Square of diminishing
When the ball hits a square, we assign a level number to the square. For example, when the number of levels on a block is 8, it takes the ball to hit the block 8 times to eliminate the block. Add one more child node lableNode to the block, which is used to record the remaining number of hits of the current block.
class GameScene: SKScene {
// ...
private func createContent(a) {
// ...
for row in 1.5 {
let box = Box(rectOf: CGSize(width: 50, height: 50))
box.position = CGPoint(x: 50 + (row * 50 + 20), y: (800 - row * 50 + 20))
box.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 50, height: 50))
box.physicsBody?.categoryBitMask = BitMask.Box
box.physicsBody?.contactTestBitMask = BitMask.Ball
box.physicsBody?.collisionBitMask = BitMask.Box
box.physicsBody?.linearDamping = 0
box.physicsBody?.restitution = 1.0
box.physicsBody?.isDynamic = false
box.fillColor = .red
let label = Label(text: "\(row)")
label.fontSize = 22
label.typoTag = Awesome!
label.fontName = "Arial-BoldMT"
label.color = .white
label.position = CGPoint(x: 0, y: -label.frame.size.height / 2)
box.addChild(label)
addChild(box)
}
}
}
Copy the code
In GameScene’s proxy callback method, we improved the checkNodeIsBox to allow detection of the number of hits remaining on each block.
extension GameScene {
private func checkNodeIsBox(_ node: SKNode?). {
guard let box = node else { return }
if box.physicsBody?.categoryBitMask = = BitMask.Box {
let label = box.children.first! as! Label
var tag = Int(label.text!)!
if (tag > 1) {
tag - = 1
label.text = "\(tag)"
} else {
box.removeFromParent()
}
}
}
}
Copy the code
At this point, running the project, we found that the ball and the square can be reduced collision detection!!
conclusion
In this article, we continue the unfinished collision detection between balls and squares in the previous article. By understanding and applying the three bitMask attributes in SKNode, we ensure that there is no collision between balls in GameScene, between balls and squares, between balls and walls, and between balls and ground. And the blocks have the ability to fade away, the game’s core detection logic has been completed. In the next article we will complete the ball launch and recovery logic.
What we have done now is:
- Game explanation;
- Familiar with 2D programming;
- Rigid body collision and detection;
- Ball launch and block elimination;
- Game logic improved.
GitHub address: github.com/windstormey…