16.データの読み込み
1603.オブジェクトビルダーを使ったデータの読み込み(CSV版)
このサンプルは
FullSample603というディレクトリに含まれます。
BaseCrossDx11.slnというソリューションを開くと
Dx11版が起動します。
このサンプルは現在Dx12版はありません。
実行結果は1601と変わりませんので、そちらを確認ください。
リフレクションについて
一般的なプログラミング言語の機能に
リフレクションというのがあります。
例えば
インスタンス構築で
class TestClass{
public:
TestClass(){}
//...
};
int main(){
//以下のようなコードは書けません
auto Ptr = new GetType("TestClass");
//...
return 0;
}
のように記述できたら便利だとは思いませんか?
どういうときに利用できるかというと、
文字列を使って
クラスの構築ができることです。こういう機能を
リフレクションといいます。
文字列でクラスのインスタンスを構築できれば、例えば
CSVやXMLにクラス名を記述しておくことで、そのクラスを構築することが可能になります。
しかし残念ながらネイティブなC++にはこういう機能はありません(C++/CLIではできます)。ですので、ネイティブC++の場合は
リフレクションを自作しなければいけません。
BaseCrossには
擬似的なリフレクションを使った
ゲームオブジェクトビルダーが実装されてます。この機能を使うと、CSVやXMLにクラスの識別名を記述することで、そのクラスのコンストラクタに、構築に必要なパラメータを渡すことが可能になります。
FullSample603と
FullSample604は
ゲームオブジェクトビルダーを利用したサンプルです。
今項で紹介する
FullSample603は
リフレクションを使って、CSVからゲームオブジェクトを構築するサンプルとなります。
ビルドして実行すると前述したように
FullSample602と同じステージになります。しかし
GameStage.cppを見ていただくと、構築用のコードがほとんど記述されてないのがわかると思います。構築用のデータは、ビューやプレイヤーなど一部のクラスを除いて
CSVファイルから読み込んでいます。
CSVの準備
mediaディレクトリの中には
GameStage1.csvがあります。以下がその内容です。
TilingFixedBox,50,1,50,0,0,0,0,-0.5,0,1,1,SKY_TX
TilingFixedBox,40,1,1,0,0,0,0,0.5,19.5,1,1,WALL_TX
TilingFixedBox,40,1,1,0,0,0,0,0.5,-19.5,1,1,WALL_TX
TilingFixedBox,40,1,1,0,XM_PIDIV2,0,19.5,0.5,0,1,1,WALL_TX
TilingFixedBox,40,1,1,0,XM_PIDIV2,0,-19.5,0.5,0,1,1,WALL_TX
SeekObject,-10,0.25,0,,,,,,,,
SeekObject,10,0.25,0,,,,,,,,
SeekObject,0,0.25,-10,,,,,,,,
SeekObject,0,0.25,10,,,,,,,,
MoveBox,-15,0.5,0,,,,,,,,
MoveBox,15,0.5,0,,,,,,,,
MoveBox,0,0.5,-15,,,,,,,,
MoveBox,0,0.5,15,,,,,,,,
それぞれ
0列目(一番左)にクラス名が記述されているのがわかります。
オブジェクトビルダーは、このクラス名に対応するクラスを作成し、そのコンストラクタに該当の行を渡します。
では、実装を見てみましょう。以下は
GameStage::OnCreate()関数です。
void GameStage::OnCreate() {
try {
//ビューとライトの作成
CreateViewLight();
//オブジェクトのグループを作成する
auto Group = CreateSharedObjectGroup(L"SeekGroup");
//ゲームオブジェクトビルダー
GameObjecttCSVBuilder Builder;
//ゲームオブジェクトの登録
Builder.Register<TilingFixedBox>(L"TilingFixedBox");
Builder.Register<SeekObject>(L"SeekObject");
Builder.Register<MoveBox>(L"MoveBox");
wstring DataDir;
App::GetApp()->GetDataDirectory(DataDir);
//CSVからゲームオブジェクトの構築
wstring CSVStr = DataDir + L"GameStage";
auto shScene = App::GetApp()->GetScene<Scene>();
CSVStr += Util::IntToWStr(shScene->GetStageNum());
CSVStr += L".csv";
Builder.Build(GetThis<Stage>(), CSVStr);
//プレーヤーの作成
CreatePlayer();
}
catch (...) {
throw;
}
}
オブジェクトビルダーにかかわる操作は赤くなっているところです。
まず、
GameObjecttCSVBuilder Builder;
と
GameObjecttCSVBuilderのインスタンスを定義します。
これは
CSV読み込み用のビルダーです。次項には
XML読み込み用のビルダーを紹介します。
ビルダーの操作としては、まず、
クラス名と
クラス名の文字列の対を登録します。
Builder.Register<TilingFixedBox>(L"TilingFixedBox");
のような感じです。これは
TilingFixedBox型を
L"TilingFixedBox"文字列に対応させています。文字列はCSVに記述される文字列です。通常これは型名と同じ名前にします。
冒頭に述べた
リフレクションを使っているのが、
GameObjecttCSVBuilderとその関連クラスです。興味がある人は読んでみましょう。
クラス名の登録が終わったら、CSVファイル名を指定して
Builder.Build(GetThis<Stage>(), CSVStr);
と呼び出します。すると、各ゲームオブジェクトがCSVの内容に従って構築されます。
ここで
CSVファイル名を作成するのに
shScene->GetStageNum()とシーンに用意したステージナンバーを使っています。この変数は
シーンクラスにある変数です。
複数のゲームステージを作る場合の参考になると思います。(ここではCSVファイル名に数字を付けて使ってます。
さて
FullSample601や
FullSample602にあった
CSVから個別データの取得はどこに行ってしまったのでしょうか?
実は、各ゲームオブジェクトクラスの
コンストラクタに記述します。
GameObjecttCSVBuilderクラスは、CSVを読み込んで、その各行の先頭のカラムにある文字列から、その文字列に対応するクラスを構築します。その時、そのCSVを1行だけコンストラクタに渡します。
ですから、各ゲームオブジェクトは、その
1行の文字列を受け取るコンストラクタを記述する必要があります。以下は
TilingFixedBoxのコンストラクタです。
Character.cppにあります。
TilingFixedBox::TilingFixedBox(const shared_ptr<Stage>& StagePtr, const wstring& Line) :
GameObject(StagePtr)
{
try {
//トークン(カラム)の配列
vector<wstring> Tokens;
Util::WStrToTokenVector(Tokens, Line, L',');
//各トークン(カラム)をスケール、回転、位置に読み込む
m_Scale = Vec3(
(float)_wtof(Tokens[1].c_str()),
(float)_wtof(Tokens[2].c_str()),
(float)_wtof(Tokens[3].c_str())
);
//回転はXM_PIDIV2の文字列になっている場合がある
m_Rotation.x = (Tokens[4] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[4].c_str());
m_Rotation.y = (Tokens[5] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[5].c_str());
m_Rotation.z = (Tokens[6] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[6].c_str());
m_Position = Vec3(
(float)_wtof(Tokens[7].c_str()),
(float)_wtof(Tokens[8].c_str()),
(float)_wtof(Tokens[9].c_str())
);
m_UPic = (float)_wtof(Tokens[10].c_str());
m_VPic = (float)_wtof(Tokens[11].c_str());
m_Texname = Tokens[12];
}
catch (...) {
throw;
}
}
赤くなっている
const wstring& Lineが
GameObjecttCSVBuilderから渡される
1行分のCSVです。ですから、コンストラクタではその文字列をトークンに分割して、必要なトークンをメンバ変数(m_Scaleやm_Position)に代入してます。
この方式の利点は、
クラスごとの引数を個別に変えられることです。渡される引数は
const wstring& Lineですが、その内容は自由に設定できます。例えば
TilingFixedBoxクラスは
スケーリング、回転、位置、テクスチャなどの情報が入ってますが、
SeekObjectクラスなどは
位置(つまりPosition)しか入ってません。
つまり
行ごとにCSVの内容を変えて記述することが可能です。
TilingPlateクラスに渡されるCSV行は、左から2番目のカラム(1番目はクラス名が入る)は
Scaleですが、
Enemy1クラスに渡されるのは
Positionです。
このように、各ゲームオブジェクトクラスのコンストラクタでCSV読み込みが完結するので、クラス単位で独立したコーディングが可能になります。