Eee41151b01614a69a5032e18f1daa64
Swift 游戏开发之「方块弹珠」(三)

前言

在上一篇文章中,我们已经完成了使用 SpriteKit 实践一些小的 demo 实例,对 SpriteKit 有了一个大体上的实践体验,在这篇文章中,我们主要关注在刚体和刚体之间,也就是小球和方块之间的碰撞交互,整个游戏的核心也就在这。

碰撞检测

在 SpriteKit 中进行刚体和刚体之间的碰撞检测,需要对这两个刚体所处的 SKScene 设置对 SKPhysicsContactDelegate 协议的遵守,这样我们的 GameScene 就可以接收到在其之中的各个刚体之间发生碰撞的「通知」。

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) {

    }
}

想要让两个刚体之间发生碰撞,这部分内容我们已经在上一篇文章中学习过了,现在我们需要进行的是如何让两个刚体之间发生碰撞后,我们的 GameScene 能够接收到的碰撞通知,定一个结构体。

struct BitMask {
    static let Ball = UInt32(0x00001)
    static let Box = UInt32(0x00002)
    static let Ground = UInt32(0x00003)
}

在这个结构体 BitMask 中定义了三个两种常量值,这些常量值被称为「碰撞检测掩码」,通过使用 physicsBodycontactTestBitMask 定义物体的类型。默认情况下,如果我们不给刚体设置 contactTestBitMask,该值为 0,也就是说这种刚体不属于任何碰撞检测的类型。

SKPhysicsContactDelegate 协议方法中的参数 SKPhysicsContact 对象参数包含了此次碰撞的相关信息,如碰撞点和力的大小。

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 }
}

有了「碰撞检测掩码」,我们就可以在碰撞检测回调中判断当前到底是什么刚体和什么刚体发生了碰撞。

extension GameScene: SKPhysicsContactDelegate {
    func didBegin(_ contact: SKPhysicsContact) {

    }

    func didEnd(_ contact: SKPhysicsContact) {
        print(contact.bodyA.contactTestBitMask)
        print(contact.bodyB.contactTestBitMask)
    }
}

bitMaks

physicsBody 中含有三个属性值,

  • categoryBitMask,定义刚体所属的类别,供碰撞检测进行区分,默认属于所有类型。
  • collisionBitMask,定义刚体可与哪种类别的其它刚体发生碰撞,默认可与所有类型发生碰撞。
  • contactTestBitMask,定义刚体可与哪种类别的其它刚体发生接触,默认不与所有类型发生接触,供代理实现中调用。

这三个属性值分别控制了 SpriteKit 中物体和物体之间的碰撞关系。在我们的这个游戏中,小球和小球之间是不能发生碰撞的,而小球和方块之间是可以发生碰撞的,而我们需要在 SpriteKit 的接触代理方法中判断出当前发生碰撞的两个对象物体分别是什么。

```swift
class GameScene: SKScene {
// ...

private func createContent() {
    for row in 0..<10 {
        let ball = Ball(circleOfRadius: 10)
        ball.fillColor = .red
        addChild(ball)
        ball.physicsBody = SKPhysicsBody(circleOfRadius: 10)
        // 设置初速度
        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
    // 静态物体
    box.physicsBody?.isDynamic = false
    box.physicsBody?.restitution = 1
    addChild(box)
top Created with Sketch.