ホーム < ゲームつくろー! < デザインパターン習得編

Composite
  〜入れ子の入れ子の入れ子の入れ子の・・・


@ オブジェクトか、箱か?

 ゲームはパーツの集まりで出来ているといっても過言ではありません。しかし、そのパーツを良く見ると、さらに細かいパーツに分かれていることも良くあります。さらにさらに、その細かいパーツもまた細かく・・・と、再帰的に繰り返されている事が多分にあるのです。  

 例えば「シーン」という概念がゲームにはあります。ゲームはシーンの連続で、シーンの中には、さらに小さなシーンがあり・・・。こういう場合、シーンを管理するシーンマネージャが良く設定されたりします。

class SceneManager
{
protected:
   vector<Scene> m_SceneAry;

public:
   void AddSnene(Scene*);
   void RemoveScene(Scene*);
};

 さて、シーンマネージャはある場面のシーンを統括するとして、別のシーンに移る時はどうするか?別のシーンマネージャを作って入れ替えたりします。そうしていくうちにシーンマネージャは増えていきます。すると、管理しきれなくなるのでシーンマネージャのマネージャが出来たりして・・・。「あれ〜、同じシーンを管理しているのに変わりはないのに、マネージャクラスばかりが増えていく・・・」。

 これは、シーンという「オブジェクト」とそれをたくさん入れる「箱」を区別してしまったために起きてしまいました。シーンマネージャが管理しているのも大きなシーンの1つに変わりありません。それであれば、いっそシーンクラスに「オブジェクト兼箱になってもらう」のが一番すっきりします。

class Scene
{
private:
   vector<scene*> m_SceneAry;   // 子シーン配列

protected;
   Scene* m_pParent;       // 親シーン

public:
   virtual void AddScene(Scene*);                // 子シーンを追加
   virtual void RemoveScene(Scene*);          // 子シーンを取り除く
   virtual Scene* GetChild(int num){                       // 子シーンにアクセス
      if(m_SceneAry.size() < num)
         return NULL;
      return m_SceneAry[num];
   }

   virtual void Begin();   // シーン開始
};

 このSceneクラスを見ると、親シーンと子シーンの両方を保持し、子シーンは追加したり取り除いたり出来る仕組みになっています。この「オブジェクト兼箱」という形で再帰的にオブジェクトを取り扱うパターンをCmpositeパターンと言います。

 シーンの場合、親シーンから始まって、次々と展開していきますが、最終的には必ず最初のシーンに戻ることになります。それがたとえエンディングを終えたとしても、最初のシーンに戻ることは可能です。図示すると次のような樹状的な感じです。

これを見ると、例えばプロローグシーンはグッドシーン1とバッドシーン1を保持する「シーン」です。

 Compositeパターンでは「箱」も「オブジェクト」も同じクラスから派生させますが、双方は実は区別されています。オブジェクトに当たるクラスでは、Add、Remove、GetChild関数の実装は空にして、子オブジェクトが無い事を保障します。一方、箱に当たるクラスでは、上記関数をすべて実装して、箱に徹します。

class LeafScene : public Scene
{
public:
  virtual void AddScene(Scene*){};                // 子シーンを追加(空)
   virtual void RemoveScene(Scene*){};          // 子シーンを取り除く(空)
   virtual Scene* GetChild(int num){ return 0;}; // 子シーンにアクセス(空)
   virtual void Begin();                // シーン開始
};
class CompsitScene : public Scene
{
public:
   virtual void AddScene(Scene*){                // 子シーンを追加
      m_SceneAry.push_back(Scenen);
  }
   virtual void RemoveScene(Scene*){          // 子シーンを取り除く
      //* 取り除く実装 *//
   }
   virtual void Begin();                // シーン開始
};


 LeafSceneは「枝葉」のシーンで、Begin関数によってシーンが開始されるとします。その代わり、子シーンを追加したり取り除いたりする機能はありません。一方CompositeSceneクラスでは、Begin関数によってシーンが始まるわけではなく、例えば保持している子シーンの選択画面になるとか、親シーンの情報から特定の子シーンのBegin関数を呼び出したりします。

 Compsiteパターンの関係図は次のようになります。

 基本的にはComposite派生クラスのOperationはchildrenとして登録されているOperationをすべて実行します。ただ、ここはシーンの例でもあったように、ユーザの自由でかまわないように思います。シーンの例に置き換えたのがこちらです。


この他にも、絵の中に絵があって、その中にまた絵があるような入れ子状の絵など、樹状構造になっているつながりがあれば、だいたいCompositeパターンを適用できます。