【Swift】 SpriteKit 入門 ④

SpriteKitの使い方を解説します。
[SpriteKit 入門 ③](https://qiita.com/tosssy/items/45572121a33eb73b991e) で自機(画像)から弾を発射することができるようになりました。今回は敵を表示して衝突判定を実装していきます。
（環境：Xcode12.1 　Swift5.3）

##敵の表示

前回学んだ`SKShapeNode`を使用して敵を表示します。今回は簡易的に、画面上部から出現するようにしてみました。出現するx座標は`CGFloat.random()`を使用してランダムにしています。

```swift:MyScene.swift
    var enemy: SKShapeNode?
    var lastUpdateTime2 : TimeInterval = 0
    //...
    //敵
    func enemies(){
        self.enemy = SKShapeNode.init(circleOfRadius: 50)
        if let enemy = self.enemy {
            enemy.position = CGPoint(x: CGFloat.random(in: -self.size.width/2 + 100 ... self.size.width/2 - 100), y: self.size.height/2 + enemy.yScale)
            enemy.fillColor = SKColor.blue
            enemy.lineWidth = 5
            enemy.strokeColor = SKColor.white
            enemy.run(SKAction.sequence([SKAction.move(by: CGVector(dx: 0, dy: -self.size.height - 100), duration: 10),SKAction.removeFromParent()]))
            self.addChild(enemy)
        }
    }
    //...
    //一定時間毎の処理
    override func update(_ currentTime: TimeInterval) {
        //...
        if (lastUpdateTime2 == 0) {
            self.lastUpdateTime2 = currentTime
        }
        if (lastUpdateTime2 + 1 <= currentTime) {
            //1秒毎に敵を表示
            self.enemies()
            lastUpdateTime2 = currentTime
        }
    }
```
<img width="300" alt="SK1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498332/469e58bc-a399-727d-e3b8-8acc38664d1c.png">

##衝突判定

物理的な挙動にかかる処理には`SKPhysicsBody`を使用します。衝突判定には`SKPhysicsBody`のプロパティとして

 - `categoryBitMask`
 - `collisionBitMask`
 - `contactTestBitMask`

を使用します。これらの詳細については以下の記事が参考になると思います。

 - [SpriteKitの衝突処理について(categoryBitMask collisionBitMask contactTestBitMask 使い方)](https://qiita.com/KikurageChan/items/c5934a4624c9b6e4e38d)
 - [SpriteKitの CategoryBitMask、CollisionBitMask、ContactTestBitMask まとめ](https://qiita.com/hadashiA/items/4faf77fddc202070e37c)

`contactTestBitMask`で指定したNode(正確にはcategoryBitMask)と接触した際には、

 - `didBegin(_ contact: SKPhysicsContact)`
 - `didEnd(_ contact: SKPhysicsContact)`

のメソッドが呼ばれます。これらのメソッドを利用するためにはあらかじめ`SKPhysicsContactDelegate`を追加しておく必要があります。
コードは次のようになります。

```swift:MyScene.swift
class MyScene: SKScene, SKPhysicsContactDelegate {
    //...
    override func sceneDidLoad() {
        //...
        //Delegateの指定
        physicsWorld.contactDelegate = self
    }

    //自機から発射する弾
    func repeatShoots(){
        self.enemy = SKShapeNode.init(circleOfRadius: 50)
        if let shoot = self.shoot {
            shoot.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 30, height: 30))
            shoot.physicsBody?.affectedByGravity = false
            shoot.physicsBody?.categoryBitMask = 0b0001
            shoot.physicsBody?.collisionBitMask = 0b0000
            shoot.physicsBody?.contactTestBitMask = 0b0001
            //...
        }
    }

    //敵
    func enemies(){
        self.enemy = SKShapeNode.init(circleOfRadius: 50)
        if let enemy = self.enemy {
            enemy.physicsBody = SKPhysicsBody(circleOfRadius: 50)
            enemy.physicsBody?.affectedByGravity = false
            enemy.physicsBody?.categoryBitMask = 0b0001
            enemy.physicsBody?.collisionBitMask = 0b0000
            enemy.physicsBody?.contactTestBitMask = 0b0001
            //...
        }
    }

    func didBegin(_ contact: SKPhysicsContact) {
        if (contact.bodyA.categoryBitMask == self.enemy?.physicsBody!.categoryBitMask || contact.bodyB.categoryBitMask == self.enemy?.physicsBody!.categoryBitMask){
            self.removeChildren(in: [contact.bodyA.node!,contact.bodyB.node!])
        }
    }
```

`didBegin(_ contact: SKPhysicsContact)`で衝突したNodeは`.bodyA`、`.bodyB`で取得することができます。ただし、AとBの順番は保証されないので注意が必要です。また`self.removeChildren(in: )`は取り除くNodeが1つの場合でも配列の形で引数を与える必要があります。

実行結果はこのようになりました。
![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498332/4359ddf5-1dee-7eb6-a587-df81f3f32a36.gif)


##まとめ
ここまでで

 - SpriteKitSceneファイル
 - SKView
 - SKScene
 - SKSpriteNode
 - SKShapeNode
 - SKAction
 - SKPhysicsBody
 - TimeInterval
 - タッチ操作

について扱いました。これらだけでもアイデア次第でいろんなゲームが作れそうですね。
~~次回はまだ扱っていない`SKEmitterNode`の使い方について解説します。~~
次回は一度に発射する弾の数を増やしてみようと思います。

###参考
この記事は以下の情報を参考にして執筆しました。

 - [SpriteKitの衝突処理について(categoryBitMask collisionBitMask contactTestBitMask 使い方)](https://qiita.com/KikurageChan/items/c5934a4624c9b6e4e38d)
 - [SpriteKitの CategoryBitMask、CollisionBitMask、ContactTestBitMask まとめ](https://qiita.com/hadashiA/items/4faf77fddc202070e37c)
 - [Swiftにおけるデリゲート (delegate)の使い方とは【初心者向け】](https://techacademy.jp/magazine/15055)