SceneKitのSCNPhysicsContactDelegateで衝突判定を行う

大好評の「[ペチャバト](https://itunes.apple.com/jp/app/id1444974380?mt=8)」にも用いられているSceneKitの物理判定API、Physicsについてを書きたいと思います。

![GIFイメージ-96F071D4106D-1.gif](https://qiita-image-store.s3.amazonaws.com/0/111366/9828765e-af3d-c7a8-76e7-c648849d3556.gif)

# SCNPhysicsContactDelegate

よく使われると思うのが、[**SCNPhysicsContactDelegate**](https://developer.apple.com/documentation/scenekit/scnphysicscontactdelegate)で、当たり判定が発生したときに呼ばれるメソッドが以下のように定義されています。

- func physicsWorld(SCNPhysicsWorld, **didBegin**: SCNPhysicsContact)
- func physicsWorld(SCNPhysicsWorld, **didUpdate**: SCNPhysicsContact)
- func physicsWorld(SCNPhysicsWorld, **didEnd**: SCNPhysicsContact)

didBegin, didUpdate, didEndで呼ばれる３種類のメソッドがあり、それぞれ呼ばれるタイミングが異なります。今回は物体同士の衝突が終わったら呼ばれる、**didBegin**を例に、実際に２つの物体がぶつかったときのハンドリング方法を簡単に紹介します。

# まずはDelegateをセット

まずは、 sceneView.scene.physicsWorldのcontactDelegateにViewControllerをセット

```swift
override func viewDidLoad() {
    super.viewDidLoad()

    sceneView.scene.physicsWorld.contactDelegate = self
}

extension ViewController: SCNPhysicsContactDelegate {
    func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {

    }
}
```

# 当たったら呼ばれるメソッドを定義

そして、SCNPhysicsContactDelegateのphysicsWorld(didBeginを定義していきます。

引数にはSCNPhysicsWorldとSCNPhysicsContactがあって、前者は環境の情報、後者は当たり判定の情報です。

```swift
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
    let nodeA = contact.nodeA
    let nodeB = contact.nodeB
}
```

以下のようなケースでは、contact.nodeAかcontact.nodeBに、青いボールか赤いマトのNodeがそれぞれセットされています。

![GIFイメージ-96F071D4106D-1.gif](https://qiita-image-store.s3.amazonaws.com/0/111366/9828765e-af3d-c7a8-76e7-c648849d3556.gif)

# 当たり判定を行う

例えば、以下のように的のNodeとボールのNodeが定義されているとしましょう。

```swift
let matoNode = SCNNode()
matoNode.name = "mato"

let ballNode = SCNNode()
ballNode.name = "ball"
```

その場合SCNPhysicsContactDelegateのメソッドにおいて、以下のように当たったかどうか判定します。(もっとスマートな書き方あるかもしれませんが)

```swift
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
    let nodeA = contact.nodeA
    let nodeB = contact.nodeB
    
    if (nodeA.name == "mato" && nodeB.name == "ball") || (nodeB.name == "mato" && nodeA.name == "ball") {
        // 当たった！！
    }
}
```

当たったときに、触覚フィードバックをしたり、相手に当たり情報を送信したりして、シューティングバトルを成立させることができます。

# 当たり判定を行うためのNodeの設定

実は、ただ単にNodeを設定しただけでは、当たり判定は発火しません。先にあげたマトのNodeとボールのNodeをそれぞれ具体的に設定してみましょう。

## マトのNode

SCNPhysicsShapeを設定する必要があります。geometryを引数にわたします。

```swift
let cylinder = SCNCylinder(radius: 0.1, height: 0.05)
let matoNode = SCNNode(geometry: box)
node.name = "mato"
node.position = SCNVector3Make(0, 0, -1.5)

// add PhysicsShape
let shape = SCNPhysicsShape(geometry: cylinder, options: nil)
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
node.physicsBody?.isAffectedByGravity = false
```

## ボールのNode

当てる側のSCNPhysicsBodyには**contactTestBitMask**を設定するのを忘れずに！
こちらデフォルトは0ですが、そのままだと当たり判定が発火しません。

```swift
let ball = SCNSphere(radius: 0.1)
ball.firstMaterial?.diffuse.contents = UIColor.blue

let ballNode = SCNNode(geometry: ball)
ballNode.name = "ball"

// add PhysicsShape
let shape = SCNPhysicsShape(geometry: ball, options: nil)
ballNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
ballNode.physicsBody?.contactTestBitMask = 1
ballNode.physicsBody?.isAffectedByGravity = false
```

以上！

# まとめ

- SceneKitのSCNPhysicsContactDelegateのメソッドには３種類ある
- SCNPhysicsContactからnodeAとnodeBがあるので、その名前からどのNodeとどのNodeが衝突したか判定できる
- SCNPhysicsShapeを設定するのを忘れずに
- 当てる側にはContactTestBitMaskも忘れずに

# サンプルコード

https://github.com/kboy-silvergym/ARKit-Emperor のPhysicsにあります！

# 参考になる記事
- [SpriteKitの CategoryBitMask、CollisionBitMask、ContactTestBitMask まとめ](https://qiita.com/hadashiA/items/4faf77fddc202070e37c)
- [iOS で SceneKit を試す(Swift 3) その66 - PhysicsBody の当たり判定PhysicsShape について](http://appleengine.hatenablog.com/entry/2017/08/15/170630)