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) 

## はじめに
SpriteKit を使って 2D 横スクロールゲームをつくってみます。なぜ Unity を使わないんだ？と疑問を持つ方もいるかもしれませんがとくに意味はありません。なんか面白そうなので SpriteKit を使ってみます。

記事は３つくらいになる予定です（たぶん）。

今回は歩いたり跳ねたりするプレイヤーを実装していきます。

## 素材準備
クリスマスも近いのでチキンをプレイヤーにします:chicken:

歩き用

| c1 | c2 |
| --- | --- |
|![c1](https://user-images.githubusercontent.com/34936885/204984008-4f64e2c6-1727-4ac1-bb35-6042986234e9.png)|![c2](https://user-images.githubusercontent.com/34936885/204984016-cd324985-0a72-4b07-b9db-ad794dc416f9.png)|

ジャンプ用

| j1 | j2 |
| --- | --- |
|![j1](https://user-images.githubusercontent.com/34936885/204984057-e02f2b44-aac1-412d-b823-1239da9a4f4d.png)|![j2](https://user-images.githubusercontent.com/34936885/204984122-f6371a83-c723-4780-b3fd-9424915051dc.png)|

## 実装
View の表示については下記をご参考ください。本記事でやるのは基本的に SKScene の実装のみです。

[SwiftUIでSpriteKit](https://qiita.com/am10/items/98e52550c9b3bc1a948e)

### 表示
とりあえず画面にプレイヤーを表示します。

```swift
final class GameScene: SKScene {

    private let player = SKSpriteNode(texture: .init(imageNamed: "c1"), size: .init(width: 32, height: 32))
    override func didMove(to view: SKView) {
        backgroundColor = .init(red: 51/255, green: 51/255, blue: 51/255, alpha: 1.0)

        let floor = SKShapeNode(rectOf: .init(width: 1000, height: 10))
        floor.name = "floor"
        floor.position = .init(x: frame.midX, y: 0)
        floor.fillColor = .white
        floor.physicsBody = .init(rectangleOf: .init(width: 1000, height: 10))
        floor.physicsBody?.isDynamic = false
        addChild(floor)

        player.position = .init(x: frame.midX, y: frame.midY)
        player.physicsBody = .init(texture: player.texture!, size: player.size)
        // ころがらないように設定
        player.physicsBody?.allowsRotation = false
        addChild(player)
    }
}
```

こんな感じです。

<img width="111" alt="chicken" src="https://user-images.githubusercontent.com/34936885/204983614-fa575c71-4958-4d38-baff-c3fe84739fa0.png">

### 歩行
歩行はボタンを押し続けている限り動作を継続したいので状態を保持して状態判定でプレイヤーを移動させるようにしました（都度アニメーションをつけるとカクカクしたので `repeatForever` にしてます）。

```swift
// プレイヤーの状態
enum PlayerStatus {
    /// 走行中
    case running
    /// ジャンプ準備中
    case prepareJump
    /// ジャンプ中
    case jumping
    /// 停止中
    case pause
}

final class GameScene: SKScene {

    // 追加
    private var status = PlayerStatus.pause
    private let runTextures: [SKTexture] = [
        .init(imageNamed: "c2"),
        .init(imageNamed: "c1"),
    ]

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        status = .running
        // アニメーション開始
        let action = SKAction.animate(with: runTextures, timePerFrame: 0.2)
        player.run(.repeatForever(action), withKey: "running")
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        // アニメーション停止
        player.removeAction(forKey: "running")
        status = .pause
    }

    override func update(_ currentTime: TimeInterval) {
        switch status {
        case .running:
            // プレイヤーを移動させる
            player.physicsBody?.velocity = CGVector(dx: 120, dy: 0)
        default:
            break
        }
    }
}
```

こんな感じです。

![run](https://user-images.githubusercontent.com/34936885/204983706-e382aa1b-9e6a-4b32-aaf7-4ba79e804572.gif)

### ジャンプ
２段ジャンプを防止しようと思ったらわりとややこしくなりました。

```swift
final class GameScene: SKScene {
    // 追加
    private let jumpTextures: [SKTexture] = [
        .init(imageNamed: "j1"),
        .init(imageNamed: "j2"),
    ]

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if status == .jumping || status == .prepareJump {
            return
        }
        status = .prepareJump
        et action = SKAction.group([
            .animate(with: jumpTextures, timePerFrame: 0.2),
            .sequence([
                .wait(forDuration: 0.2),
                .applyImpulse(.init(dx: 2, dy: 13), duration: 0.2)
            ])
        ])
        player.run(action) {
            self.player.run(.setTexture(self.runTextures[1]))
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
            self.status = .jumping
        }
    }

    override func update(_ currentTime: TimeInterval) {
        switch status {
        case .jumping:
            if player.physicsBody?.velocity.dy == 0 {
                status = .pause
            }
        default:
            break
        }
    }
}
```

こんな感じです。

![jump](https://user-images.githubusercontent.com/34936885/204983733-378229f0-8a2f-44be-926e-b5b91b044d27.gif)

## おわりに
てきとーにボタン配置するとこんな感じになります。

![game](https://user-images.githubusercontent.com/34936885/204983842-dc1b80e0-49cb-4064-bac5-342881fe8a67.gif)

次回はステージを作ります！

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)