Skip to content

第04回 マップデータの外部化(CSV読込)とスクロール

前回: 第03回 画像表示の導入とエラー確認 / 次回: 第05回 物理パラメーター調整と操作感の改善

前回の振り返り

画像描画と読み込みエラー確認を導入しました

  • LoadGraph で画像を読み込みました
  • 画像ハンドルが -1 の場合は、エラーを表示して初期化失敗にしました
  • プレイヤー、タイル、ゴールの見た目を画像に置き換えました
  • 必須画像が揃っている状態で、移動や当たり判定が同じように動きました
  • 今回は、ステージの形をコード外のCSVから読み込み、横スクロール表示も導入します

今回の目的

コードを書き換えずにマップを変更できるようにする

  • CSVファイルから地形を読み込む
  • 012 の値をステージへ反映する
  • 読み込み失敗時はエラーを表示して停止する
  • CSVを書き換えて地形とゴール位置を変更する
  • cameraX を導入して、ワールド座標を画面座標へ変換する

今回の授業内容

マップデータの外部化(CSV読込)とスクロール

  • CSVマップの仕様
  • fstreamstringstringstream の使い方
  • Stage::LoadFromCsv の実装
  • 読み込み失敗時のエラー処理
  • CSV編集による地形変更
  • 読み込み確認とエラー時の切り分け
  • ワールド座標と画面座標の分離
  • カメラ追従と端クランプの基本

マップを外へ出す

コードを直さず ステージを作ります

データを変えれば 地形が変わる

CSV仕様

授業では最小ルールに固定する

  • カンマ区切りのテキストファイルとして扱う
  • 0: 空白
  • 1: 固形床
  • 2: ゴール
  • 行数は mapHeight、列数は mapWidth を上限にする
  • ゴール 2 が1つも無い場合は読み込み失敗として扱う

CSVファイル例

Assets/Maps/stage01.csv

  • 1行が横方向のタイル列です
  • 上から順に y = 0y = 1 として読みます
  • 2 はゴール位置として扱い、床にはしません
csv
0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,2
0,0,0,0,0,0,0,1,1,1
0,0,0,0,1,1,0,0,0,0
1,1,1,1,1,1,1,1,1,1

Stage.hの変更

CSV読込用の関数を追加する

  • Initialize にCSVパスを渡せるようにします
  • LoadFromCsv は成功したら true、失敗したら false を返します
  • 失敗時はエラーを表示し、初期化を止めます
cpp
class Stage
{
public:
    bool Initialize(const char* csvPath);
    bool LoadFromCsv(const char* csvPath);
    bool IsLoadedFromCsv() const;

private:
    bool LoadImages();
    void ClearMap();
    void SetGoalTile(int tileX, int tileY);

    int tiles[mapHeight][mapWidth] = {};
    Rect goal = { 0, 0, tileSize, tileSize };
    bool loadedFromCsv = false;
};

Initializeの変更

読めなければ初期化失敗にする

  • LoadFromCsv の戻り値を loadedFromCsv に保存します
  • 読み込みに失敗した場合は、エラーを表示して false を返します
  • CSVはステージそのものなので、読めないままゲームを開始しません
cpp
bool Stage::Initialize(const char* csvPath)
{
    if (!LoadImages()) return false;

    loadedFromCsv = LoadFromCsv(csvPath);
    if (!loadedFromCsv)
    {
        MessageBoxW(nullptr,
            L"Assets/Maps/stage01.csv を読み込めません。",
            L"Map Load Error", MB_OK);
        return false;
    }

    return true;
}

bool Stage::IsLoadedFromCsv() const
{
    return loadedFromCsv;
}

CSV読込コード(1/2)

ファイルを開いて1行ずつ読む

  • <fstream> はファイル読み込みに使います
  • <sstream> は1行をカンマ区切りに分解するために使います
  • ファイルが開けない場合は false を返します
cpp
#include <fstream>
#include <sstream>
#include <string>

bool Stage::LoadFromCsv(const char* csvPath)
{
    std::ifstream file(csvPath);
    if (!file.is_open()) return false;

    ClearMap();

    bool foundGoal = false;
    std::string line;
    int y = 0;

    while (y < mapHeight && std::getline(file, line))
    {
        std::stringstream lineStream(line);
        std::string cell;
        int x = 0;

        // 次のスライドの処理でcellを数値に変換する
        y++;
    }

    return foundGoal;
}

CSV読込コード(2/2)

値をタイルとゴールへ反映する

  • std::getline(lineStream, cell, ',') でカンマ区切りの値を取り出します
  • std::stoi で文字列を数値に変換します
  • 変換できない値は 0 として扱います
cpp
while (x < mapWidth && std::getline(lineStream, cell, ','))
{
    int value = 0;

    try { value = std::stoi(cell); }
    catch (...) { value = 0; }

    if (value == 1)
    {
        tiles[y][x] = 1;
    }
    else if (value == 2)
    {
        tiles[y][x] = 0;
        if (!foundGoal)
        {
            SetGoalTile(x, y);
            foundGoal = true;
        }
    }

    x++;
}

ClearMapとSetGoalTile

読み込み前に初期化する

  • ClearMap は、すべてのタイルを空白に戻します
  • SetGoalTile は、タイル座標をピクセル座標へ変換します
  • CSV読込前に初期化しておくと、前のデータが残りません
cpp
void Stage::ClearMap()
{
    for (int y = 0; y < mapHeight; y++)
    {
        for (int x = 0; x < mapWidth; x++)
        {
            tiles[y][x] = 0;
        }
    }

    goal = { (mapWidth - 2) * tileSize,
        (mapHeight - 3) * tileSize, tileSize, tileSize };
}

void Stage::SetGoalTile(int tileX, int tileY)
{
    goal.x = tileX * tileSize;
    goal.y = tileY * tileSize;
    goal.width = tileSize;
    goal.height = tileSize;
}

読み込み失敗を通知する

必須データが無い状態で進めない

  • CSVはステージ本体なので、読めない場合はエラーにします
  • エラー文には、足りないファイル名を含めます
  • エラーを確認したら、ファイル配置とパスを直して再実行します
cpp
if (!loadedFromCsv)
{
    MessageBoxW(nullptr,
        L"Assets/Maps/stage01.csv を読み込めません。",
        L"Map Load Error", MB_OK);
    return false;
}

Game側からCSVを指定する

初期化時にパスを渡す

  • CSVファイルの場所を Stage に渡します
  • 完成サンプルでは Assets/Maps/stage01.csv を使います
  • パスが違う場合は、エラーを表示してゲームを開始しません
cpp
bool Game::Initialize()
{
    if (!LoadImages()) return false;
    if (!stage.Initialize("Assets/Maps/stage01.csv")) return false;

    ResetPlay();
    gameState = Title;
    return true;
}

コードの解説

データを読み、ゲーム内の構造へ変換する

  1. CSVファイルを開く
  2. 1行ずつ文字列として読む
  3. カンマで分割する
  4. 文字列を数値へ変換する
  5. 1 は床として tiles に入れる
  6. 2 はゴール位置として goal に反映する
  7. 失敗時はエラーを表示して初期化を止める

実習1: CSVを読み込む

まずは短い検証マップで確認する

  1. Assets/Maps/stage01.csv を作成する
  2. Stage::LoadFromCsv を実装する
  3. Stage::Initialize("Assets/Maps/stage01.csv") を呼ぶ
  4. CSVの 1 の位置を変えて床が変わるか確認する
  5. CSVの 2 の位置を変えてゴールが変わるか確認する

実習2: エラー時の動作を確認する

失敗時に止まることを確認する

  • CSVファイル名を一時的に変える
  • パスを間違えた状態で起動する
  • エラー表示でゲームが開始されないことを確認する
  • デバッグ表示や画面表示で、CSV読込成功かどうかを確認する
  • 確認後、正しいファイル名に戻す

よくあるつまずき

CSVはテキストとして扱う

  • Excelで保存した形式がCSVではない
  • カンマではなく全角カンマが入っている
  • 行数や列数が想定と大きく違う
  • 実行フォルダから見た Assets/Maps/stage01.csv が存在しない
  • ゴール値 2 が入っておらず、読み込み成功扱いにならない

今日の最低到達

コード編集なしでマップを変える

  • CSVから床タイルを読み込める
  • CSVからゴール位置を読み込める
  • CSV変更がゲーム画面に反映される
  • CSVが読めない場合はエラーを表示して停止できる
  • 012 の意味を説明できる

今回のまとめ

マップをCSVファイルから読み込みました

  • ステージの形をコード外のデータとして扱いました
  • fstreamstringstream でCSVを読みました
  • 読込失敗時にエラーを表示して停止する処理を入れました
  • CSV編集だけで、地形とゴール位置を変更できるようにしました
  • 次回は、操作感を数値で調整します

おつかれさまでした!

次回予告 第05回 物理パラメーター調整と操作感の改善

同じ仕組みでも 数値で遊びやすさが変わります