自分は普段から自作ゲームを作っているのですが
そこでよく登場するのが、ローダ。マップローダ、イベントローダ等。
ディスクやメモリからデータを読み、プログラムで使用するメモリ領域に
データをセットする役割を持ちます。
このローダの実装方法ですが、これ!というやり方がわからなくて
やきもきしてます。
マップデータ(データ構造は以下)を扱うときを例にあげると、
シンプルなケースでは、マップの名前、サイズ、マップチップのリスト
なんかを持つことになると思います。
マップデータ
マップ名(string型)
マップサイズ(SIZE型)
マップチップ(CBitmap型のvectorやlist)
※CBitmapはマップチップを表すための自作クラス
また、このマップデータは一度読み込まれたらゲーム中は変更したくない
ものとします。変更したくないのですから、データを変更するようなインター
フェースは必要ないです。これを踏まえてクラスを実装すると、以下のように
なると思います。
class MapData { public: MapData(string strMapName, SIZE mapSize, list<CBitmap> mapChipList): m_strMapName(strMapName), m_mapSize(mapSize), m_mapChipList(mapChipList){} /// Resourceはディスク上のファイルやメモリ、 /// リソース(VC++の)等 HRESULT load( Resource res ); /// データを変更したくないので /// setのインターフェースは定義しない const string& getMapName() const { return m_strMapName; } const SIZE& getMapSize() const { return m_mapSize; } list<CBitmap> getMapChipList() const { return m_mapChipList; } private: string m_strMapName; SIZE m_mapSize; list<CBitmap> m_mapChipList; }; /// メンバ関数loadの実装は以下。 /// thisポインタを渡してローダにデータをセットしてもらう。 HRESULT MapData::load( Resource res ) { MapDataLoader loader; return loader.load( this, res ); } そうするとローダのイメージは以下のようなになるはずです。 class MapDataLoader { public: HRESULT load(MapData* pMapData,Resource res) { pMapData->m_strMapName = res.getName(); pMapData->mapSize = res.getSize(); while( res.hasNext() ) { CBitmap bitmap( res.getMapChipName() ); pMapData->m_mapChipList.push_back( bitmap ); } return S_OK; } };
値渡しを各所で使っていますが、これは単に表記上めんどくさいからで
実際にはポインタを使用します。なので、今回はコピーに関するコストは
考えなくてよいです。
上記コードは、loadメンバ関数を呼び出せば、渡したMapDataにデータが
入っている状態にしたいというものですが、MapDataLoaderはMapDataの
privateメンバにアクセスできないので、コンパイルエラーになります。
MapDataにsetのインターフェースを持たせるか、MapDataLoaderを
MapDataのfriendにすれば実現できますが、setのインターフェースは
上述した通り、外部から変更を許可したくないので実装したくないです。
また、friendは毎回ローダを作る度にfriendをぽんぽん増やすのかという
疑問があり、あんまり使いたくないです。(friendについてはそんなに深く
理解していないだけというところもありますけど。。friendは継承よりも強い
アクセス権を持っているので、安易に使いたくないのです)
結局のところ、自分はいつもローダとロードされるオブジェクトの間に
情報受け渡し用の構造体を用意してローダは読み込んだデータをその
構造体にいれて返し、それをうけとったロードされる側のオブジェクトは
その構造体からデータを取り出して自分のメンバにセットするのです。
// 情報受け渡し用構造体 struct LoadingParameters { string mapname_; SIZE size_; list<CBitmap> mapChipList_; }; class MapDataLoader { public: LoadingParameters load(Resource res) { LoadingParameters param; param.mapname_ = res.getName(); param.size_ = res.getSize(); while( res.hasNext() ) { CBitmap bitmap( res.getMapChipName() ); mapChipList_.push_back( bitmap ); } return param; } }; HRESULT MapData::load( Resource res ) { MapDataLoader loader; LoadingParameters param = loader.load( res ); m_strMapName = param.mapname_; m_mapSize = param.size_; m_mapChipList = param.mapChipList_; }
いちおうこれでやりたいことはできているのですが、自分はこれが
なーんとなく無駄なことをしているように感じているのです(特に
わざわざ1つ型を作るあたり・・・)
もうちょっとスマートがやり方ってないでしょうかねぇ?^^;
市販のゲーム本はこういうところをもうちょっと取り上げてほしいですね。
ネットもそうなんですが、ゲームを作る上での末端の技術─当たり判定の
基本的なロジックとかタイマとか─の記事はけっこう見つかるのに、実際
それがゲームフローのどこでこういう風に使われるといった記事は
ほとんど見かけないです。なんででしょう。。みんなそこを悩むと思うの
ですが。