コンポーネントベースのエンティティシステムのメッセージ処理に関するヒント


8

コンポーネントベースのエンティティシステムを実装しようとしていますが、メッセージングの処理方法について少し混乱しています。システムをテストできるように、解決したい2つの問題があります。以下は私がこれまでに持っているコードです、

Entityクラス:

class Entity{
public:
    Entity(unsigned int id):
    id_(id)
    {};
    void handleMessage(BaseMessage &message){
        for(auto element: components_){
            element.second->handleMessage(message);
        }
    }
    template<class T>
    void attachComponent(T *component){
        //Consider making safer in case someone tries to attach same component type twice
        components_[typeid(T).hash_code()] = component;
    }
    template<class T>
    void detachComponent(void){
        components_.erase(typeid(T).hash_code());
    }
    template<class T>
    T* getComponent(void)const{
        return *components_.find(typeid(T).hash_code());
    }
    unsigned int getInstanceID(void)const{
        return id_;
    }
private:
    unsigned int id_;
    std::map<size_t, BaseComponent*> components_;
};

基本コンポーネントとメッセージクラス:

class BaseComponent{
public:
    virtual void handleMessage(BaseMessage &message){};
};

class BaseMessage{
public:
    virtual int getType(void) = 0;
};

1.メッセージタイプの処理

最初の質問は、さまざまな(BaseMessageから派生した)メッセージタイプをどのように処理するかです。

派生メッセージタイプのメッセージタイプを処理する2つの方法を考えました。1つは、メッセージタイプを指定する文字列からハッシュを生成(つまり、FNVを使用)し、そのハッシュを使用してメッセージタイプを決定することです。そのため、handleMessage(BaseMessage &message)関数は最初にこのハッシュをメッセージから抽出し、次にstatic_castを適切なタイプに実行します。

2番目の方法は、次のようにテンプレートを使用することです(attachComponentエンティティクラスのメソッドと同様)。

template<class T>
handleMessage(T& message){};

特定のコンポーネントが作成するメッセージタイプごとに特化します。

2番目の方法を使用する場合、欠点はありますか?パフォーマンスに関してはどうですか、なぜこの種の使用をもっと頻繁に見ないのですか?

2.入力処理

私の2番目の質問は、入力を処理するための(待ち時間と使いやすさの観点から)最適な方法は何ですか?

私の考えは、InputHandlerComponentおそらくいくつかのファイルで定義された特定のキープレスをリッスンするためにキーボードクラスに登録するを作成することでした。例えば

keyboard.register( player.getComponent<InputHandler>() , 'W')

コンポーネントベースのシステムについてより簡潔なガイドがあったらいいのにと思いますが、同じことを行うには多くの異なる方法があると思います。他にも質問がありますが、最初にできることを実装してみるのが賢明だと思います。

回答:


3

典型的なエンティティシステムでは、ゲームとコンポーネントのすべてのロジックをシステムに残し、データのみを格納します。エンティティは単なる識別子です。入力処理はその方法ではるかに簡単になり、ゲームの柔軟性については言うまでもありません。

オブザーバーパターンや信号/スロットなど、イベントを処理する方法はたくさんあります。テンプレート/仮想関数または関数ポインターを使用してメッセージ処理を行うこともできますが、ブースト::シグナルを使用する方がおそらく簡単です。パフォーマンス*が必要な場合は、テンプレートと仮想関数または関数ポインターを使用することをお勧めします。

*あなたのコードは本当に最適化できることに気づきました。typeid演算子に代わる方法があり、実際にはを使用するよりも高速ですtypeid。たとえば、テンプレート/マクロと単純な整数を使用してクラスのIDを定義します。

(JavaフレームワークArtemisからインスピレーションを得た)インスピレーションが必要な場合は、私のエンティティシステムを確認できます。(エンティティ関連のイベント以外の)イベントを処理する方法は実装していませんが、それはユーザーに任せましたが、redditで見つけたentityxライブラリを探し出しました。自分のライブラリにイベントシステムを追加できると思いました。私のエンティティシステムは、必要な機能が100%完成しているわけではありませんが、機能し、適切なパフォーマンスを発揮します(ただし、エンティティを格納する方法で特に最適化できます)。

イベントを処理するためにできることは次のとおりですentityxからヒントを得たもの):

BaseReceiver / BaseListener

  • リスナー/レシーバーを格納できる基本クラス。

レシーバー/リスナー

  • これはテンプレートクラスであり、仮想関数をオーバーライドする必要がありますrecieve(const T&)。ここで、Tはイベント情報です。
  • イベントが発生したときに特定の情報を送信するイベントによって通知されるクラスは、このクラスを継承する必要があります。

EventHandler

  • イベントの発生/発生
  • 発生したイベントによって通知されるレシーバー/リスナーオブジェクトのリストがあります。

このデザインをC ++で実装しましたが、boost :: signalsを使用していません。こちらご覧いただけます

長所

  • 静的型付け
  • 仮想関数(boost :: signalsよりも高速ですが、そうでなければなりません)
  • 通知を正しく発行しなかった場合のコンパイル時エラー
  • C ++ 98互換(私は信じています)

短所

  • ファンクタを定義する必要があります(グローバル関数ではイベントを処理できません)
  • イベントキューはありません。登録してすぐに発射してください。(あなたが望むものではないかもしれません)
  • 直接の呼び出しではありません(イベントにとってそれほど悪くはありません)
  • 同じクラスでの複数のイベント処理なし(これは変更できるため、複数の継承を介して複数のイベントを許可しますが、実装に時間がかかる場合があります)

また、私はレンダリングと入力処理を処理するエンティティシステムのを持っています。それは非常に単純ですが、私のライブラリを理解するための多くの概念を示しています。


typeidなしでタイプを発見するより速い方法の例を挙げられますか?
ルークB.

「一般的なエンティティシステムでは、ゲームのすべてのロジックとデータのみを格納するコンポーネントをシステムに残します」-「一般的な」エンティティシステムなどはありません。
Den

@LukeB。私のエンティティシステムのソースコードを見て、私は使用しませんtypeid(ComponentとComponentContainerを見てください)。基本的に、私はコンポーネントの「タイプ」を整数として格納します。コンポーネントのタイプごとに増分するグローバル整数があります。また、コンポーネントを2D配列に格納します。最初のインデックスはエンティティのIDで、2番目のインデックスはコンポーネントタイプのIDです。つまり、components [entityId] [componentTypeId]
miguel.martin

@ miguel.martin。お返事ありがとうございます。私の混乱は主に、エンティティシステムを実装する方法いくつかあり、すべてに欠点と利点があるという事実に起因していると思います。たとえば、使用するアプローチでは、CRTPを使用して新しいコンポーネントタイプを登録する必要があります。この要件は明示的ではないため、個人的には嫌いです。私は自分の実装を再検討する必要があると思います。
グリーバーハート2013年

1
@MarsonMaoは、固定
miguel.martin

1

C#でコンポーネントベースのエンティティシステムに取り組んでいますが、一般的なアイデアとパターンはまだ適用されます。

メッセージタイプを処理する方法は、各コンポーネントサブクラスがコンポーネントの保護されたRequestMessage<T>(Action<T> action) where T : IMessageメソッドを呼び出すことです。これは、コンポーネントサブクラスが特定のタイプのメッセージを要求し、コンポーネントがそのタイプのメッセージを受信したときに呼び出されるメソッドを提供することを意味します。

そのメソッドは保存され、コンポーネントがメッセージを受信すると、リフレクションを使用してメッセージのタイプを取得します。これを使用して、関連するメソッドを検索し、呼び出します。

リフレクションを他のシステムに置き換えることができます。ハッシュが最善の方法です。見て、複数の派遣ビジターパターンものの、いくつかの他のアイデアのために。

入力に関しては、私はあなたが考えていることとはまったく異なることをすることにしました。入力ハンドラは、キーボード/マウス/ゲームパッド入力を、ユーザー設定/プレーヤーがコンピュータに接続したものに基づいて、可能なアクション(MoveForward、MoveBackwards、StrafeLeft、Useなど)のフラグ列挙体(ビットフィールド)に個別に変換します。各コンポーネントはUserInputMessage、ビットフィールドとプレーヤーのルック軸の両方を含むすべてのフレームを3dベクトルとして取得します(開発者は一人称シューティングゲームに向かっています)。これにより、プレーヤーにキーバインディングを簡単に変更させることもできます。

質問の最後で述べたように、コンポーネントベースのエンティティシステムを作成するには、さまざまな方法があります。私は自分のシステムを作成中のゲームに重点的に配置しているので、RTSなどのコンテキストでは当然、私が行うことの一部が意味をなさないかもしれませんが、それは実装されており、機能していますこれまでのところ。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.