# 第24回 発展教材：複数ステージ化

第24回の1ステージ版をRelease成果物まで完成させた受講者向けの任意課題です。完成版を壊さないよう、現在のプロジェクトフォルダーを別名でコピーしてから作業します。

## この発展で作るもの

- 大きさの異なる`Stage01.csv`と`Stage02.csv`を順番に読み込む。
- 各CSVの`PlayerSpawn`からプレイヤーを開始する。
- 途中のゴールでは次のステージへ進む。
- 最後のゴールだけをゲームクリアにする。
- ゲームオーバー時は現在のステージから再開する。
- タイトルまたは最終クリアから開始すると第1ステージへ戻る。

## 1. 第2ステージを追加する

このフォルダーの`Stage02.csv`を、作業用プロジェクトの`Assets/Maps`へコピーします。サンプルは36列×15行で、48列×15行の`Stage01.csv`とは横幅が異なります。

幅と高さを別の定数へ書く必要はありません。`Stage::LoadFromCsv()`がCSVの列数と行数から`mapWidth_`と`mapHeight_`を決めます。

各ステージには、次のセルを1つずつ置きます。

| CSV値 | `StageCell` | 役割 |
| --- | --- | --- |
| `1` | `Goal` | 次のステージまたは最終クリアへ進む位置 |
| `2` | `PlayerSpawn` | そのステージのプレイヤー開始位置 |

## 2. ステージ一覧を作る

`Game.cpp`の先頭で`<array>`と`<utility>`をインクルードし、無名名前空間へステージファイル一覧を追加します。

```cpp
#include <array>
#include <utility>

namespace dxgame::platformer
{
    namespace
    {
        constexpr std::array<const wchar_t*, 2> StageFiles =
        {
            L"Assets/Maps/Stage01.csv",
            L"Assets/Maps/Stage02.csv"
        };

        // 既存のToStateName()はこの下へ残す
    }
}
```

CSVの順番を変えたい場合は、この配列だけを編集します。`Stage::Draw()`や衝突処理へステージごとの分岐は追加しません。

## 3. 現在のステージ番号を持つ

`Game.h`へ`<cstddef>`を追加し、`Game`のprivateへ読み込み関数と現在番号を追加します。

```cpp
#include <cstddef>

class Game
{
    // 既存部分は省略

private:
    bool LoadStage(std::size_t stageIndex);

    // 既存メンバーは省略
    std::size_t currentStageIndex_ = 0;
};
```

添字なので`int`ではなく`std::size_t`を使います。CSVへ現在番号を保存しません。CSVは1ステージの配置、`Game`は複数ステージの進行を管理します。

## 4. 読み込みを`LoadStage()`へ集約する

`Game.cpp`へ次の関数を追加します。

```cpp
bool Game::LoadStage(std::size_t stageIndex)
{
    if (stageIndex >= StageFiles.size())
        return false;

    Stage nextStage;
    if (!nextStage.Initialize(StageFiles[stageIndex]))
    {
        const std::wstring message =
            std::wstring(StageFiles[stageIndex]) + L"\n" +
            nextStage.GetLastLoadError();
        MessageBoxW(nullptr, message.c_str(), L"Map Load Error", MB_OK);
        return false;
    }

    stage_ = std::move(nextStage);
    currentStageIndex_ = stageIndex;
    return true;
}
```

一度`nextStage`へ正常に読み込んでから`stage_`と入れ替えます。読み込みに失敗したとき、現在プレイ中のステージまで途中状態で上書きしないためです。
失敗時は`GetLastLoadError()`から行・列・原因を取得し、読み込もうとしたファイル名と一緒に表示します。

`Game::Initialize()`にある直接読み込みを、次の呼び出しへ置き換えます。

```cpp
if (!LoadStage(0))
    return false;
```

## 5. 途中のゴールで次へ進む

`Game::UpdatePlaying()`のゴール判定を置き換えます。

```cpp
if (player_.GetHitbox().Overlaps(stage_.GetGoalTrigger()))
{
    const std::size_t nextStageIndex = currentStageIndex_ + 1;
    if (nextStageIndex < StageFiles.size())
    {
        if (!LoadStage(nextStageIndex))
        {
            ChangeState(GameState::GameOver);
            return;
        }

        ResetStage();
        return;
    }

    ChangeState(GameState::GameClear);
    return;
}
```

次のCSVを読み込んだ後、既存の`ResetStage()`を利用します。`ResetStage()`は新しいステージの`PlayerSpawn`、敵配置、ワールド幅を参照するため、Player、EnemyManager、Cameraを同じタイミングで初期化できます。

## 6. 第1ステージから開始し直す

タイトル画面のZキー処理を変更します。

```cpp
case GameState::Title:
    if (input_.IsPressed(Key::Jump) && LoadStage(0))
        ChangeState(GameState::Playing);
    break;
```

`GameClear`と`GameOver`を同じ処理にまとめず、分けて記述します。

```cpp
case GameState::GameClear:
    if (input_.IsPressed(Key::Jump) && LoadStage(0))
        ChangeState(GameState::Playing);
    else if (input_.IsPressed(Key::BackToTitle))
        ChangeState(GameState::Title);
    break;

case GameState::GameOver:
    if (input_.IsPressed(Key::Jump))
        ChangeState(GameState::Playing);
    else if (input_.IsPressed(Key::BackToTitle))
        ChangeState(GameState::Title);
    break;
```

ゲームオーバーでは現在のCSVを読み直さず、現在ステージの開始状態へ戻します。最終クリア後のZキーでは`LoadStage(0)`を呼び、第1ステージから遊び直します。

## 7. ステージ番号を表示する

`Game::DrawHud()`へ確認用表示を追加します。

```cpp
DrawFormatString(470, 30, textColor, L"STAGE: %d/%d",
    static_cast<int>(currentStageIndex_ + 1),
    static_cast<int>(StageFiles.size()));
```

## 確認項目

- タイトルから開始すると`Stage01.csv`が読み込まれる。
- 第1ステージのゴールでは結果画面を出さず、第2ステージへ進む。
- 第2ステージではCSVに置いた開始位置から始まる。
- 横幅が変わってもカメラがステージ端で止まる。
- 第2ステージでゲームオーバーになり、Zキーで第2ステージから再開できる。
- 第2ステージのゴールでのみ`GAME CLEAR`が表示される。
- 最終クリア後のZキー、またはタイトルへ戻ってからの開始で第1ステージへ戻る。

## 仕上げ課題

`Stage03.csv`を自作し、`StageFiles`へ1行追加してください。ゲーム進行のコードを変更せず、CSVと一覧への登録だけで3ステージ目が動けば完成です。
