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.
  • collisionBitMaskDefines which class of rigid bodies can occur with other rigid bodiesThe collision, can collide with all types by default.
  • contactTestBitMaskDefines 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…