外部コンポーネントマネージャーでエンティティシステムを整理しますか?


13

トップダウンマルチプレイヤー2Dシューティングゲーム用のゲームエンジンを設計しています。これは、他のトップダウンシューティングゲームで合理的に再利用できるようにしたいものです。現時点では、その中のエンティティシステムのようなものをどのように設計すべきかを考えています。最初に私はこれについて考えました:

EntityManagerというクラスがあります。UpdateというメソッドとDrawという別のメソッドを実装する必要があります。ロジックとレンダリングを分離する理由は、スタンドアロンサーバーを実行している場合、Drawメソッドを省略できるためです。

EntityManagerは、BaseEntityタイプのオブジェクトのリストを所有しています。各エンティティは、EntityModel(エンティティの描画可能な表現)、EntityNetworkInterface、EntityPhysicalBodyなどのコンポーネントのリストを所有しています。

EntityManagerは、EntityRenderManager、EntityNetworkManager、EntityPhysicsManagerなどのコンポーネントマネージャーのリストも所有しています。各コンポーネントマネージャーは、エンティティコンポーネントへの参照を保持します。このコードをエンティティ自身のクラスから移動し、代わりにまとめて実行する理由はさまざまです。たとえば、ゲームには外部の物理ライブラリ、Box2Dを使用しています。Box2Dでは、最初にボディとシェイプをワールド(この場合はEntityPhysicsManagerが所有)に追加し、コリジョンコールバック(システムのエンティティオブジェクト自体にディスパッチされる)を追加します。次に、システム内のすべてをシミュレートする関数を実行します。このような外部コンポーネントマネージャーで行うよりも、これを行うための優れたソリューションを見つけるのは難しいと思います。

エンティティの作成は、次のように行われます。EntityManagerが登録方法のRegisterEntity(entityClass、工場)を実装する方法を、そのクラスのエンティティを作成することを。また、BaseEntity型のオブジェクトを返すメソッドCreateEntity(entityClass)も実装します。

ここで私の問題が起こります:コンポーネントへの参照はどのようにコンポーネントマネージャーに登録されますか?ファクトリー/クロージャーからコンポーネントマネージャーをどのように参照するかわかりません。


おそらくこれがハイブリッドシステムを意味するのかどうかはわかりませんが、あなたの「マネージャー」が私が一般に「システム」と呼んでいるものだと思われます。つまり、エンティティは抽象IDです。コンポーネントはデータのプールです。そして、あなたが「マネージャー」と呼ぶものは、一般に「システム」と呼ばれるものです。ボキャブラリーを正しく解釈していますか?
BRPocock


見てくださいgamadu.com/artemisをし、その方法は、あなたの質問に答えるかどうかを確認します。
パトリックヒューズ

1
エンティティシステムの定義についてはコンセンサスがほとんどないため、エンティティシステムを設計する方法はありません。何@BRPocockを説明し、また、アルテミスは、HESはこのブログに深さでより多く記載されて使用するもの:t-machine.org/index.php/category/entity-systemsウィキ一緒:entity-systems.wikidot.com
user8363

回答:


6

システムは、エンティティのキ​​ーと値のペアを何らかのマップ、辞書オブジェクト、または連想配列(使用する言語に応じて)に保存する必要があります。さらに、Entity Objectを作成するとき、どのSystemからでも登録を解除できるようにする必要がない限り、Managerに格納することについては心配しません。エンティティはコンポーネントの複合体ですが、コンポーネントの更新を処理すべきではありません。これはシステムが処理する必要があります。代わりに、エンティティを、システムに含まれるすべてのコンポーネントにマッピングされるキーとして、またそれらのコンポーネントが相互に通信するための通信ハブとして扱います。

Entity-Component-Systemモデルの優れた点は、1つのコンポーネントからエンティティの残りのコンポーネントにメッセージを簡単に渡す機能を実装できることです。これにより、コンポーネントは、そのコンポーネントが誰であるか、または変更中のコンポーネントをどのように処理するかを実際に知ることなく、別のコンポーネントと通信できます。代わりに、メッセージを渡して、コンポーネントが自分自身を変更できるようにします(存在する場合)

たとえば、Position Systemには多くのコードは含まれず、Positionコンポーネントにマッピングされたエンティティオブジェクトのみを追跡します。しかし、位置が変更されると、関係するエンティティにメッセージを送信でき、そのエンティティのすべてのコンポーネントに順番に渡されます。なんらかの理由でポジションが変わりますか?位置システムは、位置が変更されたことを示すメッセージをエンティティに送信し、どこかで、そのエンティティの画像レンダリングコンポーネントがそのメッセージを取得し、次に自身を描画する場所を更新します。

逆に、物理システムは、そのすべてのオブジェクトが何をしているかを知る必要があります。衝突をテストするには、すべてのワールドオブジェクトを表示できる必要があります。衝突が発生すると、エンティティのコンポーネントを直接参照するのではなく、何らかの「方向変更メッセージ」をエンティティに送信して、エンティティの方向コンポーネントを更新します。これにより、特定のコンポーネントに依存するのではなく、メッセージを使用して方向を変更する方法からマネージャーを分離します(まったく存在しない場合があり、この場合、メッセージはエラーではなく耳が聞こえなくなるだけです)予想されるオブジェクトが存在しなかったために発生します)。

ネットワークインターフェイスを持っていると述べたので、これから大きな利点に気付くでしょう。ネットワークコンポーネントは、他の誰もが知っておく必要のあるすべてのメッセージを受信します。ゴシップが大好きです。その後、ネットワークシステムが更新されると、ネットワークコンポーネントは他のクライアントマシン上の他のネットワークシステムにそれらのメッセージを送信し、それらのメッセージを他のすべてのコンポーネントに再送信してプレーヤーの位置などを更新します。ネットワーク経由でメッセージを送信しますが、それはシステムの美しさです。適切なものを登録することで、そのロジックを制御することができます。

要するに:

エンティティは、メッセージを受信できるコンポーネントの構成です。エンティティはメッセージを受信し、そのメッセージをすべてのコンポーネントに委任して更新します。(位置変更メッセージ、速度変更方向など)これは、すべてのコンポーネントが互いに直接話し合うのではなく、互いに聞くことができる中央のメールボックスのようなものです。

コンポーネントは、エンティティの一部であり、エンティティの状態を保存します。これらは特定のメッセージを解析し、他のメッセージを捨てることができます。たとえば、「方向コンポーネント」は「方向変更メッセージ」のみを対象とし、「位置変更メッセージ」は対象外とします。コンポーネントはメッセージに基づいて独自の状態を更新し、システムからメッセージを送信して他のコンポーネントの状態を更新します。

システムは特定のタイプのすべてのコンポーネントを管理し、フレームごとにコンポーネントを更新し、コンポーネントが管理するコンポーネントからコンポーネントが属するエンティティにメッセージを送信します

システムは、すべてのコンポーネントを並行して更新し、すべてのメッセージをそのまま保存することができます。次に、すべてのシステムの更新メソッドの実行が完了したら、各システムにメッセージを特定の順序でディスパッチするように依頼します。おそらく最初にコントロール、次に物理、次に方向、位置、レンダリングなどが続きます。物理の方向変更は常にコントロールに基づく方向変更よりも優先されるため、どの順序で送信されるかが重要です。

お役に立てれば。それはデザインパターンの地獄ですが、正しく行われた場合、それは途方もなく強力です。


0

私は自分のエンジンで同様のシステムを使用していますが、その方法は各エンティティにコンポーネントのリストが含まれていることです。EntityManagerから、各エンティティをクエリし、どのエンティティに特定のコンポーネントが含まれているかを確認できます。例:

class Component
{
    private uint ID;
    // etc...
}

class Entity
{
    List<Component> Components;
    // etc...
    public bool Contains(Type type)
    {
        foreach(Component comp in Components)
        {
            if(typeof(comp) == type)
                return true;
        }
        return false;
    }
}

class EntityManager
{
    List<Entity> Entities;
    // etc...
    public List<Entity> GetEntitiesOfType(Type type)
    {
        List<Entity> results = new List<Entity>();
        foreach(Entity entity in Entities)
        {
            if(entity.Contains(type))
                results.Add(entity);
        }
        return results;
    }
}

明らかにこれは正確なコードではありません(実際には、使用するのではなく、異なるコンポーネントタイプをチェックするテンプレート関数が必要ですtypeof)が、概念はあります。次に、それらの結果を取得し、探しているコンポーネントを参照し、それを工場に登録します。これにより、コンポーネントとそのマネージャー間の直接的な結合が防止されます。


3
注意事項:エンティティにデータが含まれる時点では、エンティティではなくオブジェクトです...この構造では、ECSの並列化(基本的な)メリットのほとんどが失われます。「純粋な」E / C / Sシステムはリレーショナルであり、オブジェクト指向ではありません…一部のケースでは必ずしも「悪い」というわけではありませんが、確かに「リレーショナルモデルを破る」
BRPocock

2
私はあなたを理解しているかわかりません。私の理解(間違っている場合は修正してください)は、基本的なEntity-Component-Systemには、コンポーネントを格納するEntityクラスがあり、ID、名前、または何らかの識別子がある場合があるということです。エンティティの「タイプ」とはどういう意味か、誤解があると思います。エンティティを「タイプ」と言うとき、コンポーネントのタイプを指しています。つまり、エンティティは、スプライトコンポーネントを含む場合、「スプライト」タイプです。
マイククロック

1
純粋なエンティティ/コンポーネントシステムでは、エンティティは通常アトミックtypedef long long int Entityです。コンポーネントは、それがstructアタッチされているエンティティへの参照を持つレコードです(オブジェクトクラスとして実装される場合もあれば、単にとして実装される場合もあります)。システムはメソッドまたはメソッドのコレクションになります。ECSモデルはOOPモデルとあまり互換性がありませんが、コンポーネントは(ほとんど)データのみのオブジェクトであり、システムはコードのみのシングルトンオブジェクトであり、状態はコンポーネント内に存在します... 「純粋な」ものよりも一般的で、彼らは多くの生得的な利益を失います。
BRPocock

2
@BRPocockは、「純粋な」エンティティシステムです。オブジェクトとしてのエンティティは完全に問題ないと思います。単純なIDである必要はありません。一つはシリアル化された表現であり、もう一つはオブジェクト/概念/エンティティのメモリ内レイアウトです。データ駆動性を維持できる限り、「純粋な」方法だからといって、非イディオマティックなコードに縛られるべきではありません。
レイン

1
@BRPocockこれは公正な警告ですが、「t-machine」のようなエンティティシステムの場合です。理由はわかりますが、コンポーネントベースのエンティティをモデル化する方法はそれだけではありません。俳優は興味深い選択肢です。特に純粋に論理的なエンティティの場合は、それらを高く評価する傾向があります。
レイン

0

1)Factoryメソッドには、それを呼び出したEntityManagerへの参照を渡す必要があります(例としてC#を使用します)。

delegate BaseEntity EntityFactory(EntityManager manager);

2)CreateEntityに、エンティティのクラス/タイプのほかにID(たとえば、文字列、整数、それはあなた次第)も受け取り、そのIDをキーとして使用して、作成されたエンティティを辞書に自動的に登録します。

class EntityManager
{
    // Rest of class omitted

    BaseEntity CreateEntity(string id, Type entityClass)
    {
        BaseEntity entity = factories[entityClass](this);
        registry.Add(id, entity);
        return entity;
    }

    Dictionary<Id, BaseEntity> registry;
}

3)GetterをEntityManagerに追加して、IDでエンティティを取得します。

class EntityManager
{
    // Rest of class omitted

    BaseEntity GetEntity(string id)
    {
        return registry[id];
    }
}

Factoryメソッド内からComponentManagerを参照するために必要なのはそれだけです。例えば:

BaseEntity CreateSomeSortOfEntity(EntityManager manager)
{
    // Create and configure entity
    BaseEntity entity = new BaseEntity();
    RenderComponent renderComponent = new RenderComponent();
    entity.AddComponent(renderComponent);

    // Get a reference to the render manager and register component
    RenderEntityManager renderer = manager.GetEntity("RenderEntityManager") as RenderEntityManager;
    if(renderer != null)
        renderer.Register(renderComponent)

    return entity;
}

Idの他に、ある種のTypeプロパティ(カスタム列挙型、または単に言語の型システムに依存する)を使用して、特定の型のすべてのBaseEntitiesを返すゲッターを作成することもできます。


1
教訓的ではありませんが、再び...純粋なエンティティ(リレーショナル)システムでは、エンティティはそのコンポーネントによって付与されるものを除き、タイプを持ちません…
BRPocock

@BRPocock:純粋な美徳に沿った例を作成できますか?
ゾロモン

1
@Raineおそらく、私はこれを直接体験したことはありませんが、それは私が読んだことです。また、コンポーネントをIDで検索する時間を短縮するために実装できる最適化があります。キャッシュの一貫性に関しては、特にコンポーネントが軽量または単純なプロパティである場合、同じタイプのデータをメモリに連続して格納しているため、それは理にかなっていると思います。PS3での1つのキャッシュミスは、1,000 CPU命令と同じくらい高価になる可能性があり、同様のタイプのデータを連続して格納するこのアプローチは、現代のゲーム開発で非常に一般的な最適化手法です。
デヴィッドゴーベイア

2
参照:「純粋な」エンティティシステム:エンティティIDは通常次のようなものですtypedef unsigned long long int EntityID;。理想は、各システムが個別のCPUまたはホスト上に存在し、そのシステムに関連する/アクティブなコンポーネントのみを取得する必要があることです。Entityオブジェクトでは、各ホストで同じEntityオブジェクトをインスタンス化する必要があり、スケーリングがより難しくなります。純粋なエンティティコンポーネントシステムモデルでは、通常、エンティティではなくシステムごとにノード(プロセス、CPU、またはホスト)で処理が分割されます。
BRPocock

1
@DavidGouveiaは、「最適化... IDによるエンティティの検索」に言及しました。実際、この方法で実装した(少数の)システムは、そうしない傾向があります。多くの場合、特定のシステムに関心があることを示すパターンでコンポーネントを選択し、コンポーネント間結合にのみエンティティ(ID)を使用します。
BRPocock
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.