SpriteKitの衝突処理について(categoryBitMask collisionBitMask contactTestBitMask 使い方)

# はじめに
私はSpriteKitをアプリの部品の一部として使ったりするのですが、
ノード(ゲーム中の物体)同士の衝突処理であるcategoryBitMaskやcollisionBitMaskについて
理解に悩んだので書いてみました。
また、私の勝手な解釈を含みますので、考え方の1つとして理解して頂けたら幸いです。

# physicsBodyのビットマスク
|名前|説明|
|:--|:--|
|categoryBitMask|自分が属するカテゴリ値|
|collisionBitMask|**この値**と**ぶつかってくる相手のcategoryBitMaskの値**とをAND算出結果が1で衝突する|
|contactTestBitMask|物体と衝突した時に、通知として送る値|

# categoryBitMask
categoryBitMaskという値を使って**「属する物理の世界において、値で違いをつけるもの」**とイメージしてみました。

![world.png](https://qiita-image-store.s3.amazonaws.com/0/128250/c633cdb2-cc01-a609-0763-45297925cde4.png)

# collisionBitMask
collisionBitMaskという値で**「自分というノードが当てられる側の時」どの世界にいる値(categoryBitMask)のノードと当たるかを決めるもの**と理解してみました。

![Collision.png](https://qiita-image-store.s3.amazonaws.com/0/128250/6758ea2f-c7e7-146d-a5c6-deee530e1034.png)

ここで分かりづらいのですが、**衝突される/衝突されない**という
**れる/られる**の関係があるのです。

## サンプル
**ANode**
![Green.png](https://qiita-image-store.s3.amazonaws.com/0/128250/29cf7b4a-7827-6116-a0b1-0ec81a2efdbc.png)

**LightNode**
![Light.png](https://qiita-image-store.s3.amazonaws.com/0/128250/5f5abe2b-1c34-c5cd-950b-dceab0090791.png)

として、サンプルをあげてみたいと思います。

```GameScene.swift
aNode.physicsBody?.categoryBitMask = 0b0001
lightNode.physicsBody?.categoryBitMask = 0b0010
        
aNode.physicsBody?.collisionBitMask = 0b0010
lightNode.physicsBody?.collisionBitMask = 0b0100
```

![Sample.png](https://qiita-image-store.s3.amazonaws.com/0/128250/c06fe136-e5a9-d2f1-9900-c1bc36c512e9.png)

このようにcategoryBitMaskとcollisionBitMaskをそれぞれのノードに設定し、動かして衝突させた場合このような感じです。

![sample.gif](https://qiita-image-store.s3.amazonaws.com/0/128250/4adf7433-c867-632c-fbb3-579223223344.gif)

ANodeから衝突しに行っても、LightNodeのcollisionBitMaskは**「0100」**の世界のノードからしか衝突しないことになっているためLightNodeは動きません。
また、ANodeのcollisionBitMaskは**「0010」**の世界のノードと衝突することになっているため、LightNodeから衝突されるとANodeは動きます。

# contactTestBitMask
contactTestBitMaskとは**衝突相手のcategoryBitMask値を指定しておくことで衝突を検知できる「設定の値」**と理解してみました。

![Test.png](https://qiita-image-store.s3.amazonaws.com/0/128250/1b30b4b8-eba6-a40b-7655-7ed9c753a6d1.png)

SKPhysicsContactDelegateでノード同士が衝突したことを検知できる仕組みがあります。
衝突を検知したいノードに、もう片方の**categoryBitMask**を指定することで、
**didBegin(_:)**メソッド内で衝突処理を書くことができます。
また、**自身のcollisionBitMask**と必ずしも合わせる必要はなく、
**衝突しないけれど重なったことは検知する**というような使い方ができます。

## サンプル
**ANode**
![Green.png](https://qiita-image-store.s3.amazonaws.com/0/128250/29cf7b4a-7827-6116-a0b1-0ec81a2efdbc.png)

**BNode**
![Blue.png](https://qiita-image-store.s3.amazonaws.com/0/128250/4777ef33-045f-abbf-a545-c24031de42c3.png)

**LightNode**
![Light.png](https://qiita-image-store.s3.amazonaws.com/0/128250/5f5abe2b-1c34-c5cd-950b-dceab0090791.png)

```GameScene.swift
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)
    }
}
```

![light.gif](https://qiita-image-store.s3.amazonaws.com/0/128250/99fe8a5c-1ffe-89ed-5677-6ca00f9a8fe7.gif)

# さいごに

```swift
func didBegin(_ contact: SKPhysicsContact)
```

の**contact**が持つbodyA/bodyBの配置について、この順序について

[Apple API Reference SKPhysicsContactDelegateのdidBegin(_:)](https://developer.apple.com/reference/spritekit/skphysicscontactdelegate/1449595-didbegin)より
> The two physics bodies described in the contact parameter are not passed in a guaranteed order.

> contactパラメータに記述されている2つの物理的ボディは、保証された順序で渡されません。 

上の例ではbodyAにのみ条件を見ていましたが、何らかの理由で入れ替わることがあるかもしれないので、基本的にbodyBに対しても条件をかけていた方が良さそうです。

また、今回の例では以下のように

```swift
lightNode.physicsBody?.contactTestBitMask = aNode.physicsBody!.categoryBitMask | bNode.physicsBody!.categoryBitMask
```

**LightNode**に対して**contactTestBitMask**を設定しましたが、

```swift
aNode.physicsBody?.contactTestBitMask = lightNode.physicsBody!.categoryBitMask
bNode.physicsBody?.contactTestBitMask = lightNode.physicsBody!.categoryBitMask
```

このように**ANode/BNode**をそれぞれに設定しても
bodyA bodyBに変わりはありませんでした。

<img width="236" alt="print.png" src="https://qiita-image-store.s3.amazonaws.com/0/128250/61379e50-8a7a-d95c-fd16-05024097d0b3.png">

contactTestBitMaskには**衝突される/衝突されない**という関係はないのかなという疑問が残りました。

# 参考にさせていただいた記事
- [カテゴリマスクと衝突マスクで衝突するノードを分ける　はじはじアプリ体験記](http://hajihaji-lemon.com/smartphone/swift/spritekit_collision/)

- [SpriteKitの CategoryBitMask、CollisionBitMask、ContactTestBitMask まとめ Qiita](http://qiita.com/hadashiA/items/4faf77fddc202070e37c)




見て頂いてありがとうございます。