Edited at
Siv3DDay 6

早い・簡単・便利な Config ファイルクラスを作る (C++/OpenSiv3D)

プログラムを再ビルドせずにパラメータを変更 できれば、ゲームやアプリを高速に開発できます。本記事では、TOML 形式のテキストファイルに int32double, String, Vec2, ColorF, Rect, Array<Point>, Array<String> といった型の値を記述し、プログラムの実行中に値を書き換えたときに、実行中のアプリケーションに反映されるような Config ファイルを扱う便利クラスを紹介します。

最短のケースで、テキストファイルに 1 行、プログラムに 2 行追加するだけで、実行中に更新可能な変数・配列を作れるようになります。


ライブラリ

Config.hpp をダウンロードしてプロジェクトに追加します。


使い方

設定記述ファイルをプロジェクトフォルダ (App/ フォルダ内) に配置します。

ここでは config.toml というファイルを作ります。

TOML の仕様については TOML仕様の和訳 が参考になります。


config.toml

[Setting]

a = 123
b = "text"
c = "(0.1, 0.2, 0.3)"
d = [10, 20, 30, 40, 50]

記述されたデータ全てを管理する ConfigData クラスを作ります。

struct Setting setting のように、セクションごとに内部で構造体を定義すると扱いやすくなります。

void ConfigData::reload(const TOMLReader& toml) では、それぞれの内部の構造体に対して S3DCFG_RELOAD_SECTION をします。

それぞれの構造体は void reload(const TOMLReader& toml) メンバを持ち、最初にセクション名で S3DCFG_LOAD_SECTION したあと、すべての値に S3DCFG_LOAD_VALUE または S3DCFG_LOAD_ARRAY します。

これで、サンプルプログラムのように、いつでも値を更新できるようになります。


Main.cpp

# include <Siv3D.hpp> // OpenSiv3D v0.3.1

# include "ConfigFile.hpp"

struct ConfigData
{
void reload(const TOMLReader& toml)
{
// 新しい構造体を作ったらここに追加
S3DCFG_RELOAD_SECTION(setting);
}

// [] セクションごとに構造体を作ると便利
struct Setting
{
int32 a;
String b;
ColorF c;
Array<int32> d;

// それぞれの構造体に reload 関数を定義する
void reload(const TOMLReader& toml)
{
// 先頭に S3DCFG_LOAD_SECTION
S3DCFG_LOAD_SECTION(U"Setting");
// LOAD_VALUE で値をロード
S3DCFG_LOAD_VALUE(a);
S3DCFG_LOAD_VALUE(b);
S3DCFG_LOAD_VALUE(c);
// 配列の場合は LOAD_ARRAY
S3DCFG_LOAD_ARRAY(d);
}
} setting;
};

using Config = ConfigFile<ConfigData>;

void Update(const Config& config)
{
Graphics::SetBackground(config.setting.c);
Print << config.setting.a;
Print << config.setting.b;
Print << config.setting.c;
Print << config.setting.d;
}

void Main()
{
// 設定記述ファイルのファイルパス
const FilePath configPath = U"config.toml";
Config config;

try
{
// Config ファイルを読み込み
config.reload(configPath);
}
catch (const ConfigError& err)
{
// Config の読み込みエラーがあると ConfigError 例外
err.show();
return;
}

Update(config);

while (System::Update())
{
// Config ファイルが更新されたら
if (config.hasChanged())
{
try
{
ClearPrint();

// Config ファイルを再読み込み
config.reload(configPath);
}
catch (const ConfigError& err)
{
err.print();
}

Update(config);
}
}
}



サンプルプログラム

少し規模が大きいサンプルです。

記述ファイルを変更すると、ウィンドウタイトルやサイズ、背景色、パラメータが変わります。


config.toml

[Window]

title = "My Game"
size = "(1280, 720)"

[UI]
background = "(0.8, 0.9, 1.0)"

[Parameter]
a = 123
b = 3.1415
c = "text"
d = "(0.1, 0.2, 0.3)"
e = [10, 20, 30, 40, 50]
f = ["AAA", "BBB", "CCC"]



Main.cpp

# include <Siv3D.hpp> // OpenSiv3D v0.3.1

# include "ConfigFile.hpp"

struct ConfigData
{
void reload(const TOMLReader& toml)
{
S3DCFG_RELOAD_SECTION(window);
S3DCFG_RELOAD_SECTION(ui);
S3DCFG_RELOAD_SECTION(parameters);
}

struct Window
{
String title;
Size size = s3d::Window::DefaultClientSize;

void reload(const TOMLReader& toml)
{
S3DCFG_LOAD_SECTION(U"Window");
S3DCFG_LOAD_VALUE(title);
S3DCFG_LOAD_VALUE(size);
}
} window;

struct UI
{
ColorF background = Palette::DefaultBackground;

void reload(const TOMLReader& toml)
{
S3DCFG_LOAD_SECTION(U"UI");
S3DCFG_LOAD_VALUE(background);
}
} ui;

struct Parameter
{
int32 a;
double b;
String c;
Vec3 d;
Array<int32> e;
Array<String> f;

void reload(const TOMLReader& toml)
{
S3DCFG_LOAD_SECTION(U"Parameter");
S3DCFG_LOAD_VALUE(a);
S3DCFG_LOAD_VALUE(b);
S3DCFG_LOAD_VALUE(c);
S3DCFG_LOAD_VALUE(d);
S3DCFG_LOAD_ARRAY(e);
S3DCFG_LOAD_ARRAY(f);
}
} parameters;
};

using Config = ConfigFile<ConfigData>;

void Update(const Config& config)
{
Window::SetTitle(config.window.title);
Window::Resize(config.window.size);
Graphics::SetBackground(config.ui.background);
Print << config.parameters.a;
Print << config.parameters.b;
Print << config.parameters.c;
Print << config.parameters.d;
Print << config.parameters.e;
Print << config.parameters.f;
}

void Main()
{
const FilePath configPath = U"config.toml";
Config config;

try
{
config.reload(configPath);
}
catch (const ConfigError& err)
{
err.show();
return;
}

Update(config);

while (System::Update())
{
if (config.hasChanged())
{
try
{
ClearPrint();
config.reload(configPath);
}
catch (const ConfigError& err)
{
err.print();
}

Update(config);
}
}
}



現時点での制約

「配列の配列」や「テーブルの配列」などを 1 行でロードできるマクロは未実装なので、各構造体の reload 内で、TOMLReader の API を使って自前でロードを実装する必要があります。


まとめ

C++/OpenSiv3D プログラミングに Config クラスを導入して、高速な開発を実現しましょう。