通常、物理学またはグラフィックスコンポーネントは、コンポーネント指向システムでどのように構築されますか?


19

私は最後の48時間をオブジェクトコンポーネントシステムに目を通し、それを実装する準備が十分に整ったと感じています。基本のObjectクラスとComponentクラスを作成しましたが、実際のコンポーネントの作成を開始する必要があるため、少し混乱しています。HealthComponentまたは基本的に単なるプロパティのようなものでそれらを考えるとき、それは完全に理にかなっています。物理/グラフィックコンポーネントとしてより一般的なものである場合、少し混乱します。

これまでのところ、私のオブジェクトクラスは次のようになっています(変更が必要な場合は、まだ新しいことをお知らせください)。

typedef unsigned int ID;

class GameObject
{
public:

    GameObject(ID id, Ogre::String name = "");
    ~GameObject();

    ID &getID();
    Ogre::String &getName();

    virtual void update() = 0;

    // Component Functions
    void addComponent(Component *component);
    void removeComponent(Ogre::String familyName);

    template<typename T>
    T* getComponent(Ogre::String familyName)
    {
        return dynamic_cast<T*>(m_components[familyName]);
    }

protected:

    // Properties
    ID m_ID;
    Ogre::String m_Name;
    float m_flVelocity;
    Ogre::Vector3 m_vecPosition;

    // Components
    std::map<std::string,Component*> m_components;
    std::map<std::string,Component*>::iterator m_componentItr;
};

今、私が直面している問題は、一般の人々が物理学/グラフィックスなどのコンポーネントに何を入れるかです。Ogre(私のレンダリングエンジン)の場合、表示されるオブジェクトは、シーンにアタッチするための複数のOgre :: SceneNode(複数の場合もある)、表示されるメッシュを表示するためのOgre :: Entity(複数の場合もある)で構成されます。複数のGraphicComponentをオブジェクトに追加し、各GraphicComponentで1つのSceneNode / Entityを処理するのが最善でしょうか、それとも各コンポーネントの1つが必要なのでしょうか?

物理学では、私はさらに混乱しています。おそらくRigidBodyを作成し、質量/インテリア/などを追跡していると思います。理にかなっています。しかし、実際にコンポーネントに詳細を入れる方法を考えるのに苦労しています。

これらの「必須」コンポーネントをいくつか完了したら、もっと意味があると思います。今のところ、私はまだ少し困惑しています。

回答:


32

まず第一に、あなたは、ビルド・コンポーネント・ベースのシステムでは、あなたがいない時に持って回すのアプローチ取るためにすべてをコンポーネントにします。実際、一般的にはすべきではありません-それは初心者の間違いです。私の経験では、コンポーネントベースのアーキテクチャでレンダリングシステムと物理システムを結び付ける最良の方法は、これらのコンポーネントを実際のレンダリングオブジェクトまたは物理プリミティブオブジェクト用のダムコンテナ以上にすることです。

これには、レンダリングシステムと物理システムをコンポーネントシステムから切り離しておくという利点があります。

たとえば、物理システムの場合、物理シミュレータは通常、ティックするたびに操作する剛体オブジェクトの大きなリストを保持します。コンポーネントシステムはそれを混乱させません-物理コンポーネントは、コンポーネントが属するオブジェクトの物理的側面を表す剛体への参照を単に保存し、コンポーネントシステムからのメッセージを剛体の適切な操作に変換します。

レンダリングの場合も同様です。レンダラーには、レンダリングされるインスタンスデータを表すものがあり、視覚コンポーネントはそれへの参照を保持します。

それは比較的簡単です-何らかの理由で人々を混乱させるようですが、それは多くの場合、彼らがそれについて考えすぎているためです。そこには魔法はあまりありません。そして、完璧な設計に苦労するのではなく、物事を成し遂げることが最も重要であるため、mombocoが示唆しているように、コンポーネントの少なくともいくつかがインターフェースを定義し、ゲームオブジェクトの直接のメンバーであるアプローチを強く検討する必要があります。それは同じように有効なアプローチであり、理解しやすい傾向があります。

他のコメント

これはあなたの直接的な質問とは無関係ですが、これらはあなたのコードを見たときに気づいたものです:

ゲームオブジェクトにOgre :: Stringとstd :: stringを混在させているのはなぜですか。これは表面上は非グラフィックオブジェクトであるため、Ogreに依存しないはずです。変換を行うレンダリングコンポーネント自体を除き、Ogreへの直接参照をすべて削除することをお勧めします。

update()は純粋な仮想であり、これはGameObjectをサブクラス化する必要があることを意味します。実際には、コンポーネントアーキテクチャを使用したい方向とは正反対です。コンポーネントを使用する主なポイントは、継承よりも機能の集約を優先することです。

const(特に参照によって戻る場合)の使用から恩恵を受けることができます。また、ベロシティを本当にスカラーにする必要があるかどうかもわかりません(または、速度と位置が、目的とするような過度に汎用的なコンポーネント手法でゲームオブジェクトに存在する必要がある場合)。

コンポーネントの大きなマップとupdate()ゲームオブジェクトの呼び出しを使用するというアプローチは、まったく最適ではありません(そして、この種のシステムを最初に構築する人にとってよくある落とし穴です)。更新中のキャッシュの一貫性が非常に悪くなり、同時実行性と、大量のデータまたは動作のSIMDスタイルのプロセスの傾向を一度に利用することができません。多くの場合、ゲームオブジェクトがそのコンポーネントを更新するのではなく、コンポーネント自体を担当するサブシステムがそれらを一度にすべて更新するデザインを使用する方が適切です。これは読む価値があるかもしれません。


この答えは素晴らしかった。私はまだコメント内に段落を挿入する方法を理解していません(キーを入力するだけで送信します)が、これらの応答に対処します。オブジェクト/コンポーネントシステムの説明は非常に理にかなっています。それで、明確にするために、PhysicsComponentの場合、コンポーネントのコンストラクターで、PhysicsManagerのCreateRigidBody()関数を呼び出して、それへの参照をコンポーネントに保存できますか?
エイダンナイト

「その他のコメント」部分に関しては、これに感謝します。私は通常、Ogreを使用する私のプロジェクトの基盤としてすべてのOgre関数を使用することを好むため、文字列を混合しましたが、マップ/ペアなどがないため、Object / Componentシステムで切り替えを開始しました。あなたは正しいですが、私はそれらをすべてstdメンバーに変換し、Ogreから分離しました。
エイダンナイト

1
私はしばらくここで読むのコンポーネントベースのモデル、過剰一般化に対する素敵なコントラストに関する最初のまともな答えだ1
はMaik Semder

5
これにより、コヒーレンシ(データと命令キャッシュの両方)が向上し、更新を個別のスレッドまたはワーカープロセス(SPUなど)にほとんど簡単にオフロードすることができます。場合によっては、コンポーネントを更新する必要さえありません。つまり、update()メソッドのno-op呼び出しのオーバーヘッドを回避できます。また、データ指向システムの構築を支援します-それはすべてCPUアーキテクチャの最新トレンドを活用するバルク処理に関するものです。

2
+1「完璧なデザインに
苦しむ

5

コンポーネントアーキテクチャは、同じノードからすべての要素を継承することで達成される大規模な階層、およびつながるカップリングの問題を回避するための代替手段です。

「汎用」コンポーネントを持つコンポーネントアーキテクチャを示しています。「汎用」コンポーネントをアタッチおよびデタッチするための基本的なロジックがあります。汎用コンポーネントを使用したアーキテクチャは、どのコンポーネントであるかわからないため、実現がより困難です。利点は、このアプローチが拡張可能であることです。

有効なコンポーネントアーキテクチャは、定義されたコンポーネントで実現されます。つまり、定義では実装ではなくインターフェイスが定義されます。たとえば、GameObjectはIPhysicInstance、Transformation、IMesh、IAnimationControllerを持つことができます。これには、インターフェイスを知っていて、GameObjectが各コンポーネントを処理する方法を知っているという利点があります。

過剰一般化という用語への対応

概念は明確ではないと思います。コンポーネントと言っても、ゲームオブジェクトのすべてのコンポーネントがコンポーネントインターフェイスまたは類似のものから継承する必要があるという意味ではありません。ただし、これは汎用コンポーネントのアプローチであり、コンポーネントとして名前を付けるという概念だと思います。

コンポーネントアーキテクチャは、ランタイムオブジェクトモデルに存在するもう1つのアーキテクチャです。それだけではなく、すべての場合に最適というわけではありませんが、経験から、低カップリングなどの多くの利点があることが実証されています。

コンポーネントアーキテクチャを区別する主な機能は、関係is-aをhas-aに変換することです。たとえば、オブジェクトアーキテクチャは次のとおりです。

is-aアーキテクチャ

オブジェクトアーキテクチャの代わりにhas-a(components):

has-aアーキテクチャ

プロパティ中心のアーキテクチャもあります:

たとえば、通常のオブジェクトアーキテクチャは次のようになります。

  • Object1

    • 位置=(0、0、0)
    • 方向=(0、43、0)
  • Object2

    • 位置=(10、0、0)
    • 方向=(0、0、30)

プロパティ中心のアーキテクチャ:

  • ポジション

    • Object1 =(0、0、0)
    • Object2 =(10、0、0)
  • オリエンテーション

    • Object1 =(0、43、0)
    • Object2 =(0、0、30)

オブジェクトモデルアーキテクチャの方が良いですか?パフォーマンスの観点から見ると、キャッシュフレンドリーであるため、プロパティ中心の方が優れています。

コンポーネントは、has-aではなく、関係is-aを参照します。コンポーネントは、変換マトリックス、クォータニオンなどです。ただし、ゲームオブジェクトとの関係は関係is-aで、ゲームオブジェクトは継承されているため、変換を持ちません。ゲームオブジェクトには変換があります(関係has-a)。変換はコンポーネントです。

ゲームオブジェクトはハブのようなものです。常にそこにあるコンポーネントが必要な場合は、コンポーネント(マトリックスなど)がオブジェクトではなく、ポインター内にある必要があります。

すべての場合に完璧なゲームオブジェクトはありません。エンジン、エンジンが使用するライブラリに依存します。メッシュを持たないカメラが欲しいのかもしれません。is-aアーキテクチャの問題は、ゲームオブジェクトにメッシュがある場合、ゲームオブジェクトから継承するカメラにメッシュが必要であるということです。コンポーネントには、カメラに変形、動きのコントローラーなど、必要なものがすべて備わっています。

詳細については、「Jason Gregory」の「Game Engine Architecture」の本の「14.2。Runtime Object Model Architecture」の章で具体的にこの主題を扱っています。

UMLダイアグラムはそこから抽出されます


最後の段落には同意しませんが、過剰に一般化する傾向があります。コンポーネントベースのモデルは、すべての機能がコンポーネントである必要があるという意味ではなく、機能とデータの関係を考慮する必要があります。MeshのないAnimationControllerは役に立たないので、所属する場所にあるMeshに配置します。変換のないゲームオブジェクトはゲームオブジェクトではありません。定義上、3Dワールドに属しているため、通常のメンバーとしてゲームオブジェクトに入れてください。カプセル化機能とデータの通常の規則は引き続き適用されます。
マイクセンダー

@Maik Semder:一般的な用語で過剰に一般化することに同意しますが、用語の要素は明確ではないと思います。回答を編集しました。それを読んで、あなたの印象を与えてください。
momboco

@mombocoよく、あなたは同じポイントをより少なく繰り返しました;)TransformとAnimationControllerはまだコンポーネントとして記述されています。キーは、どのようなコンポーネントに配置する必要があり、何を定義することではない:
はMaik Semder

そもそもGame-Object(GO)を動作させるために何かが必要な場合、つまりオプションではなく必須である場合、それはコンポーネントではありません。コンポーネントはオプションです。変換は良い例です。GOにはまったくオプションではありません。変換なしのGOは意味がありません。一方で、すべてのGOが物理を必要とするわけではないため、それがコンポーネントになる可能性があります。
マイクセンダー

AnimationController(AC)とMeshのように2つの機能性の間に強い関係がある場合は、ACをコンポーネントに押し込んでこの関係を決して壊さないでください。対照的に、Meshは完全なコンポーネントですが、ACは属しますメッシュに。
マイクセンダー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.