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/90e3a2f58ae88e4ad6c4)の続きです。

今回はついにゴール作成です:sunglasses:

## 実装
ゴール判定は迷ったのですが指定のブロックに接触したらゴールしたことにします。

横に 50 個ブロックをならべているので最後の３つのブロックを判定に使います。

### クリア画面作成
クリア画面を作成します。

```swift
final class ClearScene: SKScene {

    private let jumpTextures: [SKTexture] = [
        .init(imageNamed: "j1"),
        .init(imageNamed: "j2"),
    ]

    override func didMove(to view: SKView) {
        backgroundColor = .init(red: 51/255, green: 51/255, blue: 51/255, alpha: 1.0)

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

        let floor = SKShapeNode(rectOf: .init(width: 1000, height: 10))
        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)

        let player = SKSpriteNode(texture: .init(imageNamed: "c1"), size: .init(width: 32, height: 32))
        player.position = .init(x: frame.midX, y: 20)
        player.physicsBody = .init(texture: player.texture!, size: player.size)
        player.physicsBody?.allowsRotation = false
        addChild(player)

        let action = SKAction.group([
            .animate(with: jumpTextures, timePerFrame: 0.2),
            .sequence([
                .wait(forDuration: 0.2),
                .applyImpulse(.init(dx: 0, dy: 13), duration: 0.2)
            ])
        ])
        player.run(.repeatForever(.sequence([
            action,
            .wait(forDuration: 0.5)
        ])))

        if let snow = SKEmitterNode(fileNamed: "Snow") {
            snow.position = .init(x: size.width/2, y: size.height)
            addChild(snow)
        }
    }
}
```

こんな感じです。

![clear](https://user-images.githubusercontent.com/34936885/205442003-244c648d-0351-4c16-933a-f4ffee3bdc6e.gif)

### ゴール判定
`GameScene` の `didMove` のタイルの `physicsBody` 設定処理を下記のように修正します。

```swift
tilePositions.forEach {
    let node = SKSpriteNode()
    node.name = "floor"
    node.position = .init(x: CGFloat($0.column) * tileSize.width + tileSize.width/2,
                          y: CGFloat($0.row) * tileSize.height + tileSize.height/2)
    node.physicsBody = .init(rectangleOf: tileSize)
    node.physicsBody?.isDynamic = false
    // ここ追加
    if $0.column > tilePositions.last!.column - 3 {
        node.physicsBody?.categoryBitMask = 0b0011
        node.name = "goal"
    } else {
        node.physicsBody?.categoryBitMask = 0b0100
    }
    tileMap.addChild(node)
}

```

あとは `SKPhysicsContactDelegate` の衝突判定で下記の遷移処理を追加すれば完成です。

```swift
if nodeA.name == "goal" || nodeB.name == "goal" {
    view?.presentScene(ClearScene(size: size), transition: .fade(withDuration: 2.0))
}
```

### 微調整
敵の数など微調整していきます。

敵が１体はさみしいので３体表示。

```swift
let enemyPositions: [CGFloat] = [400, 800, 950]
enemyPositions.forEach { x in
    let enemy = makeEnemy()
    enemy.position = .init(x: x, y: frame.midY)
    addChild(enemy)
    enemy.run(makeEnemyAction())
}
```

ブロック数修正。

```swift
private let tilePositions: [(column: Int, row: Int)] = [
        (0, 0), (0, 1),
        (1, 0), (1, 1),
        (2, 0), (2, 1),
        (3, 0), (3, 1),
        (4, 0), (4, 1),
        (5, 0), (5, 1),
        (6, 0), (6, 1),
        (7, 0), (7, 1),
        (8, 0), (8, 1),
        (9, 0), (9, 1),
        (10, 0), (10, 1),
        (11, 0), (11, 1),
        (12, 0), (12, 1),
        (13, 0), (13, 1), (13, 2),
        (14, 0), (14, 1), (14, 2), (14, 3),
        (15, 0), (15, 1),
        (16, 0), (16, 1),
        (17, 0), (17, 1),
        (18, 0), (18, 1),
        (19, 0), (19, 1),
        (20, 0), (20, 1),
        (21, 0), (21, 1),
        (22, 0), (22, 1),
        (23, 0), (23, 1),
        (24, 0), (24, 1),
        (25, 0), (25, 1),
        (26, 0), (26, 1),
        (27, 0), (27, 1),
        (28, 0), (28, 1),
        (29, 0), (29, 1),
        (30, 0), (30, 1),
        (31, 0), (31, 1),
        (32, 0), (32, 1),
        (33, 0), (33, 1),
        (34, 0), (34, 1),
        (35, 0), (35, 1),
        (36, 0), (36, 1),
        (37, 0), (37, 1),
        (38, 0), (38, 1),
        (39, 0), (39, 1),
        (40, 0), (40, 1),
        (41, 0), (41, 1),
        (42, 0), (42, 1),
        (43, 0), (43, 1), (43, 2),
        (44, 0), (44, 1), (44, 2), (44, 3),
        (45, 0), (45, 1), (45, 2), (45, 3), (45, 4),
        (46, 0), (46, 1), (46, 2), (46, 3), (46, 4), (46, 5),
        (47, 0), (47, 1),
        (48, 0), (48, 1),
        (49, 0), (49, 1),
    ]

// ここのrowsも修正
let tileMap = SKTileMapNode(tileSet: tileSet, columns: tilePositions.last!.column + 1, rows: 6, tileSize: tileSize)
```

プレイヤーの開始位置修正。

```swift
player.position = .init(x: frame.midX, y: frame.midY)
```

完成品はこんな感じです。

<iframe width="362" height="643" src="https://www.youtube.com/embed/kdzrXBCjF7w" title="SpriteKitで2D横スクロールゲームをつくろう" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## おわりに
これで SpriteKit を使った 2D 横スクロールゲームは完成です:confetti_ball:

あとは SE とか付けるともっとそれっぽくなると思います。SKAudioNode 使うとできそう。`SKNode` はその他にも色々あるので下記記事を参考にどうぞ。

[SKNodeまとめ（Swift）](https://www.am10.blog/archives/502)

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)