LoginSignup
3
4

More than 5 years have passed since last update.

CSVでお手軽パラメータ管理

Last updated at Posted at 2015-12-23

Siv3D Advent Calendar 2015 - Qiitaの23日目の記事です。

突然だが、とりとめなく四角形を沢山表示したい。
risou.png

でもこんなソースコードは嫌だ。
見づらいし調整のたびにビルドしなければならない。

test.cpp
Rect(10,10,20,200).draw({255,128,128,255});
Rect(30,30,40,220).draw({128,255,128,255});
Rect(70,50,60,240).draw({128,128,255,255});
Rect(130,70,80,260).draw({128,128,128,255});
Rect(210,90,100,280).draw({255,0,0,255});
Rect(310,110,120,300).draw({0,255,0,255});
Rect(430,130,140,320).draw({0,0,255,255});
Rect(570,150,160,340).draw({255,255,255,255});

そうだ。Siv3DではCSV形式で記述されたファイルを超お手軽に読み込めるではないか。
ということで、CSVを使ってパラメータ管理を効率化してみる。

概要

今回はゲームでの運用を想定し、
CSVファイルに記入した値を以下のように解釈する。

・縦軸:パラメータの種類。攻撃力とかHPとか。
・横軸:パラメータのバリエーション。雑魚敵とかボスとか。

つまりこうだ。
mob.png
※実装例では敵のパラメータではなく、四角形のパラメータを管理する。

今回の紹介でミソとなる点は以下のとおり。

識別子によるパラメータ種類の管理
CSVファイルの列(縦軸)でパラメータの種類(HP、攻撃力)を管理する。
各列にはそれぞれ識別子を設定し、読み込みを自動化することで、パラメータ種類の増減に対応しやすくする。

IDによるパラメータバリエーションの管理
行(横軸)をひとまとまりのパラメータバリエーション(雑魚A、ラスボス)とし、IDで管理する。
IDをキーとすることで、他のパラメータと協調しやすくなる。

パラメータファイルのコメントアウト対応
CSVファイル内で、パラメータを記載したままコメントアウトできるようにする。
調整の際に重宝する。

CSVファイル

CSVではこんなふうにパラメータを記述する。
data.png

1行目を識別子用の行、1列目をコメント用の列として確保している。
また、2列目は識別子を設定せず、列単位でのコメントとして活用する。

実装

実装例は以下のとおり。
CSVファイルの値を変数に読み込み、長方形を描画している。

Main.cpp

# include <Siv3D.hpp>
# include <unordered_map>

//enumを使ってパラメータをID管理する
enum class EnumRectId{
    None            = 0,
    LightRedBox     = 100,
    LightGreenBox   = 200,
    LightBlueBox    = 300,
    RedBox          = 400,
    GreenBox        = 500,
    BlueBox         = 600,
    WhiteBox        = 700,
};

//CSVのパラメータを直接使うより読みやすい変数に移したほうがよい
struct StructRectParam{
    EnumRectId RectId;
    Point Size;
    Point Pos;
    Color Color;
};

void Main()
{
    //CSV読み込み
    CSVReader Csv(L"data.csv");

    //パラメータの各列の識別子を定義
    String RectId = L"RectId";
    String PosX = L"PosX", PosY = L"PosY", SizeX = L"SizeX", SizeY = L"SizeY";
    String ColorR = L"ColorRed", ColorG = L"ColorGreen", ColorB = L"ColorBlue", ColorA = L"ColorAlpha";

    //mapで識別子と対応する列を関連付け
    std::unordered_map<String, int> ItemMap;
    for (int i = 0; i < Csv.columns(0); i++){
        ItemMap[Csv.get<String>(0, i)] = i; //コメント列もマッピングされるが許容
    }   

    //パラメータ本体をArrayではなくmapで管理する
    //それはそれとして一覧用にArrayを準備
    std::unordered_map<EnumRectId, StructRectParam> ParamMap;
    Array<EnumRectId> ParamIdList;

    //パラメータ読み込み処理
    for (int i = 0; i < Csv.rows; i++){
        //一列目が空でないならその行は飛ばす(コメントアウト処理)
        if (Csv.get<String>(i, 0) != L""){ continue; }

        //tmpParamにパラメータを読み込んで、ParamMapに追加
        //識別子mapで識別子を読み込み対象列に変換
        StructRectParam tmpParam;
        //enumは直接読めないので一度intで読んでからenumにキャスト
        tmpParam.RectId = (EnumRectId)Csv.get<int>(i, ItemMap[RectId]);
        tmpParam.Pos =  Point(Csv.get<int>(i, ItemMap[PosX]), Csv.get<int>(i, ItemMap[PosY]));
        tmpParam.Size = Point(Csv.get<int>(i, ItemMap[SizeX]), Csv.get<int>(i, ItemMap[SizeY]));
        tmpParam.Color = Color(
            Csv.get<int>(i, ItemMap[ColorR]), Csv.get<int>(i, ItemMap[ColorG]), Csv.get<int>(i, ItemMap[ColorB]), Csv.get<int>(i, ItemMap[ColorA]));

        ParamMap[tmpParam.RectId] = (tmpParam); //mapにRectIdをキーとして格納
        ParamIdList.push_back(tmpParam.RectId); //ArrayにRectIdを追加
    }

    while (System::Update())
    {
        //パラメータの数だけ長方形を描画
        for (auto prmId : ParamIdList){
            //こういう時にArrayが役に立つ
            Rect(ParamMap[prmId].Pos, ParamMap[prmId].Size).draw(ParamMap[prmId].Color);
        }

        //パラメータとは別の位置にID指定で長方形を描画
        //普段使うならむしろこちらがメインになる
        Rect(Point(50, 400), ParamMap[EnumRectId::GreenBox].Size).draw(ParamMap[EnumRectId::GreenBox].Color);
    }
}

実行するとこんな感じ。
yattaze.png

補足

わざわざIDで呼び出すの回りくどくない?
コメントに記載したとおり、IDをパラメータ群の識別子にしておくと何かと取り回しがいい。
例えば雑魚Aのパラメータが1種類だけならばいいが、実は他にも描画パラメータがあったり、あとから仲間になった時のパラメータが追加になる、などすると収集がつかなくなる。
ここで各項目間で共通のIDがあれば便利だ。雑魚Aに関わることなら全て雑魚AのIDを投げておけばいい。

なぜIDをenumで管理しているの?intかStringでもよくね?
値の直打ちはTypoひとつで問題に繋がる。
IDは似たような字面になることが多いので、うっかり似たような別のIDにしてしまったら原因を突き止めるのも一苦労だ。
VC++2013ならオートコレクト機能があるのでTypoは少なくなるし、長いIDを覚えておく必要がない。
enum classを使用することでIDの属性を明確にすることができる。EnumGameParamIdならゲーム用パラメータIDだし、EnumDrawParamIdなら描画パラメータIDだ。
ソースコードの外でもenumで管理できると楽なのだがこればかりは仕方ない(変換クラスを作るのも本末転倒のような気がする)。
ちなみに、頭にEnumとつけているのはオートコレクトで呼び出しやすいからだ。同じ理由で構造体にもStructをつけている。

ExcelでCSVを開いたままだと読み込めない
ExcelでCSVファイルを開いたまま実行した場合、パラメータが読み込まれないようである。
筆者は別のツールを使って編集している。
これなら開きっぱなしで実行しても問題なく読み込まれる。

まとめ

・CSVは、沢山のパラメータを管理する場合に役立つ
・識別子でパラメータを、IDでバリエーションを管理できると扱いやすい
・enumは直接扱えない。intで読み込んでキャストしよう。
・CSVを開いたままでは読み込まれないことがある。そんな時は編集ツールを変えてみよう。

明日は@hat_a2cさんの投稿です。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4