コンポーネントベースの設計:オブジェクトの相互作用の処理


9

コンポーネントベースのデザインで、オブジェクトが他のオブジェクトに対してどのように正確に機能するかはわかりません。

Objクラスがあるとしましょう。私がやります:

Obj obj;
obj.add(new Position());
obj.add(new Physics());

次に、ボールを動かすだけでなく、物理を適用する別のオブジェクトを作成するにはどうすればよいでしょうか。実装の詳細ではなく、オブジェクトの通信方法を抽象的に探しています。エンティティベースのデザインでは、次のようになります。

obj1.emitForceOn(obj2,5.0,0.0,0.0);

コンポーネント駆動の設計と基本的なことを行う方法をよりよく理解するための記事や説明は、非常に役に立ちます。

回答:


10

これは通常、メッセージを使用して行われます。このサイトの他の質問には、ここそこなど、たくさんの詳細があります。

特定の例に答えるにはMessage、オブジェクトが処理できる小さなクラスを定義する方法があります。例:

struct Message
{
    Message(const Objt& sender, const std::string& msg)
        : m_sender(&sender)
        , m_msg(msg) {}
    const Obj* m_sender;
    std::string m_msg;
};

void Obj::Process(const Message& msg)
{
    for (int i=0; i<m_components.size(); ++i)
    {
        // let components do some stuff with msg
        m_components[i].Process(msg);
    }
}

このようにしてObj、コンポーネント関連のメソッドでクラスインターフェイスを「汚染」することはありません。メッセージの処理を選択できるコンポーネントもあれば、無視するコンポーネントもあります。

最初に、このメソッドを別のオブジェクトから直接呼び出すことができます。

Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);

この場合は、obj2さんはPhysicsメッセージを選択し、処理することが行う必要があるものは何でもします。完了すると、次のいずれかになります。

  • Positionコンポーネントが選択する「SetPosition」メッセージを自分に送信します。
  • またはPosition、変更のためにコンポーネントに直接アクセスします(すべてのオブジェクトにPositionコンポーネントがあるとは限りませんが、Positionコンポーネントがの要件である可能性があるため、純粋なコンポーネントベースの設計ではかなり間違っていますPhysics)。

通常、メッセージの実際の処理を次のコンポーネントの更新まで遅らせることをお勧めします。それをすぐに処理することは、他のオブジェクトの他のコンポーネントにメッセージを送信することを意味する可能性があります。

おそらく後で、より高度なシステムを使用する必要があります。非同期メッセージキュー、オブジェクトのグループへのメッセージの送信、コンポーネントごとのメッセージの登録/登録解除などです。

上記のMessageように、クラスは単純な文字列の汎用コンテナにすることができますが、実行時の文字列の処理は実際には効率的ではありません。文字列、整数、浮動小数点数などの一般的な値のコンテナを使用できます。名前を付けて、さらにはIDを付けて、さまざまなタイプのメッセージを区別できます。または、特定のニーズに合わせて基本クラスを派生させることもできます。あなたのケースでは、目的の力ベクトルEmitForceMessageから派生しMessageて追加するを想像できますが、そうする場合はRTTIの実行時のコストに注意してください。


3
コンポーネントに直接アクセスする「純粋でない」ことについては心配しません。コンポーネントは、学界ではなく、機能と設計のニーズに対応するために使用されます。コンポーネントが存在することを確認したい(たとえば、getコンポーネント呼び出しの戻り値がnullでないことを確認する)。
ショーンミドルディッチ

RTTIを使用して最後に言ったように、私はいつもそれを考えていましたが、RTTIについて多くの人々が非常に多くの悪いことを言ってきました
jmasterx '27

@SeanMiddleditch確かに、私はこのようにします。同じエンティティの他のコンポーネントにアクセスするときに何をしているのかを常にダブルチェックする必要があることを明確にするために、このように述べます。
Laurent Couvidou 2012

@Miloコンパイラーによって実装されたRTTIとそのボトルネックになるdynamic_cast 可能性があります、今のところは心配していません。これが問題になった場合は、後で最適化できます。CRCベースのクラス識別子は魅力のように機能します。
Laurent Couvidou 2012

´template <typename T> uint32_t class_id(){static uint32_t v; return(uint32_t)&v; } ´-RTTIは必要ありません。
'08

3

あなたが示すものと同様の問題を解決するために私が行ったのは、いくつかの特定のコンポーネントハンドラーを追加し、ある種のイベント解決システムを追加することです。

したがって、「Physics」オブジェクトの場合、それが初期化されると、それ自体がPhysicsオブジェクトの中央マネージャーに追加されます。ゲームループでは、これらの種類のマネージャーには独自の更新ステップがあるため、このPhysicsManagerが更新されると、すべての物理相互作用が計算され、イベントキューに追加されます。

すべてのイベントを生成した後、何が起こったかを確認し、それに応じてアクションを実行するだけでイベントキューを解決できます。この場合、オブジェクトAとBが何らかの形で相互作用したことを示すイベントが存在するはずなので、emitForceOnメソッドを呼び出します。

この方法の長所:

  • 概念的には、従うのは本当に簡単です。
  • クワッドレスまたは必要なものを使用するなど、特定の最適化の余地を与えます。
  • それは本当に「プラグアンドプレイ」になります。物理オブジェクトは、マネージャには存在しないため、非物理オブジェクトと相互作用しません。

短所:

  • たくさんの参照が動き回るので、注意しないと(コンポーネントからコンポーネントの所有者、マネージャーからコンポーネント、イベントから参加者まで)すべてを正しくクリーンアップするのが少し面倒になる可能性があります。 )。
  • すべてを解決する順序に特別な配慮をする必要があります。それはあなたのケースではないと思いますが、イベントが別のイベントを作成する複数の無限ループに直面し、それをイベントキューに直接追加しただけでした。

これがお役に立てば幸いです。

PS:誰かがこれを解決するより良い/より良い方法を持っているなら、私は本当にそれを聞きたいです。


1
obj->Message( "Physics.EmitForce 0.0 1.1 2.2" );
// and some variations such as...
obj->Message( "Physics.EmitForce", "0.0 1.1 2.2" );
obj->Message( "Physics", "EmitForce", "0.0 1.1 2.2" );

この設計で注意すべき点がいくつかあります。

  • コンポーネントの名前は最初のパラメーターです-これは、メッセージに対して過剰なコードが機能しないようにするためです-メッセージがトリガーする可能性のあるコンポーネントを知ることはできません-そして、すべてのコンポーネントが90%のエラーでメッセージを噛みたくはありません多くの不要なブランチとstrcmpに変換するレート。
  • メッセージの名前は2番目のパラメーターです。
  • 最初のドット(#1と#2)は必要ありません。読みやすくするためだけです(コンピューターではなく、人々にとって)。
  • sscanf、iostream、you-name-itと互換性があります。メッセージの処理を単純化するために何もしない構文糖はありません。
  • 1つの文字列パラメーター:比較的不明なタイプの不明な数のパラメーターをサポートする必要があるため、ネイティブタイプを渡すことは、メモリ要件の点で安くはありません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.