2018年9月10日月曜日

Spriteの衝突検出

衝突判定検出用delegateの設定

class GameScene: SKScene, SKPhysicsContactDelegate { }

SKPhysicsWorldはSprite世界に関してのクラスで、GameSceneのdidMove()などで以下のように設定しておく。
self.physicsWorld.contactDelegate = self

これによって衝突時に didBegin()メソッド が呼ばれるようになる。

エッジベースの物理体とは

ゲーム中の障害物など、重力の影響を受けないスプライト。
縁(エッジ)しか使われないということか?

GameSceneの場合、範囲として自分のframeを与えてやればいい。
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)

ほかのSpriteは中心位置をoriginにした矩形になっているため、以下のようにwidthとheightの1/2だけ左上にずらしたCGRectで指定する。ちとめんどい。
self.label?.physicsBody = SKPhysicsBody(edgeLoopFrom: 
                  CGRect(x: -((self.label?.frame.size.width)! / 2),
                         y: -((self.label?.frame.size.height)! / 2),
                     width: self.frame.size.width,
                    height: self.frame.size.height))

ボリュームベースの物理体とは

ゲーム中のキャラクターなど、重力の影響を与えるスプライト。

categoryBitMaskの設定

操作する自分のキャラなのか、敵のキャラなのか、壁などの障害物なのかなどをざっくりカテゴライズすることで、衝突の影響を受けるのか、衝突時に didBegin()メソッドでチェックをするのかなどを細かに設定する。
32bitの任意のビットのみを1にすることで設定する。
以下のようにあらかじめカテゴライズ用定数を用意しておくと便利。
let rocketCate:UInt32 = 0x1 << 0
let labelCate:UInt32  = 0x1 << 1
let frameCate:UInt32  = 0x1 << 2

self.physicsBody?.categoryBitMask = frameCate
self.label?.physicsBody?.categoryBitMask = labelCate
sprite.physicsBody?.categoryBitMask = rocketCate

collisionBitMaskの設定

spriteのカテゴリーどうしの衝突(collision)の有無を決める。
これも32bitのプロパティで、categoryBitMaskの値を使って指定し、ビットが1になっているカテゴリーと衝突する。
デフォルトではすべてのカテゴリーどうしが衝突することになり、0に設定するとすり抜ける事になる。
2カテゴリー以上指定する場合は、カテゴリーどうしをor(|)でつないで指定する。
sprite.physicsBody?.collisionBitMask = frameCate | labelCate

contactTestBitMaskの設定

collisionBitMaskの設定だけでは、衝突して跳ね返ったりするものの、その検知がされないので、delegateメソッドのdidBegin()で検知する相手のカテゴリーを指定する。
sprite.physicsBody?.contactTestBitMask = labelCate

応用

collisionBitMaskを設定せず、contactTestBitMaskだけ設定すれば、すり抜けるけど衝突は検知するなんてこともできるわけだ。

衝突時のdelegateメソッド

SKPhysicsContactDelegateのメソッド。
引数のcontactで、どのspriteどうしが衝突したのか検出できる。
衝撃の値と衝突した位置も。
これを使って敵キャラを消したりするわけですな。

func didBegin(_ contact: SKPhysicsContact) {
    let bodyA = contact.bodyA
    let bodyB = contact.bodyB
    print("bodyA = \(String(describing: bodyA.node?.name)), bodyB = \(String(describing: bodyB.node?.name))")
    print("衝撃値 = \(contact.collisionImpulse)")
    print("衝突位置 = \(contact.contactPoint)")
}

撃ち落としたら消す

敵機を発射したミサイルが当たったら撃ち落とす場合は、最後に以下のようなコードを書いておく。あらかじめ敵機のnodeにenemyとでも名前をつけておく。

    if bodyA.node?.name == "enemy" || bodyB.node?.name == "enemy" {
        bodyA.node?.parent?.removeFromParent()
        bodyB.node?.parent?.removeFromParent()
    }

bodyAとbodyBのどっちが敵機、ミサイルになるのかは不定なので、どちらかが敵機だったら双方をremoveするようにする。
爆発の表現(particle)は未実装だが、remove処理の直前に処理を書けばいいだろう。

0 件のコメント:

コメントを投稿