概要
SpriteKit の物理エンジンで使える衝突判定の設定について実際に手を動かしながらどうさ確認してみました
今更感もありますが個人的な備忘録として残しておきます
環境
- macOS X 10.13.1
- Xcode Version 9.1 (9B55)
categoryBitMask を設定してみる
とりあえず categoryBitMask
だけをそれぞれ設定して実行できるところまで準備しましょう
GameScene.sks
ファイルには SKSpriteNode を縦に適当に 3 つ配置しておきます
ちなみに以下では青ノード (node1)、緑ノード (node2)、ピンクノード (node3) として扱います
またピンクノードを落下させて挙動を確認しています
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var node1 = SKSpriteNode()
var node2 = SKSpriteNode()
var node3 = SKSpriteNode()
let Node1: UInt32 = 0x1 << 1
let Node2: UInt32 = 0x1 << 2
let Node3: UInt32 = 0x1 << 3
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
node1 = self.childNode(withName: "node1") as! SKSpriteNode
node1.physicsBody = SKPhysicsBody(rectangleOf: node1.size)
node1.physicsBody?.affectedByGravity = false
node1.physicsBody?.isDynamic = true
node1.physicsBody?.categoryBitMask = Node1
node2 = self.childNode(withName: "node2") as! SKSpriteNode
node2.physicsBody = SKPhysicsBody(rectangleOf: node2.size)
node2.physicsBody?.affectedByGravity = false
node2.physicsBody?.isDynamic = true
node1.physicsBody?.categoryBitMask = Node2
node3 = self.childNode(withName: "node3") as! SKSpriteNode
}
func touchDown(atPoint pos : CGPoint) {
if let node = atPoint(pos) as? SKSpriteNode {
if node == node3 {
node3.physicsBody = SKPhysicsBody(rectangleOf: node3.size)
node3.physicsBody?.affectedByGravity = true
node3.physicsBody?.isDynamic = true
node3.physicsBody?.categoryBitMask = Node3
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
}
friction や linearDumping, mass などは設定しないで OK です
この状態で実行すると以下のように衝突後 3 つのノードが下に落下します
ちなみに categoryBitMask を適当に変更しても状況は変わりません
ノード間の衝突ロジックはデフォルトだと通過ではなく衝突になるようです
これを通過できるようにするのに collisionBitMask の設定をいじります
collisionBitMask を設定してみる
次に collisionBitMask を設定して挙動を確認してみます
まずは各ノードにそれぞれ設定します
node1.physicsBody?.collisionBitMask = Node1
node2.physicsBody?.collisionBitMask = Node2
node3.physicsBody?.collisionBitMask = Node3
でこれで挙動を確認すると以下のように変わります
青 (node1) 、緑 (node2) ともに衝突せずピンク (node3) がすーっと通過していきました
なぜこうなるかというと
例えば、緑のカテゴリ (categoryBitMask) は 2進数表現で「100」です
ピンクの衝突フラグ (collisionBitMask) は 2進数表現で「1000」です
これの 2 つ値の論理積を取ると「0」になります
そう、0 になる 2 つノード間の categoryBitMask と collisionBitMask の論理積が 0 になると衝突せず通過となります
逆に論理積が 1 となると衝突します
一番始めのデモではそれぞれ衝突しましたが、デフォルトだと collisionBitMask はすべてのノードに衝突する設定になっているようです (print すると nil でした)
ではこれを踏まえて青 (node1) とピンク (node3) だけ衝突するようにしています
node3.physicsBody?.collisionBitMask = Node1
に変更しましょう
node1 側の collisionBitMask は変更しなくて OK です
これで実行すると以下のようになります
青には衝突するようになりました
が青が動かなくなりました
isDynamic = true
にしているのに止まります
もし衝突したときに青も動かしたい場合には node1 の collisionBitMask を 0 にします (もしくは設定しません)
node1.physicsBody?.collisionBitMask = 0
すると以下のような挙動になります
緑で止まるのは青の collisionBitMask が全衝突判定 (0 or nil) になっているからです
ここで少し補足なのですが collisionBitMask が 0 or nil どちらでも同じ挙動になったということです
そして 0 or nil の場合は他と衝突した場合に isDynamic の設定の影響を受けるのですが明示的に設定している場合 (例えば今回のように node3.physicsBody?.collisionBitMask = Node1
などとした場合) は isDynamic が必ず false の挙動になるようです
これはおそらく仕様かなと思います (バグなような気もしますが、、、)
なので今回の場合、青に衝突してピンクと一緒に落下するけど緑はスルーしたいというケースはおそらく設定できないと思います
もし設定できる方法があれば教えていただきたいです
ちなみに青をスルーして緑に衝突させて停止させたい場合は以下のように設定します
node2.physicsBody?.collisionBitMask = Node2
node3.physicsBody?.collisionBitMask = Node2
さらにちなみに node2 の collisionBitMask の設定を削除すると緑も一緒に落下していきます
contactTestBitMask を設定してみる
これを設定することで衝突時にコールバックとして didBegin メソッドがコールされるようになります
didBegin メソッドは以下のような感じで実装しました
func didBegin(_ contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
print(firstBody)
print(secondBody)
}
上記が書けたら contactTestBitMask を各ノードに設定してみます
何も考えずそれぞれに設定してみます
node1.physicsBody?.contactTestBitMask = Node1
node2.physicsBody?.contactTestBitMask = Node2
node3.physicsBody?.contactTestBitMask = Node3
で実行しても didBegin メソッドはコールされないと思います
なぜか、node3.physicsBody?.contactTestBitMask = Node3
の設定の意味は
node3 が Node3 の categoryBitMask を設定しているノードに衝突したときに didBegin をコールする
という意味になります
今回の場合、node3 は node1 or node2 に衝突します
なので、以下のように書き換えてあげることがで didBegin メソッドがコールされるようになります
node3.physicsBody?.contactTestBitMask = Node1
これで node1 に衝突もしくは通過したときに didBegin メソッドがコールされるようになります
見ての通り青は通過、緑で衝突するように collisionBitMask を設定しています
更に上記の通り contactTestBitMask は青を通過したときに didBegin がコールされるようにしています
ここでやりたくなるのが「緑と衝突したときも didBegin がコールされたい」だと思います
その場合は以下のように設定し直しましょう
node3.physicsBody?.contactTestBitMask = Node1 + Node2
これで実行すると緑と衝突したときもコールされるようになります
こんな感じで衝突したいノードのカテゴリ情報を加算していけば OK です
firstBody と secondBody について
didBegin 内で衝突した 2 つノード情報を取得することができます
firstBody が衝突された側で secondBody が衝突した側になります
今回のサンプルで言うとピンクが second で 青や緑が first になります
青は緑に衝突する可能性があるので second になり得る可能性もあります
最後に
SpriteKit の衝突判定で使用する categoryBitMask, collisionBitMask, contactTestBitMask の挙動についてまとめてみました
categoryBitMask は名前の通りノードに設定するカテゴリみたいなものです
カテゴリに設定した情報を元に collisionBitMask や contactTestBitMask に設定した値と比較して衝突するのかスルーするのかコールバックメソッドをコールするのかしないのかを決定する感じです
基本は論理積を取って 0 は何もしない 1 ならアクションという感じです
正直ややこしいです
たぶん自分も今回の記事の説明を聞いただけではさっぱり理解できないと思います
なので、やはり手を動かしながら実際に挙動を確認して理解するのが良いかなと思います
SpriteKit の物理エンジンや衝突判定はややこしいですがこれを理解しないとおもしろいゲームは作れないと思うので頑張って理解してみてください
0 件のコメント:
コメントを投稿