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

35:列挙関数を作ってみよう

 ゲームの規模が大きくなるにつれて、タスクリストには多種多様なデータが格納されるようになります。必要なデータを探し出す処理を逐一記述するのも面倒なので、必要なオブジェクトのみを抜き出す機能を追加することにしましょう。

 列挙をするためのクラスを「GameObject.h」内に作成します。このクラスには、検索用キーワードを格納する変数、検索位置(イテレータ)、検索を実行する関数を用意しています。イテレータはCGameObjectのプライベートメンバであるタスクリストから取得する必要があるので、CGameObjectをフレンドクラスに指定しています。

enum.h
class CEnumeration
{
protected:
	list<ListData>::iterator ipos, iend;
public:
	virtual CGameObject* GetNext() = 0;
};

class CKeyEnum : public CEnumeration
{
private:
	char keyword[NAME_SIZE];
public:
	friend CGameObject;
	virtual CGameObject* GetNext();
};

class CPrioEnum : public CEnumeration
{
private:
	int start, end;
public:
	friend CGameObject;
	CGameObject* GetNext();
};

 CGameObjectクラス内に、CEnumerationクラスに検索情報を格納するための関数を用意します。プログラムの構成によっては引数のkeywordがメモリから破棄される場合もあるので、ポインタのコピーではなく、strcpy_sによる配列のコピーを行っています。

createenum.cpp
void CGameObject::CreateEnumeration(const char *keyword, CKeyEnum *cenum)
{
	cenum->ipos = objectlist.begin();
	cenum->iend = objectlist.end();

	strcpy_s(cenum->keyword, NAME_SIZE, keyword);
}

void CGameObject::CreateEnumeration(int p_begin, int p_end, CPrioEnum *cenum)
{
	cenum->ipos = objectlist.begin();
	cenum->iend = objectlist.end();

	cenum->start = p_begin;
	cenum->end = p_end;
}

 列挙対象となるCGameObjectを抜き出す関数CEnumeration::GetNext()を作ります。いろいろと一致条件を変えてみるのもいいかもしれません。

getnext.cpp
CGameObject* CKeyEnum::GetNext()
{
	// キーワードとオブジェクト名の先頭が一致すれば
	// そのオブジェクトのポインタを返す
	char *p1, *p2;
	while(ipos != iend){
		p1 = keyword;
		p2 = (*ipos).name;

		while(*p1 != '\0'){
			if(*p1 != *p2) break;
			p1++;
			p2++;
		}
		
		if(*p1 == '\0'){
			CGameObject *g = ipos->gameobj;
			ipos++;
			return g;
		}

		ipos++;
	}

	// 見つからなければNULLを返す
	return NULL;
}

CGameObject* CPrioEnum::GetNext()
{
	// 指定範囲内の優先度値をもつオブジェクトがあれば
	// そのオブジェクトのポインタを返す
	while(ipos != iend){
		if(ipos->priority > this->end){
			ipos = iend;
			return NULL;
		}else if(ipos->priority >= this->start){
			CGameObject *g = ipos->gameobj;
			ipos++;
			return g;
		}

		// 見つからなければNULLを返す
		ipos++;
	}

	return NULL;
}


転職、求人情報ならリクルートの転職サイト【リクナビNEXT】

 こちらが参考用のテストクラスです。CEnumTestStart::Init()では、わざと「敵」と「味方」を交互にリストに追加しています。CEnumTestStart::Exec()にあるdynamic_castでは<>内のクラスへの変換ができないときはNULLを返してくれるので、複数の種類のクラスが返される可能性がある場合はこれを使うとよいでしょう。

enumtest.h
class CEnumTestStart : public CGameObject
{
protected:
	void Init();
	void Exec();
};

class CStringData : public CGameObject
{
public:
	TCHAR str[10];
	CStringData(LPCTSTR s){
		_tcscpy_s(str, 10, s);
	}
};

enumtest.cpp
#include "EnumTest.h"

void CEnumTestStart::Init()
{
	char name[NAME_SIZE];
	for(int i = 0; i < 20; i++){
		sprintf_s(name, NAME_SIZE, "teki_%d", i);
		AppendObject(new CStringData(_T("敵です")), 1000 + i * 10, name, true);

		i++;

		sprintf_s(name, NAME_SIZE, "mikata_%d", i);
		AppendObject(new CStringData(_T("味方です")), 1000 + i * 10, name, true);
	}

	RemoveObject(this);
}

void CEnumTestStart::Exec()
{
	CKeyEnum e;
	CreateEnumeration("mikata", &e);

	CGameObject *g;
	CStringData *s;
	while(g = e.GetNext()){
		s = dynamic_cast<CStringData*>(g);
		if(s) DXTRACE_MSG(s->str);
	}

	PostQuitMessage(NULL);
}

 このテストクラスを実行すると、Visual C++の出力ウィンドウに「味方」のみが表示される、つまり、味方のデータクラスのみが列挙された後で、プログラムが終了します。