私はシューティングゲーム(1942、クラシック2Dグラフィックスなど)を書いていますが、コンポーネントベースのアプローチを使用したいと思います。これまでのところ、次の設計について考えました。
各ゲーム要素(飛行船、発射物、パワーアップ、敵)はエンティティです
各エンティティは、実行時に追加または削除できるコンポーネントのセットです。例には、Position、Sprite、Health、IA、Damage、BoundingBoxなどがあります。
飛行船、発射物、敵、パワーアップはゲームクラスではないという考え方です。エンティティは、所有するコンポーネントによってのみ定義されます(時間の経過とともに変化する可能性があります)。そのため、プレイヤーの飛行船は、スプライト、位置、ヘルス、および入力コンポーネントから始まります。パワーアップには、Sprite、Position、BoundingBoxがあります。等々。
メインループは、ゲームの「物理」、つまりコンポーネントの相互作用を管理します。
foreach(entity (let it be entity1) with a Damage component)
foreach(entity (let it be entity2) with a Health component)
if(the entity1.BoundingBox collides with entity2.BoundingBox)
{
entity2.Health.decrease(entity1.Damage.amount());
}
foreach(entity with a IA component)
entity.IA.update();
foreach(entity with a Sprite component)
draw(entity.Sprite.surface());
...
コンポーネントは、メインC ++アプリケーションにハードコーディングされています。エンティティはXMLファイル(luaまたはpythonファイルのIAパーツ)で定義できます。
メインループはエンティティをあまり気にしません。コンポーネントを管理するだけです。ソフトウェア設計では、次のことを許可する必要があります。
コンポーネントを指定して、それが属するエンティティを取得します
エンティティを指定すると、タイプ「type」のコンポーネントを取得します
すべてのエンティティについて、何かをする
すべてのエンティティのコンポーネントについて、何かを実行します(例:シリアル化)
私は次のことを考えていました:
class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component {...};
class Position : public Component {...};
class IA : public Component {... virtual void update() = 0; };
// I don't remember exactly the boost::fusion map syntax right now, sorry.
class Entity
{
int id; // entity id
boost::fusion::map< pair<Sprite, Sprite*>, pair<Position, Position*> > components;
template <class C> bool has_component() { return components.at<C>() != 0; }
template <class C> C* get_component() { return components.at<C>(); }
template <class C> void add_component(C* c) { components.at<C>() = c; }
template <class C> void remove_component(C* c) { components.at<C>() = 0; }
void serialize(filestream, op) { /* Serialize all componets*/ }
...
};
std::list<Entity*> entity_list;
この設計では、#1、#2、#3(boost :: fusion :: mapアルゴリズムに感謝)と#4を取得できます。また、すべてがO(1)です(正確ではありませんが、それでも非常に高速です)。
より一般的なアプローチもあります。
class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component { static const int type_id = 0; };
class Position : public Component { static const int type_id = 1; };
class Entity
{
int id; // entity id
std::vector<Component*> components;
bool has_component() { return components[i] != 0; }
template <class C> C* get_component() { return dynamic_cast<C> components[C::id](); } // It's actually quite safe
...
};
もう1つの方法は、Entityクラスを削除することです。各コンポーネントタイプは独自のリストにあります。スプライトリスト、ヘルスリスト、ダメージリストなどがあります。これらはエンティティIDにより同じロジックエンティティに属していることがわかります。これはもっと簡単ですが、IAコンポーネントは基本的に他のすべてのエンティティのコンポーネントにアクセスする必要があり、各ステップで他のコンポーネントのリストを検索する必要があります。
どのアプローチが良いと思いますか?boost :: fusion mapは、そのように使用するのに適していますか?