ゲームを作る上であると便利なもの その2 - obj設置基本クラス -

というわけで、第二回。

今回はobj設置なお話。

 せっかくobjを定義できても、

設置する度に領域確保(newとかcalloc()とか)したり、

解放する為に確保場所のポインタを毎回保持する

…となると、非常に面倒、というかとても危なっかしくて使えない。

それだったら、objの生成を制御するクラスがあってもいいんじゃないかと。

typedef struct struct_SETTER_PRIMARY
{
protected:
	PRIMARY **obj;   // 基本objのポインタの配列
	                 // (…の確保用のポインタ)
	int obj_num;     // obj数

public:
	/*=============
	コンストラクタ
	=============*/
	struct_SETTER_PRIMARY()
	{
		obj = NULL;
		obj_num = 0;
	};
	/*=============
	デストラクタ
	=============*/
	~struct_SETTER_PRIMARY()
	{
		int i=0;

		for( ; i<obj_num ; i++ )
		{
			DELETER( obj[i] );
		}

		if( obj != NULL )
		{
			DELETE_ARRAY( obj );
		}
	};
// ↓に続く

 メンバに設置したいobjのクラスのポインタの配列を確保。

ポインタの配列にしてあるのは、ここで型をそれぞれのobj基底型にしておけば派生後の型でも確保できるようになるため。

ここでは配列で扱っているけれど、もちろんvector<>やlist<>等を使用してもOK。

(…というか、多分そちらの方が汎用性は高い。配列なのは単に個人的な趣味です。(要素数決め打ちで使いやすい・Cでも使える等々の理由から))

 で、コンストラクタとデストラクタ。

共に「ポインタ・obj数の初期化」「ポインタの解放」を行うのみ。

特に説明不要。


	// 初期化 (引数分だけobjを確保する)
	void Init( int object_num )
	{
		obj = new PRIMARY*[object_num];
		for( int i=0 ; i<object_num ; i++ )
		{
			obj[i] = NULL;
		}

		obj_num = object_num;
	};
	// 解放 (=デストラクタ)
	void Quit()
	{
		int i=0;

		for( ; i<obj_num ; i++ )
		{
			DELETER( obj[i] );
		}

		if( obj != NULL )
		{
			DELETE_ARRAY( obj );
		}
	};

	// obj消去
	void Clear()
	{
		int i=0;

		for( ; i<obj_num ; i++ )
		{
			DELETER( obj[i] );
		}
	};
// ↓に続く

 次に初期化関連。

 Init()は必須。(コンストラクタに組み込んでしまってもいいと思う。)

配列の要素数決定と初期化…なので、vector<>等のときはある意味不要?

 Clear()は配列の確保状態をそのまま確保しつつ、要素を全て解放。

戻り復活等、画面上のobjをリセットする機会があるゲームには必須。

 Quit()は…おまけ?(酷)

C++以外で使う用か。

	// 生成
	virtual PRIMARY *CreateObj( int param[] )
	{
		if( type == 0 &&
			param != NULL )
		{
			param = param;
		}
		return new PRIMARY();
	};
	// 確保
	virtual PRIMARY *Create( int param[] )
	{
		int i = -1;
		PRIMARY *return_obj = NULL;

                  // 空もしくは使用可能なobjを探す
		for( ; i<obj_num ; i++ )
		{
			if( obj[i] == NULL )
			{
				return_obj =
				obj[i] = CreateObj( param );
				break;
			}
			else if( ~(obj[i]->valid) & OBJECT_EXIST )
			{
				DELETER( obj[i] );

				return_obj =
				obj[i] = CreateObj( param );
				break;
			}
		}

		return return_obj;  // 「NULL」時はキャラオーバー
	};
}

 で、生成・設置関連。

至って単純に「生成用関数」と「全体を調べて使用可能な領域に確保してやる関数」。

(paramはあると何となく便利なので。)

生成済みだが使用済みの領域を使用する(下のif文の方)際は要素の解放を忘れないこと!

でないとメモリリークを起こしてしまう。

 この「Create()」で得られる返り値を使用してやれば、問題なくを操作することが出来ます。

…はてさて、何か抜けていると思いませんか?…はい、objの使用済みおよび解放処理です。

コレに関しては、用済みになったら、生存フラグを倒せばOK。

前述の箇所で用済みなら解放してくれるので、解放処理は不要。



 ここまでで、一応生成・解放はある程度自在に出来るようになったはず。

ただ、生成するだけでは使い勝手として微妙。

なので、ついでに座標を持たせるようにしてしまおう。

	// 設置
	virtual PRIMARY *Set( float pX, float pY, float pZ,
                               int param[] )
	{
		int i = 0;
		PRIMARY *return_obj = NULL;

		for( ; i<obj_num ; i++ )
		{
			if( obj[i] == NULL )
			{
				return_obj =
				obj[i] = CreateObj( param );
				obj[i]->x = pX;
				obj[i]->y = pY;
				obj[i]->z = pZ;
				break;
			}
			else if( ~(obj[i]->valid) & OBJECT_EXIST )
			{
				DELETER( obj[i] );

				return_obj =
				obj[i] = CreateObj( param );
				obj[i]->x = pX;
				obj[i]->y = pY;
				obj[i]->z = pZ;
				break;
			}
		}

		return return_obj;  // 「NULL」時はキャラオーバー
	};

 これで、生成と同時に座標をセットすることができるようになる。

ただ、まだobj単体で扱う必要があるので、まとめて制御する関数も追加してしまおう。

/*=====================================
処理まとめ関数
=====================================*/
void PRIMARY_SETTER::Process()
{
	int i=0;

	for( ; i<obj_num ; i++ )
	{
		if( obj[i] != NULL &&
			obj[i]->valid & VALID_EXIST )
		{
			obj[i]->Process();
		}
	}

	return;
}
/*=====================================
描画まとめ関数
=====================================*/
void PRIMARY_SETTER::Draw()
{
	int i=0;

	for( ; i<obj_num ; i++ )
	{
		if( obj[i] != NULL &&
			obj[i]->valid & VALID_EXIST )
		{
			obj[i]->Draw();
		}
	}

	return;
}

 これで、すべての生きているobjに関してループごとに処理・描画を行える。

継承先のメンバ等を扱いたい場合は、別途キャストが必要になるので注意。



 …と、ここまでやれば、外部から触る必要のあるものは、

「Init()」「Set()」「Process() ・ Draw()」のみになるため、それなりに扱いやすくなるはず。



 というわけで、今回はobjの設置と操作を一括して扱いましょう的なお話でした。