基本的なシステム、コンポーネント、エンティティのアプローチから始めます。
コンポーネントのタイプに関する情報だけから集合(この記事から派生した用語)を作成しましょう。これは、エンティティにコンポーネントを1つずつ追加/削除するのと同じように、実行時に動的に行われますが、タイプ情報のみを対象としているため、より正確に名前を付けましょう。
次に、それらすべての集合を指定するエンティティを作成します。エンティティを作成すると、その組み合わせは不変です。つまり、その場で直接変更することはできませんが、ローカルコピーへの既存のエンティティの署名を(コンテンツとともに)取得し、適切に変更して、新しいエンティティを作成できます。それの。
ここで重要な概念について説明します。エンティティが作成されると、それは常にassemblage bucketというオブジェクトに割り当てられます。つまり、同じ署名のすべてのエンティティが同じコンテナ(例:std :: vector)に置かれます。
現在、システムは関心のあるすべてのバケットを反復処理し、その仕事をしています。
このアプローチにはいくつかの利点があります。
- コンポーネントは少数(正確にはバケット数)の連続したメモリチャンクに格納されます-これによりメモリの使いやすさが向上し、ゲーム全体の状態をダンプするのが簡単になります
- システムはコンポーネントを線形的に処理します。つまり、キャッシュの一貫性が向上します。さようなら辞書とランダムメモリジャンプ
- 新しいエンティティの作成は、アセンブリをバケットにマッピングし、必要なコンポーネントをそのベクトルにプッシュバックするのと同じくらい簡単です
- エンティティの削除は、std :: moveを1回呼び出して最後の要素を削除された要素と交換するのと同じくらい簡単です。現時点では順序は関係ないためです。
完全に異なるシグネチャを持つ多くのエンティティがある場合、キャッシュコヒーレンシの利点はある程度減少しますが、ほとんどのアプリケーションでは発生しないと思います。
ベクトルが再割り当てされると、ポインターの無効化にも問題があります。これは、次のような構造を導入することで解決できます。
struct assemblage_bucket {
struct entity_watcher {
assemblage_bucket* owner;
entity_id real_index_in_vector;
};
std::unordered_map<entity_id, std::vector<entity_watcher*>> subscribers;
//...
};
そのため、ゲームロジックの何らかの理由で、新しく作成されたエンティティを追跡したいときはいつでも、バケット内にentity_watcherを登録し、エンティティを削除中にstd :: moveする必要がある場合は、そのウォッチャーをルックアップして更新しますそれらreal_index_in_vector
を新しい値に。ほとんどの場合、これはエンティティの削除ごとに1回の辞書検索を課します。
このアプローチには他に不利な点はありますか?
なぜ明白なのに、なぜ解決策がどこにも言及されていないのですか
編集:コメントが不十分であるため、「回答に答える」ために質問を編集しています。
静的なクラスの構築を回避するために特別に作成された、プラグ可能なコンポーネントの動的な性質を失います。
私はしません。多分私はそれを十分に明確に説明しなかった:
auto signature = world.get_signature(entity_id); // this would just return entity_id.bucket_owner->bucket_signature or so
signature.add(foo_component);
signature.remove(bar_component);
world.delete_entity(entity_id); // entity_id would hold information about its bucket owner
world.create_entity(signature); // automatically assigns new entity to an existing or a new bucket
これは、既存のエンティティの署名を取得して変更し、新しいエンティティとして再度アップロードするだけの簡単なものです。プラグイン可能な動的な性質?もちろん。ここでは、「アセンブリ」クラスと「バケット」クラスが1つしかないことを強調しておきます。バケットはデータ駆動型で、実行時に最適な量で作成されます。
有効なターゲットが含まれている可能性のあるすべてのバケットを通過する必要があります。外部データ構造がないと、衝突検出も同様に困難になります。
これが、前述の外部データ構造がある理由です。回避策は、次のバケットにジャンプするタイミングを検出するイテレータをSystemクラスに導入するのと同じくらい簡単です。ジャンプはロジックに純粋に透明になります。