はじめに
SceneKit で物体を衝突させようと思うといろいろ設定しないといけません
let floorNode: SCNNode = {
let floor = SCNFloor()
floor.firstMaterial?.diffuse.contents = UIColor.blue
let floorNode = SCNNode(geometry: floor)
return floorNode
}()
scnView.scene?.rootNode.addChildNode(floorNode)
let boxNode: SCNNode = {
let box = SCNBox(width: 1.0, height: 1.5, length: 1.0, chamferRadius: 0)
box.firstMaterial?.diffuse.contents = UIColor.red
let boxNode = SCNNode(geometry: box)
boxNode.position = SCNVector3(x: 0, y: 2, z: 0)
boxNode.physicsBody = .init(type: .dynamic, shape: .init(geometry: box))
return boxNode
}()
scnView.scene?.rootNode.addChildNode(boxNode)
上記のコードだと床とキューブは衝突せずにすり抜けてしまいます。床にキューブを衝突させる方法について記載します。
SCNPhysicsBody
物体を衝突させるためには SCNNode
の SCNPhysicsBody
の collisionBitMask
などを設定する必要があります。
SCNNode
の SCNPhysicsBody
はデフォルトが nil
なのでまず SCNPhysicsBody
を設定してやる必要があります。
init(type:shape:)
で初期化し SCNPhysicsBodyType
には下記の3つが指定できます。
- static
床や壁など固定する物体に指定します - dynamic
デフォルトで下方向に重力がかかるので何かにぶつかるまで下に落下します - kinematic
これはどういうときに使うかわからない。。。(ドキュメント)
衝突
物体の衝突には SCNPhysicsBody
の以下の3つのプロパティが関係しています。
- categoryBitMask
この物体のカテゴリ - collisionBitMask
この物体と衝突できる物体のカテゴリ - contactTestBitMask
この物体と衝突したときに通知する物体のカテゴリ
これが非常にややこしいです(ちょっと理解できてるかわからないです。。。)。
categoryBitMask と collisionBitMask
物体が衝突するかどうかはこの2つの値が関係しています。collisionBitMask のドキュメントには下記のように記載があります。
SceneKit compares the body’s collision mask to the other body’s category mask by performing a bitwise AND operation. If the result is a nonzero value, then the body is affected by the collision.
collisionBitMask
と categoryBitMask
の AND(論理積)が 0 以外の場合に衝突が起こるということだと思います。
それぞれのデフォルト値は下記のようになっています。
- categoryBitMask
SCNPhysicsBody
がstatic
の場合はSCNPhysicsCollisionCategory.static
、それ以外の場合はSCNPhysicsCollisionCategory.default
- collisionBitMask
SCNPhysicsCollisionCategory.all
とりあえず下記のように床にも physicsBody
を設定してやると床とキューブは衝突するようになります。
let floorNode: SCNNode = {
let floor = SCNFloor()
floor.firstMaterial?.diffuse.contents = UIColor.blue
let floorNode = SCNNode(geometry: floor)
floorNode.physicsBody = .init(type: .static, shape: .init(geometry: floor)) // ここ
return floorNode
}()
scnView.scene?.rootNode.addChildNode(floorNode)
let boxNode: SCNNode = {
let box = SCNBox(width: 1.0, height: 1.5, length: 1.0, chamferRadius: 0)
box.firstMaterial?.diffuse.contents = UIColor.red
let boxNode = SCNNode(geometry: box)
boxNode.position = SCNVector3(x: 0, y: 2, z: 0)
boxNode.physicsBody = .init(type: .dynamic, shape: .init(geometry: box))
return boxNode
}()
scnView.scene?.rootNode.addChildNode(boxNode)
下記のように色々試してやるとぶつかったりぶつからなかったりすることがわかります。
// ぶつかる
floorNode.physicsBody?.collisionBitMask = 1
boxNode.physicsBody?.categoryBitMask = 1
// ぶつからない
floorNode.physicsBody?.collisionBitMask = 1
boxNode.physicsBody?.categoryBitMask = 2
// ぶつかる
floorNode.physicsBody?.collisionBitMask = 1
boxNode.physicsBody?.categoryBitMask = 3
// ぶつからない
floorNode.physicsBody?.collisionBitMask = 5
boxNode.physicsBody?.categoryBitMask = 10
contactTestBitMask
こちらは SCNPhysicsContactDelegate
で衝突の通知を受け取りたいときに使用するやつです。
contactTestBitMask
と categoryBitMask
の AND(論理積)が 0 以外の場合に通知がとぶようです。
floorNode.physicsBody?.contactTestBitMask = 1
boxNode.physicsBody?.categoryBitMask = 1
scnView.scene?.physicsWorld.contactDelegate = self
上記のように設定してやると床とキューブが衝突する際に下記のデリゲートが呼ばれます。
extension ViewController: SCNPhysicsContactDelegate {
func physicsWorld(_ world: SCNPhysicsWorld, didBegin: SCNPhysicsContact) {
print("Begin!!!")
}
func physicsWorld(_ world: SCNPhysicsWorld, didUpdate: SCNPhysicsContact) {
print("Update!!!")
}
func physicsWorld(_ world: SCNPhysicsWorld, didEnd: SCNPhysicsContact) {
print("End!!!")
}
}
下記のように色々設定して試してみました。
// 衝突するし通知も呼ばれる
floorNode.physicsBody?.contactTestBitMask = 1
boxNode.physicsBody?.categoryBitMask = 1
// 衝突するが通知は呼ばれない
floorNode.physicsBody?.collisionBitMask = 2
floorNode.physicsBody?.contactTestBitMask = 1
boxNode.physicsBody?.categoryBitMask = 2
// 衝突しないが通知は呼ばれる
floorNode.physicsBody?.collisionBitMask = 5
floorNode.physicsBody?.contactTestBitMask = 3
boxNode.physicsBody?.categoryBitMask = 10
// 衝突しないし通知も呼ばれない
floorNode.physicsBody?.collisionBitMask = 5
floorNode.physicsBody?.contactTestBitMask = 21
boxNode.physicsBody?.categoryBitMask = 10
おまけ
チューブの中にキューブを落とそうと思い下記のようにチューブを追加するとキューブが空中に浮いてしまいます。
let tubeNode: SCNNode = {
let tube = SCNTube(innerRadius: 3, outerRadius: 5, height: 3)
tube.firstMaterial?.diffuse.contents = UIColor.green
let tubeNode = SCNNode.init(geometry: tube)
tubeNode.physicsBody = .init(type: .static, shape: .init(geometry: tube))
return tubeNode
}()
floorNode.addChildNode(tubeNode)
よくわかりませんが下記のように修正してやることで無事チューブの中に落とすことができました
tubeNode.physicsBody = .init(type: .static, shape: .init(geometry: tube, options: [.type: SCNPhysicsShape.ShapeType.concavePolyhedron]))
おわりに
ビットマスクというのがよく理解できませんでしたがとりあえず物体同士を衝突させて通知を受信できるようになりました