はじめに
私はSpriteKitをアプリの部品の一部として使ったりするのですが、
ノード(ゲーム中の物体)同士の衝突処理であるcategoryBitMaskやcollisionBitMaskについて
理解に悩んだので書いてみました。
また、私の勝手な解釈を含みますので、考え方の1つとして理解して頂けたら幸いです。
physicsBodyのビットマスク
名前 | 説明 |
---|---|
categoryBitMask | 自分が属するカテゴリ値 |
collisionBitMask | この値とぶつかってくる相手のcategoryBitMaskの値とをAND算出結果が1で衝突する |
contactTestBitMask | 物体と衝突した時に、通知として送る値 |
categoryBitMask
categoryBitMaskという値を使って**「属する物理の世界において、値で違いをつけるもの」**とイメージしてみました。
collisionBitMask
collisionBitMaskという値で**「自分というノードが当てられる側の時」どの世界にいる値(categoryBitMask)のノードと当たるかを決めるもの**と理解してみました。
ここで分かりづらいのですが、衝突される/衝突されないという
れる/られるの関係があるのです。
サンプル
として、サンプルをあげてみたいと思います。
aNode.physicsBody?.categoryBitMask = 0b0001
lightNode.physicsBody?.categoryBitMask = 0b0010
aNode.physicsBody?.collisionBitMask = 0b0010
lightNode.physicsBody?.collisionBitMask = 0b0100
このようにcategoryBitMaskとcollisionBitMaskをそれぞれのノードに設定し、動かして衝突させた場合このような感じです。
ANodeから衝突しに行っても、LightNodeのcollisionBitMaskは**「0100」の世界のノードからしか衝突しないことになっているためLightNodeは動きません。
また、ANodeのcollisionBitMaskは「0010」**の世界のノードと衝突することになっているため、LightNodeから衝突されるとANodeは動きます。
contactTestBitMask
contactTestBitMaskとは**衝突相手のcategoryBitMask値を指定しておくことで衝突を検知できる「設定の値」**と理解してみました。
SKPhysicsContactDelegateでノード同士が衝突したことを検知できる仕組みがあります。
衝突を検知したいノードに、もう片方のcategoryBitMaskを指定することで、
**didBegin(_:)**メソッド内で衝突処理を書くことができます。
また、自身のcollisionBitMaskと必ずしも合わせる必要はなく、
衝突しないけれど重なったことは検知するというような使い方ができます。
サンプル
import Foundation
import SpriteKit
class GameScene: SKScene {
var backgroundNode: SKSpriteNode!
var aNode: SKSpriteNode!
var bNode: SKSpriteNode!
var lightNode: SKShapeNode!
var touchedNode: SKNode?
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
backgroundNode = childNode(withName: "Background") as! SKSpriteNode
backgroundNode.isPaused = true
aNode = childNode(withName: "ANode") as! SKSpriteNode
aNode.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 100, height: 100))
aNode.physicsBody?.affectedByGravity = false
bNode = childNode(withName: "BNode") as! SKSpriteNode
bNode.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 100, height: 100))
bNode.physicsBody?.affectedByGravity = false
lightNode = childNode(withName: "Light") as! SKShapeNode
lightNode.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 50, height: 50))
lightNode.physicsBody?.affectedByGravity = false
aNode.physicsBody?.categoryBitMask = 0b0001
bNode.physicsBody?.categoryBitMask = 0b0010
lightNode.physicsBody?.categoryBitMask = 0b0001
aNode.physicsBody?.collisionBitMask = 0b0001
bNode.physicsBody?.collisionBitMask = 0b0001
lightNode.physicsBody?.collisionBitMask = 0b0001
lightNode.physicsBody?.contactTestBitMask = aNode.physicsBody!.categoryBitMask | bNode.physicsBody!.categoryBitMask
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let pos = touches.first?.location(in: self) {
touchedNode = atPoint(pos)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let pos = touches.first?.location(in: self) {
let action = SKAction.move(to: pos, duration: 0.1)
touchedNode?.run(action)
}
}
}
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
print("------------衝突しました------------")
print("bodyA:\(contact.bodyA.node?.name)")
print("bodyB:\(contact.bodyB.node?.name)")
if contact.bodyA.categoryBitMask == aNode.physicsBody!.categoryBitMask {
lightNode.fillColor = UIColor.yellow
}else if contact.bodyA.categoryBitMask == bNode.physicsBody!.categoryBitMask {
lightNode.fillColor = UIColor.cyan
}
}
func didEnd(_ contact: SKPhysicsContact) {
lightNode.fillColor = UIColor(red: 216/255, green: 216/255, blue: 216/255, alpha: 216/255)
}
}
さいごに
func didBegin(_ contact: SKPhysicsContact)
のcontactが持つbodyA/bodyBの配置について、この順序について
Apple API Reference SKPhysicsContactDelegateのdidBegin(_:)より
The two physics bodies described in the contact parameter are not passed in a guaranteed order.
contactパラメータに記述されている2つの物理的ボディは、保証された順序で渡されません。
上の例ではbodyAにのみ条件を見ていましたが、何らかの理由で入れ替わることがあるかもしれないので、基本的にbodyBに対しても条件をかけていた方が良さそうです。
また、今回の例では以下のように
lightNode.physicsBody?.contactTestBitMask = aNode.physicsBody!.categoryBitMask | bNode.physicsBody!.categoryBitMask
LightNodeに対してcontactTestBitMaskを設定しましたが、
aNode.physicsBody?.contactTestBitMask = lightNode.physicsBody!.categoryBitMask
bNode.physicsBody?.contactTestBitMask = lightNode.physicsBody!.categoryBitMask
このようにANode/BNodeをそれぞれに設定しても
bodyA bodyBに変わりはありませんでした。
contactTestBitMaskには衝突される/衝突されないという関係はないのかなという疑問が残りました。
参考にさせていただいた記事
見て頂いてありがとうございます。