2013年09月07日

ゲーム製作におけるセーブ機構の実装


 ゲーム製作で躓く点はいくつかあると思いますが、セーブ・ロードの実装もその一つではないでしょうか?アクションならステージのクリア状態、キャラクターの状態等、セーブに必要なデータは限られるかと思いますが、拙作の様なSLGでは多岐にわたります。例えばマップのあらゆるデータ、乗客の動き、車両や関連事業の売上等々。アクションでどこでもセーブ機能を実装する事に近いかと思います。今回は、そういったセーブロードの実装を何とかしようと思います。

 で、具体的に何が問題かと言うとオブジェクト設計なプログラミングにおいて「どのようにカプセル化されたメンバ変数にアクセスするのか」と言うことに尽きると思います。また、オブジェクトそのものの数も変わるなら、ロード時にそれを再現してやらなければなりません。
 そこで、GoFのMementoパターンを使います。他のサイトでも扱われてますが、ここではもう少し実践的なMementoを作りましょう。MementoパターンはOriginator(ゲームクラス等)から、データを内包したMementoというオブジェクトを生成します。Mementoを取得した側(Caretaker)は中身を触る必要がないようにします。またOriginaterにそのMementoを返せば、元の状態に復元できるという仕組みです。
 このMementoは(1)一定の大きさのバッファを生成し、(2)そのバッファへ相互コピーが出来る、ようにすれば汎用的なものが作れそうです。
 但し、これは1オブジェクトに対してのみで、そのオブジェクトがプライベートなメンバとしてオブジェクトを持つような状態になっていれば、やはりそのメンバにアクセスできない事に違いはありません。

memento01.png

そこで、Memento自身にMementoを持たせるようにしましょう。Memento生成メソッドで下位のオブジェクトのMementoを取得すれば良いわけです。
memento02.png

こうすれば一番大元のオブジェクトからMementoを1回取得すれば全て済みます。 なにやらゴチャゴチャと子Mementoがついてるかもしれませんが、Caretakerはそれを気にする必要が有りません。

 あとは、大元のMementoでどうやってシリアライズするか、つまりHDDに保存しやすいように1つの連続したバッファにデータをコピーし尚且つ元の構造に復元するのか、という問題が有ります。
 これは各MementoがそのMemento自身のバッファのサイズ、及びそのMemento以下子Mementoを含めたサイズを保持すれば可能です。

バッファ構造のイメージ
memento03.png

ロード時におけるバッファ解析のイメージ
memento04.png

以下はそのシリアライズ・復元のコードです。


int CMementoTree::get_serialized(void *buffer)
{
	int tsize,size;
	int offset=0;
	int i;

	tsize = get_treesize();		//自身のバッファサイズ+子Mementoのサイズ+8byteを取得
	set_treesize(buff,tsize);	//然る領域に保存

	size = get_size_with_header();
	memcpy(buffer,buff,(size_t)size);
	offset += size;

	for(i=0;iget_serialized(&((char*)buffer)[offset]);
		offset += mements[i]->get_treesize();
	}

	return 0;
}

int CMementoTree::set_serialized(void *buffer)
{
	CMementoTree *m;
	int tsize,size;
	int offset=0;
	int i;

	size	= get_size(buffer)+MEMENTOTREE_HEADER_SIZE;
	tsize	= get_treesize(buffer);

	create_buffer(size);
	memcpy(buff,buffer,size);

	offset += size;
	while(offset<tsize)
	{
		m = new CMementoTree();
		m->set_serialized(&((char*)buffer)[offset]);
		offset += m->get_treesize();
		mements.push_back(m);
	}

	return 0;
}

※木構造になるのでクラス名をCMementoTreeとしました:-)

 オブジェクト毎に8byteずつ余分に大きくなりますが、HDDが大きくなった現在大した問題とはならないでしょう。
 これによってセーブデータのフォーマットに変更があってもオブジェクト単位でフォーマットのバージョンを保持しておけば柔軟且つ細かく対応が出来ます。またクラス単位でのテストが容易になります。

 ゲーム製作の一助になれば幸いです。


今回のソースコード及び使用例
mementotree.zip




posted by ぷーすけ at 13:43| Comment(0) | ゲーム製作・同人活動
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: