結果だけでなく過程も見てください

たい焼きさんの日々の奮闘を綴る日記です。

ほげほげローダ


自分は普段から自作ゲームを作っているのですが
そこでよく登場するのが、ローダ。マップローダ、イベントローダ等。
ディスクやメモリからデータを読み、プログラムで使用するメモリ領域に
データをセットする役割を持ちます。


このローダの実装方法ですが、これ!というやり方がわからなくて
やきもきしてます。


マップデータ(データ構造は以下)を扱うときを例にあげると、
シンプルなケースでは、マップの名前、サイズ、マップチップのリスト
なんかを持つことになると思います。


マップデータ
マップ名(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つ型を作るあたり・・・)
もうちょっとスマートがやり方ってないでしょうかねぇ?^^;


市販のゲーム本はこういうところをもうちょっと取り上げてほしいですね。
ネットもそうなんですが、ゲームを作る上での末端の技術─当たり判定の
基本的なロジックとかタイマとか─の記事はけっこう見つかるのに、実際
それがゲームフローのどこでこういう風に使われるといった記事は
ほとんど見かけないです。なんででしょう。。みんなそこを悩むと思うの
ですが。