604.オブジェクトビルダーを使ったデータの読み込み(XML版)
XMLによるゲームオブジェクトビルダー
前項では
CSVによるゲームオブジェクトビルダーを紹介しました。今項ではその
XML版を紹介します。
FullSample604を実行してみてください。実行画面は前項と同じになります。
まず、XMLですが
mediaディレクトリに
GameStage1.xmlというXMLファイルがあります。その中身は以下です。
<?xml version="1.0" encoding="utf-8" ?>
<GameStage>
<GameObject Type="TilingPlate" Scale="40,40,1"
Rot="XM_PIDIV2,0,0" Pos="0,0,0" UPic="1.0" VPic="1.0" />
<GameObject Type="TilingFixedBox" Scale="40,1,1"
Rot="0,0,0" Pos="0,0.5,19.5" UPic="1.0" VPic="1.0" />
<GameObject Type="TilingFixedBox" Scale="40,1,1"
Rot="0,0,0" Pos="0,0.5,-19.5" UPic="1.0" VPic="1.0" />
<GameObject Type="TilingFixedBox" Scale="40,1,1"
Rot="0,XM_PIDIV2,0" Pos="19.5,0.5,0" UPic="1.0" VPic="1.0" />
<GameObject Type="TilingFixedBox" Scale="40,1,1"
Rot="0,XM_PIDIV2,0" Pos="-19.5,0.5,0" UPic="1.0" VPic="1.0" />
<GameObject Type="Enemy1" Pos="-10,0.25,0" />
<GameObject Type="Enemy1" Pos="10,0.25,0" />
<GameObject Type="Enemy1" Pos="0,0.25,-10" />
<GameObject Type="Enemy1" Pos="0,0.25,10" />
<GameObject Type="Enemy2" Pos="-15,0.25,0" />
<GameObject Type="Enemy2" Pos="15,0.25,0" />
<GameObject Type="Enemy2" Pos="0,0.25,-15" />
<GameObject Type="Enemy2" Pos="0,0.25,15" />
<GameObject Type="Enemy3" Pos="10,0.25,10" />
</GameStage>
このドキュメントの表示スペースの関係で、何か所か改行してますが、実際のXMLファイルは改行してません。
XMLを見てみると
<GameObject>タグが複数作成されています。これが
1つのゲームオブジェクトを表す
ノードになります。
そして、
<GameObject>タグにある
Type="TilingPlate"という記述が
クラス名を表します。タグの囲みの中にある
hoge="huga"のような記述を
アトリビュートといいます。
さて、このようになっている
XMLファイルから、ステージにオブジェクトを配置するわけですが、前項のCSV版と同様の機能がある、
ゲームオブジェクトXMLビルダーを使用します。使用方法は、前項とあまり変わりません。
GameStage::OnCreate()関数に記述があります。
void GameStage::OnCreate() {
try {
//ビューとライトの作成
CreateViewLight();
//オブジェクトのグループを作成する
auto Group = CreateSharedObjectGroup(L"EnemyGroup");
//ゲームオブジェクトビルダー
GameObjecttXMLBuilder Builder;
//ゲームオブジェクトの登録
Builder.Register<TilingPlate>(L"TilingPlate");
Builder.Register<TilingFixedBox>(L"TilingFixedBox");
Builder.Register<Enemy1>(L"Enemy1");
Builder.Register<Enemy2>(L"Enemy2");
Builder.Register<Enemy3>(L"Enemy3");
wstring DataDir;
App::GetApp()->GetDataDirectory(DataDir);
//XMLからゲームオブジェクトの構築
wstring XMLStr = DataDir + L"GameStage";
XMLStr += Util::IntToWStr(m_StageNum);
XMLStr += L".xml";
Builder.Build(GetThis<Stage>(), XMLStr, L"GameStage/GameObject");
//プレーヤーの作成
CreatePlayer();
}
catch (...) {
throw;
}
}
赤くなっているのが
ビルダー関連です。XML用には
GameObjecttXMLBuilderクラスを使用します。
インスタンスを定義したら、前項のように
Builder.Register<TilingPlate>(L"TilingPlate");
と
クラス名と
クラス名の文字列を対で登録します。
登録が済んだら
XMLファイル名を作成し、
ゲームオブジェクトへのパスと一緒に
Build()関数に渡します。
Builder.Build(GetThis<Stage>(), XMLStr, L"GameStage/GameObject");
こんな感じです。
ゲームオブジェクトへのパスは
XMLのパスです。XMLはノードが入れ子状態になっているので、
ディレクトリのパスのように考えられます。
L"GameStage/GameObject"というのは
GameStage配下のGameObjectという意味です。
XMLパスが
ディレクトリのパスと違うところは
同じノード名を複数作れるところにあります。つまり
L"GameStage/GameObject"に該当するノードは
複数あることになります。
GameObjecttXMLBuilderクラスは指定されたパスを抽出して該当するノードをすべて読み取ります。そして、その
Typeアトリビュートに該当するクラスを構築して、コンストラクタにその
ノードを渡します。
ですから、
CSVのビルダーは
1行のCSVを渡すのに対して、
XMLのビルダーは
1つのノードを渡します。XMLのノードは
子ノードを作成することもできますから、当然、
CSVより複雑なデータを渡すことも可能です。しかし、このサンプルでは前項との比較が目的ですので、XMLの構造も単純になってます。
XMLのノードを受け取るコンストラクタ
では
XMLのノードを受け取るコンストラクタを実際に見てみましょう。例として、
TilingPlateクラスのコンストラクタを紹介します。
//構築と破棄
TilingPlate::TilingPlate(const shared_ptr<Stage>& StagePtr, IXMLDOMNodePtr pNode):
GameObject(StagePtr)
{
try {
auto ScaleStr = XmlDocReader::GetAttribute(pNode, L"Scale");
auto RotStr = XmlDocReader::GetAttribute(pNode, L"Rot");
auto PosStr = XmlDocReader::GetAttribute(pNode, L"Pos");
auto UPicStr = XmlDocReader::GetAttribute(pNode, L"UPic");
auto VPicStr = XmlDocReader::GetAttribute(pNode, L"VPic");
//トークン(カラム)の配列
vector<wstring> Tokens;
//トークン(カラム)単位で文字列を抽出(L','をデリミタとして区分け)
Tokens.clear();
Util::WStrToTokenVector(Tokens, ScaleStr, L',');
//各トークン(カラム)をスケール、回転、位置に読み込む
m_Scale = Vec3(
(float)_wtof(Tokens[0].c_str()),
(float)_wtof(Tokens[1].c_str()),
(float)_wtof(Tokens[2].c_str())
);
Tokens.clear();
Util::WStrToTokenVector(Tokens, RotStr, L',');
Vec3 Rot;
//回転は「XM_PIDIV2」の文字列になっている場合がある
Rot.x = (Tokens[0] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[0].c_str());
Rot.y = (Tokens[1] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[1].c_str());
Rot.z = (Tokens[2] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[2].c_str());
//プレートの回転の引数はクオータニオンになっているので変換
m_Qt.rotationRollPitchYawFromVector(Rot);
Tokens.clear();
Util::WStrToTokenVector(Tokens, PosStr, L',');
m_Position = Vec3(
(float)_wtof(Tokens[0].c_str()),
(float)_wtof(Tokens[1].c_str()),
(float)_wtof(Tokens[2].c_str())
);
m_UPic = (float)_wtof(UPicStr.c_str());
m_VPic = (float)_wtof(VPicStr.c_str());
}
catch (...) {
throw;
}
}
まず、渡された
ノードを使って、
L"Scale"や
L"Rot"と指定がある
アトリビュートを取得します。
auto ScaleStr = XmlDocReader::GetAttribute(pNode, L"Scale");
というのは、XML上は、
Scale="40,40,1"のようになっているアトリビュートです。この
40,40,1という文字列が
wstring型に変換されて取得できます。(ScaleStrはwstring型です)
このように文字列を取得したら、後は
トークンに分割して各値をメンバ変数
m_Scaleなどに代入してます。
トークン分割して、
分割された文字列からfloat型への変換までの過程は、これまでのものと変わりません。
このように
XMLからゲームオブジェクトを作成することが
GameObjecttXMLBuilderクラスを使うことで比較的簡単に行うことができます。前項の
CSV版と比べてみましょう。
さて、では、
データの読み込みをどのように設計したらいいのでしょうか?
これは
ゲームによるとしか言いようがありません。例えばステージ上に
セルをまたぐオブジェクトがないのであれば
FullSample601のようなCSVのセルマップや
FullSample602の
XMLによるセルマップ読み込みがいいでしょう。しかし、オブジェクトが
体力や知力や攻撃力などの複雑なパラメータを持っているのであれば、CSVだと、1行がとんでもなく長いものになってしまいます。そういう場合は構造的に扱える
XML形式がいいと思います。
また、ゲーム中に
パラメータの保存が行われる場合も、
XML形式で保存する(この場合は
XmlDocクラスを使って保存する)のが安全でしょう。CSVも保存可能ですが、単純なCSVであれば保存もいいですが、複雑なデータをCSVで保存するのは不具合のもとになります。CSVはどうしても
1列ずれただけで大変なことになるデータ形式ですから。