ゲームを作る上であると便利なもの その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の設置と操作を一括して扱いましょう的なお話でした。