SpriteKitで2D横スクロールゲームをつくろう（その４）

![Xcode-14.1](https://img.shields.io/badge/Xcode-14.1-brightgreen) ![iOS-16.0](https://img.shields.io/badge/iOS-16.0-brightgreen) 

## はじめに
[前回](https://qiita.com/am10/items/8729f77f3ee512a86052)の続きです。

今回はさみしかった画面に敵キャラを登場させます:smiling_imp:

## 素材準備
敵キャラはマヨネーズにしました（とくに意味はありません）。

| m1 | m2 | m3 |
| --- | --- | --- |
|![m1](https://user-images.githubusercontent.com/34936885/205437544-999592a3-0c88-4230-a93f-0e86305d043a.png)|![m2](https://user-images.githubusercontent.com/34936885/205437608-8a331054-b838-4bc8-b8fd-a7888e2ccefd.png)|![m3](https://user-images.githubusercontent.com/34936885/205437641-4a8127d7-93c2-431a-8a07-1c02383fcb7b.png)|

## 実装
細かい当たり判定が難しいので今回は接触すればゲームオーバーになる撃破不可の敵にします。

### とりあえず表示
位置とかは気にせずとりあえず表示します。

```swift
final class GameScene: SKScene {

    override func didMove(to view: SKView) {
        let enemy = makeEnemy()
        enemy.position = .init(x: frame.midX + 30, y: frame.midY)
        addChild(enemy)
        enemy.run(makeEnemyAction())
    }

    private func makeEnemy() -> SKSpriteNode {
        let enemy = SKSpriteNode(texture: .init(imageNamed: "m1"), size: .init(width: 32, height: 32))
        enemy.physicsBody = .init(texture: enemy.texture!, size: enemy.size)
        enemy.physicsBody?.allowsRotation = false
        return enemy
    }

    private func makeEnemyAction() -> SKAction {
        let textures: [SKTexture] = [
            .init(imageNamed: "m2"),
            .init(imageNamed: "m3"),
            .init(imageNamed: "m1"),
        ]
        return .repeatForever(
            .sequence([
                .group([
                    .animate(with: textures, timePerFrame: 0.2),
                    .sequence([
                        .wait(forDuration: 0.2),
                        .applyImpulse(.init(dx: -1, dy: 4), duration: 0.2)
                    ])
                ]),
                .wait(forDuration: 0.5)
            ])
        )
    }
}
```

こんな感じです。

![enemy](https://user-images.githubusercontent.com/34936885/205439905-c2ac207d-27d4-48cf-9dc2-36c336971412.gif)

### 衝突判定
衝突判定に使う `categoryBitMask` などの詳細は下記をご参考ください。

[SpriteKitで衝突判定（Swift）](https://www.am10.blog/archives/513)

```swift
override func didMove(to view: SKView) {
    physicsWorld.contactDelegate = self
    :
    :
    // 床表示のtilePositions.forEachの処理に追加
    node.physicsBody?.categoryBitMask = 0b0100
    node.name = "floor"

    // playerにbitMask設定
    player.name = "player"
    player.physicsBody?.contactTestBitMask = 0b0011
    player.physicsBody?.categoryBitMask = 0b0011
    
    // enemyにbitMask設定
    enemy.name = "enemy"
    enemy.physicsBody?.categoryBitMask = 0b0011        
}
```

下記のように衝突判定のデリゲート設定。これで `player` と `enemy` が接触するとコンソールに「Hit!!!!!!!!!」と表示されます。

```swift
extension GameScene: SKPhysicsContactDelegate {

    func didBegin(_ contact: SKPhysicsContact) {
        guard let nodeA = contact.bodyA.node,
              let nodeB = contact.bodyB.node else {
            return
        }
        if nodeA.name == "enemy" || nodeB.name == "enemy" {
            print("Hit!!!!!!!!!")
        }
    }
}
```

### ゲームオーバー処理
まずはゲームオーバーの画面を作成します。

```swift
final class GameOverScene: SKScene {

    override func didMove(to view: SKView) {
        backgroundColor = .black

        let label = SKLabelNode(text: "Game Over")
        label.fontName = "PixelMplus10-Bold"
        label.fontColor = .systemRed
        label.position = .init(x: frame.midX, y: frame.midY)
        addChild(label)

        let button = SKLabelNode(text: "もう一回やる")
        button.name = "continue"
        button.fontName = "PixelMplus10-Regular"
        button.fontColor = .white
        button.position = .init(x: frame.midX, y: frame.midY - 100)
        addChild(button)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else {
            return
        }

        if atPoint(touch.location(in: self)).name == "continue" {
            view?.presentScene(GameScene(size: size), transition: .doorway(withDuration: 2.0))
        }
    }
}
```

こんな感じです。

<img width="300" src="https://user-images.githubusercontent.com/34936885/205439883-1cf4400a-2691-4445-bf47-99d6f8faa081.png" alt="game_over">

カスタムフォントには下記を使用しました。

[PixelMplus](https://fontfree.me/373)

カスタムフォントの導入方法は下記の記事を参考にしました。

[【Swift】UIKitとSwiftUIでカスタムフォントを使用する](https://dev.classmethod.jp/articles/uikit-swiftui-custom-font/)

あとは衝突判定で `print("Hit!!!!!!!!!")` と書いていたところに下記の遷移処理を追加すれば完成です！

```swift
view?.presentScene(GameOverScene(size: size), transition: .fade(withDuration: 2.0))
```

## おわりに
前回のと合わせるとこんな感じになります。

![game](https://user-images.githubusercontent.com/34936885/205439949-5362249f-452f-4fc4-8aa2-77b1b2b6d098.gif)

次回はゴール判定を実装して完成です:tada:

SpriteKit あまりさわったことないのでもっといい方法あればぜひ教えて下さい:bow:

## まとめ
* [その１](https://qiita.com/am10/items/df42c1f0091e6c4705e2)
* [その２](https://qiita.com/am10/items/d53d1cdf1b973a9f56cc)
* [その３](https://qiita.com/am10/items/8729f77f3ee512a86052)
* [その４](https://qiita.com/am10/items/90e3a2f58ae88e4ad6c4)
* [その５](https://qiita.com/am10/items/deacceda00c034ac29bb)