クラスまみれのゲームプログラミング入門

13:タスク処理を実装する(3)

 それでは、CGameObjectクラスを管理するためのリストをプログラムに組み込むことにしましょう。リストにCGameObject以外の情報もあると管理がさらに簡単になります。今回は以下の情報が格納されたデータをリストに追加していくことにします。

  • CGameObjectのポインタ
  • 実行順位(キャラクターの重ね順などに柔軟に対応できる)
  • オブジェクトの名前(文字列でオブジェクトを検索できる)
  • リストから削除された場合、CGameObject*をdeleteするかどうか
    newで作成したオブジェクトを自動で削除できる)

GameObject1.h
#pragma once

/* 省略 */

class CGameObject;			// あらかじめデータを宣言

// リストで管理する情報を構造体で宣言
#define NAME_SIZE 32
struct ListData
{
	int priority;			// 実行順位
	char name[NAME_SIZE];	// 管理名称
	CGameObject* gameobj;	// オブジェクトのポインタ
	bool autodelete;		// リスト削除と同時にオブジェクトも削除
};

class CGameObject
{
private:
	/* 省略 */

	// 格納するリストの宣言
	static list<ListData> objectlist;

	/* 省略 */
};

 リストにデータを追加するための関数を作成します。同じ名称の関数が2つ用意されていますが、引数のデータが異なっているのであれば、コンパイラが状況に合わせた関数を自動で参照してくれます。これを関数の多重定義(オーバーロード)といいます。また、外部のstatic関数からもアクセスできるように、これらの関数はstatic属性にしています。

addobj.h
public:
	static void AppendObject(
		ListData &listdata		// 構造体で直接渡すときの参照
	);
	static void AppendObject(
		CGameObject *object,	// ゲームオブジェクトのポインタ
		int priority,			// 実行順位(値が少ないほど優先される)
		bool autodelete			// オブジェクトの自動削除
	);
	static void AppendObject(
		CGameObject *object,	// ゲームオブジェクトのポインタ
		int priority,			// 実行順位
		LPCSTR name,			// オブジェクトの名称
		bool autodelete			// オブジェクトの自動削除
	);

addobj.cpp
void CGameObject::AppendObject(ListData &listdata)
{
	if(listdata.gameobj == NULL) return;

	// 初期化関数を実行
	listdata.gameobj->Init();

	// リストの先頭に追加
	if(objectlist.size() == 0){
		objectlist.push_front(listdata);
		return;
	}

	// オブジェクトの順位に従い挿入
	list<ListData>::iterator i;
	for(i = objectlist.begin(); i != objectlist.end(); i++)
	{
		if((*i).priority > listdata.priority){
			objectlist.insert(i, listdata);
			return;
		}
	}

	// 間に入れるところがなければ、リストの最後に追加
	objectlist.push_back(listdata);
}

void CGameObject::AppendObject(CGameObject *object,
	int priority, bool autodelete)
{
	// 名前を未記入扱いにして転送
	AppendObject(object, priority, NULL, autodelete);
}

void CGameObject::AppendObject(CGameObject *object,
	int priority, LPCSTR name, bool autodelete)
{
	ListData ld;
	ld.priority = priority;
	if(name != NULL){
		strcpy_s(ld.name, NAME_SIZE, name);
	}else{
		ZeroMemory(ld.name, NAME_SIZE * sizeof(char));
	}
	ld.gameobj = object;
	ld.autodelete = autodelete;

	AppendObject(ld);
}

 41行目に見慣れない関数「strcpy_s」が登場しています。これは文字列複製関数「strcpy」を拡張したもので、コピーできる上限をあらかじめ設定しておくことで、メモリの破壊を未然に防ぐ機能が追加されています。現時点でこの関数はマイクロソフト社の開発環境しか対応していませんが、ISOでも策定段階にはいっているので、近い将来は様々なCコンパイラが対応することになるでしょう。


低価格PC・自作パーツ充実!ツクモネットショップ

 続いて、オブジェクトをリストから削除するための関数を追加します。このコードでは、削除したいゲームオブジェクトのポインタがリスト内に存在した場合は、その箇所をリストから削除し、そうでない場合は、リスト内の探索を続けるようにしています。

remove0.cpp
void CGameObject::RemoveObject(CGameObject *target)
{
	list&lt;ListData&gt;::iterator i = objectlist.begin();
	while(i != objectlist.end())
	{
		if((*i).gameobj == target){
			objectlist.erase(i);
			return;
		}else{
			i++;
		}
	}
}

 しかしながら、ここで少しばかり問題が発生します。ゲームタスク処理中にリストからデータを除去することもあるかと思うのですが、この状態でループ処理中にリストからデータを抜き取ってしまうと、イテレータが場所を参照できなくなるおそれがあるからです。

 そこで、タスク処理に利用しているイテレータをクラス内で共有し、削除するイテレータと、タスク処理を行っているイテレータが一致するときは、参照を失わないようにあらかじめRemoveObject内でタスク処理側のイテレータをひとつ後に進めておきます。

remove.h
class CGameObject
{
private:
	/* 省略 */

	// タスク処理時における仮イテレータ
	static bool it_moved;
	static list<ListData>::iterator it_task;

public:
	/* 省略 */
	static void RemoveObject(CGameObject *target);
}

 最後にタスクをまとめて処理するための関数を作成して、オブジェクト管理ルーチンは完成です。

alltask.h
/* 省略 */

class CGameObject
{
private:
	/* 省略 */

	BOOL Initialize(HWND hWnd, HINSTANCE hInstance);
	void Uninitialize();
	void DoAllTasks();

	/* 省略 */

public:
	/* 省略 */

protected:
	/* 省略 */
};

alltask.cpp
void CGameObject::DoAllTasks()
{
	it_task = objectlist.begin();

	it_moved = false;
	while(it_task != objectlist.end()){
		it_task->gameobj->Exec();
		if(it_moved == false){
			// 次に移動
			it_task++;
		}else{
			// フラグをリセット
			it_moved = false;
		}
	}
}

 今回の講座までに作成したCGameObjectのプログラムサンプルをアーカイブ形式で用意しています。ダウンロードはこちらから。なお、このアーカイブファイルには優先度を指定してのオブジェクト削除ルーチンも組み込まれているので参考にしてください。WinMain関数に埋め込む作業は次回にて。