【C++】CSVファイルでゲームのマップを作る方法|タイルマップ入門

サムネイル画像 C++

今回は、C++でCSVファイルをゲームのマップ(タイルマップ)として活用する方法について解説していきます。

この記事を読み終えると、あなたはCSVを使ったマップ管理をマスターできると思いますので、ぜひ最後まで読んでいただけると嬉しいです。

こんな悩みはありませんか?

  • ゲームのマップをソースコードに直書きしていて、修正がつらい
  • マップを変えるたびにコンパイルし直すのが面倒
  • CSVでマップを管理したいけど、C++での読み込み方がわからない

この記事では、CSVファイルにタイルIDを並べてゲームのマップを作る方法を、コンソール表示から2Dゲームへの応用まで解説します!

CSVマップとは?

CSVマップとは、

マップを構成するタイルのID(番号)をCSVファイルに並べたもの

です。

たとえば、5×5のマップを以下のようなCSVで表現できます。

▼map.csv

0,0,0,0,0
0,1,1,1,0
0,1,2,1,0
0,1,1,1,0
0,0,3,0,0

各数字がタイルIDで、IDに意味を割り当てることでマップが完成します。

タイルID意味コンソール表示記号
0道(地面).
1#
2スタート地点S
3ゴール地点G

CSVはテキストエディタで編集できるため、コンパイルなしでマップを変更できるのが最大のメリットです。

CSVを読み込んでマップを表示してみよう

C++でCSVを読み込む流れはこうです。

  1. std::ifstream でCSVファイルを開く
  2. std::getline で1行ずつ取得する
  3. std::stringstream + std::getline(ss, cell, ',') でカンマ区切りを分割する
  4. std::stoi で文字列を整数に変換し、2次元vectorに格納する

タイルIDによる処理の分岐にはswitch文を使います。switch文の使い方についてはこちらの記事で詳しく解説しています。

▼main.cpp


#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>

using MapData = std::vector<std::vector<int>>;

// タイルIDを表示用の記号に変換
char tileToChar(int id) {
    switch (id) {
        case 0: return '.'; // 道
        case 1: return '#'; // 壁
        case 2: return 'S'; // スタート
        case 3: return 'G'; // ゴール
        default: return '?';
    }
}

// CSVファイルを読み込んでマップデータを返す
MapData loadMap(const std::string& filePath) {
    MapData mapData;
    std::ifstream file(filePath);

    if (!file) {
        std::cout << "マップファイルを開けませんでした\n";
        return mapData;
    }

    std::string line;
    while (std::getline(file, line)) {
        if (line.empty()) continue; // 空行をスキップ

        std::vector<int> row;
        std::stringstream ss(line);
        std::string cell;

        while (std::getline(ss, cell, ',')) {
            // Windows環境の '\r' を除去
            if (!cell.empty() && cell.back() == '\r') {
                cell.pop_back();
            }
            row.push_back(std::stoi(cell));
        }
        mapData.push_back(row);
    }
    return mapData;
}

int main() {
    MapData map = loadMap("map.csv");

    for (const auto& row : map) {
        for (int tile : row) {
            std::cout << tileToChar(tile);
        }
        std::cout << "\n";
    }

    return 0;
}

実行結果

.....
.###.
.#S#.
.###.
..G..

CSVに書いた数字がそのままマップとして表示されましたね!

なお、loadMap関数を別ファイルに切り出してヘッダーに宣言を書く方法については、プロトタイプ宣言の記事を参考にしてみてください。

2Dゲームのタイルマップへの応用

コンソールで確認できたら、次は2Dゲームへの応用です。

2DゲームではタイルIDに対応した画像を座標に配置することでマップを描画します。基本的な考え方は次のとおりです。


// 2Dゲームへの応用イメージ(DirectX / DXLibなどを想定した疑似コード)
const int TILE_SIZE = 32; // 1タイル = 32×32ピクセル

void drawMap(const MapData& mapData) {
    for (int y = 0; y < (int)mapData.size(); y++) {
        for (int x = 0; x < (int)mapData[y].size(); x++) {
            int tileId = mapData[y][x];
            float drawX = (float)(x * TILE_SIZE);
            float drawY = (float)(y * TILE_SIZE);

            // tileIdに対応した画像を(drawX, drawY)に描画
            // DrawBox(drawX, drawY, tileTextures[tileId]); ← 使用するライブラリの関数に置き換える
        }
    }
}

ポイントは「列番号 × タイルサイズ = X座標」「行番号 × タイルサイズ = Y座標」という計算式です。

タイルサイズを32にすれば、5×5マップは画面上で160×160ピクセルの領域に描画されます。CSVを書き換えるだけでステージの形が変わるので、ステージデザインが格段に楽になります!

私が実際にCSVマップで困った体験談

個人でローグライク風ゲームを自主制作していたときの話です。

最初はマップをソースコードの中にint配列でべた書きしていました。


// こんな感じで直書きしていた(修正のたびにコンパイルが必要)
int map[5][5] = {
    {0, 0, 0, 0, 0},
    {0, 1, 1, 1, 0},
    {0, 1, 2, 1, 0},
    {0, 1, 1, 1, 0},
    {0, 0, 3, 0, 0}
};

ステージが増えるにつれて、配列だらけのコードがどんどん肥大化。しかも数字の羅列だけではどんなマップなのか全く想像できないという問題もありました。「0と1がズラッと並んでいても、頭の中でマップが浮かばない…」という状態です。

CSVに外出しするようにしてからは、テキストエディタでマップを視覚的に編集できるようになり、ステージ追加の作業が格段に楽になりました。外部ファイルから読み込むのでコンパイルも不要です。

「ゲームのデータはなるべくコードの外に出す」という考え方の大切さを、この経験から身をもって学びました。

CSVマップのよくある失敗例と解決策

① ファイルが開けない(パスの間違い)

最も多いトラブルです。map.csvの置き場所に注意しましょう。

  • コマンドラインから実行する場合:実行ファイルと同じフォルダに置く
  • Visual Studioの場合:プロジェクトフォルダ(.vcxprojと同じ場所)に置く

// ❌ チェックを忘れると何も表示されず終了してしまう
std::ifstream file("map.csv");

// ✅ 必ず開けたか確認する
if (!file) {
    std::cout << "ファイルが開けませんでした\n";
    return 1;
}

解決策if (!file)で必ずエラーチェックを入れましょう。エラーメッセージが出れば「ファイルが見つからない問題」だとすぐ気づけます。

② CSVの末尾の空行で余分なデータが読み込まれる

テキストエディタによっては、保存時にファイル末尾に空行が入ることがあります。そのまま読み込むと、空の行でも処理しようとしてクラッシュの原因になります。


// ✅ 空行はcontinueでスキップ
while (std::getline(file, line)) {
    if (line.empty()) continue;
    // 処理...
}

解決策if (line.empty()) continue; の1行を入れるだけで防げます。

③ '\r' が残って std::stoi が失敗する

Windowsで作ったCSVファイルの改行は \r\n です。std::getline で1行読むと末尾に '\r' が残り、std::stoi("0\r") のような形になって例外が発生します。


// ✅ '\r' を削除してから std::stoi に渡す
while (std::getline(ss, cell, ',')) {
    if (!cell.empty() && cell.back() == '\r') {
        cell.pop_back(); // '\r' を除去
    }
    row.push_back(std::stoi(cell));
}

解決策cell.back() == '\r' のチェックと pop_back() を入れましょう。この記事のサンプルコードには最初から含まれています。

まとめ

CSVマップについてまとめます。

  • CSVマップ:タイルIDをCSVに並べてゲームのマップを表現する仕組み
  • std::ifstream + std::getline + std::stringstream の組み合わせでCSVを読み込める
  • タイルIDをswitch文で記号や画像に変換することでマップを表現する
  • 2Dゲームでは「列番号 × タイルサイズ = X座標」「行番号 × タイルサイズ = Y座標」でタイルを配置する
  • コードに直書きするよりCSVにする方が、編集・管理がずっと楽になる
  • ファイルチェック・空行スキップ・'\r'除去の3点を忘れずに!

CSVマップを使えば、テキストエディタでステージを直感的に編集できるようになります。ゲーム制作のワークフローをぐっと改善できますので、ぜひ取り入れてみてください!

ここまで読んでくださり、ありがとうございました。

この記事が皆様の学習に役立てば幸いです。

関連記事