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

Prototype
  〜原型を用意して、後はコピーコピーコピー


@ 似たような面倒な物を沢山使う

 ゲームには似たようなものが沢山登場します。例えばSTGの弾。敵によっては大きい弾を発することもあります。RPGの地形。草原や砂漠など、小さなパーツである絵が組み合わさりマップを構成します。「弾」「パーツ」、こういう似ているけれどもちょっと違う性質を持つものはサブクラス化すると、うまく表現できます。しかし、例えば次のような状況をご覧下さい。

 RPGの地形を表すMapGraphicsクラスがあるとします。地形には山、草原、川、砂漠などさまざまあります。この時、「派生、派生・・・」とまともに考えてしまうと、地形の種類だけクラスを作る必要が出てきます。次のような感じです。

class MapGraphics
{
protected:
   BITMAP m_BMP;   // 使用する絵
   int m_width;          // 絵の幅
   int m_height;         // 絵の高さ
   int m_MapCode;     // 絵のコード

public:
   virtual void CreateMap()=0;
};


class MapMountain : public MapGraphics
{
public:
   virtual void CreateMap(){
      // 山の絵を読み込み幅や高さを格納する
   }
}

class MapRiver : public MapGraphics
{
public:
   virtual void CreateMap(){
      // 川の絵を読み込み幅や高さを格納する
   }
}


 これは、MapGraphicsという地形の絵を表す親クラスを用意し、CreateMap関数で地形の絵をクラスにセットします。MapMountainとMapRiverはその派生クラスで、その名の通り、山と川の地形を保持します。この派生を利用すれば、砂漠も草原も表せそうです。CreateMap関数内部では、対応する絵をファイル群から探し、絵の部分を抽出して格納し、その幅や高さ、マップIDなども登録します。この作業はなかなかに大変そうです。

 1枚のマップを作成するためには、パーツとなる絵が沢山必要です。しかし、毎回毎回CreateMap関数でパーツオブジェクトを生成するのはちょっとコストを要しそうです。それよりも、生成は1度だけで、次からはそれを「丸写し」したオブジェクトを生成した方が簡単ですよね。

 これがPrototypeパターンです。このパターンは、「生成は面倒だけどコピーは簡単」というオブジェクトが自身の分身を作る関数を持つパターンです。先ほどのMapGraphicsクラスは、プロトタイプパターンに従うと次のようになります。

class MapGraphics
{
protected:
   BITMAP m_BMP;   // 使用する絵
   int m_width;          // 絵の幅
   int m_height;         // 絵の高さ
   int m_MapCode;     // 絵のコード

public:
   virtual void CreateMap()=0;
   MapGraphics* Clone();    // 複製関数
};

  「これだけ?」と思ってしまうかもしれませんが、prototypeパターンは言ってしまえばこれだけです。でも、とても便利なのです。

 マップ用のパーツを扱うMapManagerクラスを作成したとします。こういう複数のオブジェクトを管理するマネージメントクラスはゲーム製作では必ず必要になります。このクラスはクライアントから「山のマップくれ」と命令されると、山のオブジェクトを作成して差し出します(Factory Methodパターンです)。

class MapManager
{
public:
   MapGraphics* GetPart(int map_id);   // マップのパーツを提供
};

 今、新しいパーツが出来たとします。MapManagerクラスはそのパーツの事は知りませんから、普通であればMapManagerクラスを派生させて対応します。しかし、パーツが加わる度にその管理クラスも派生させていては、なんとも効率が悪いですし、クラスの数が増えすぎて管理しきれなくなります。ところが、MapGraphicsがPrototypeパターンに従っていれば、管理クラス内のMapGraphicsクラスを「登録制」にすることで、これを劇的に改善できます。

class MapManager
{
protected:
   vector<MapGraphics*> m_MapPartVect;

public:
   MapGraphics* GetPart(int map_id){   // マップのパーツを提供
      return m_MapPartVect[map_id].Clone();
   }
   AddPart(MapGraphics);                  // マップパーツを追加
};

 管理クラスは登録したオブジェクト、もしくはオブジェクトポインタの配列を保持し、さらにオブジェクトを登録する関数(AddPart関数)を持ちます。登録されたオブジェクトは、GetPart関数によって、その複製を手に入れる事が出来ます。
 こうすると、MapGraphics関数がどれだけ増えようとも、管理クラスは1つで十分です。また、ゲームによってクラスを変えるのではなく、管理クラスに登録する絵を変える方式に変わるので、クラスの数がどっと増える事は無くなります。

 プロトタイプパターンの関係図は次のようになります。


 先ほどの管理クラスの例に置き換えるとこうなります。


 対応関係が良くわかると思います。


A Deep CopyかNarrow Copyか?

 Prototypeパターンは自身をコピーする関数(Clone関数等)がキーとなります。ところで、オブジェクトをコピーするというのは時に面倒だったりします。次の2つのクラスをご覧下さい。

class EasyCopyCls
{
protected:
   int m_A;
   int m_Ary[10];

public:
   Clone();
}


class DifficultCopyCls
{
protected:
   int *m_pA;
   int *m_Ary;       // 可変長配列
   int m_ArySize;   // 配列の大きさ

public:
   Clone();
}

 EasyCopyClsクラスはint型の変数及び静的な配列を持っています。このクラスのオブジェクトを複製するのはとても簡単です。ただ単に新しいオブジェクトを生成して、自身を「=」で代入するだけです。一方、DifficultCopyClsクラスはポインタ変数と可変長の配列を持っています。このオブジェクトの複製は注意が必要です。というのは、「=」で代入すると、「ポインタを共有」することになるからです。実体を複数のポインタが指すと、誰でも値を変える権利が発生してしまいます。STGでこれを用いると、たとえば敵の1匹が「やられた」と自身の変数を書き換えると、コピーしたすべての敵オブジェクトがいっせいに「やられた」になってしまいます。

 =演算子などを用い、ポインタの共有を許容するオブジェクトのコピーを「Narrow Copy(浅いコピー)」と言います。一方、格納する領域を新たに設け、ポインタ先の実体をそこにコピーして、ポインタの共有を行わないコピーを「Deep Copy(深いコピー)」と言います。prototypeパターンを用いる場合、このどちらであるかをしっかり意識する必要があります。

DeepCopyは時に非常に難しい事があります。例えば網の目のようにリンクがつながっているオブジェクトを、その形状を保ったまま丸ごとコピーするのは、なかなか難しいです。また、ポインタの先の実体がオブジェクトで、それがDeepCopyをサポートしていないとなると、DeepCopy自体が不可能になる場合もあります(カプセル化により変数へのアクセスが出来ないため)。よって、prototypeパターンを用いるときには、「複製が容易なオブジェクト」であることが大切になります