これは難しい問題です。私の特定の経験(YMMV)に基づいて、いくつかの質問に取り組みます。
コンポーネントは他のコンポーネントのデータにアクセスする必要があります。たとえば、renderコンポーネントのdrawメソッドは、transformコンポーネントの位置にアクセスする必要があります。これにより、コードに依存関係が作成されます。
ここでは、カップリング/依存関係の量と複雑さ(程度ではなく)を過小評価しないでください。あなたはこれの間の違いを見ているかもしれません(そしてこの図はすでにおもちゃのようなレベルに途方もなく単純化されています、そして現実の例は結合を緩めるためにその間にインターフェースを持っています):
... この:
...またはこれ:
コンポーネントはポリモーフィックになる可能性があり、さらに複雑さをもたらします。たとえば、レンダーコンポーネントの仮想描画メソッドをオーバーライドするスプライトレンダーコンポーネントがある場合があります。
そう?vtableおよび仮想ディスパッチに類似する(またはリテラル)同等物は、オブジェクトがその基本的な状態/データを隠すのではなく、システムを介して呼び出すことができます。ポリモーフィズムは、類似したvtableまたは関数ポインターがシステムが呼び出す「データ」のような種類の「データ」に変わるとき、「純粋な」ECS実装でまだ非常に実用的で実現可能です。
ポリモーフィックな動作(レンダリングなど)はどこかに実装する必要があるため、システムに外部委託するだけです。(たとえば、スプライトレンダーシステムは、レンダーノードを継承するスプライトレンダーノードを作成し、それをレンダーエンジンに追加します)
そう?私はこれが皮肉として脱落しないことを望みます(私は頻繁に非難されてきましたが、私の意図ではありませんが、テキストを通じて感情をよりよく伝えることができればと思います)。生産性へのコスト。
システム間の通信は避けるのが難しい場合があります。たとえば、衝突システムには、そこにある具体的なレンダリングコンポーネントから計算される境界ボックスが必要な場合があります。
この例は私には特に奇妙に思えます。レンダラーがシーンにデータを出力する理由(私は通常、このコンテキストではレンダラーを読み取り専用と見なします)、またはレンダラーが他のシステムではなくAABBを把握して、レンダラーと衝突/物理(ここで「レンダーコンポーネント」の名前に引っかかるかもしれません)。それでも、この例にこだわりすぎないようにしたいのは、それがあなたが意図していることではないということです。それでも、システム間の通信(システムが他のユーザーによって行われた変換に直接依存している中央のECSデータベースへの間接的な読み取り/書き込みの形式であっても)は、必要であれば頻繁に行う必要はありません。それ'
システムの更新関数を呼び出す順序が定義されていないと、問題が発生する可能性があります。
これは絶対に定義する必要があります。ECSは、コードベース内のすべての可能なシステムのシステム処理評価順序を再配置し、フレームとFPSを扱うエンドユーザーにまったく同じ種類の結果を返すための最終的なソリューションではありません。これは、ECSを設計する際に、少なくとも事前にある程度予想する必要があることを強くお勧めします(ただし、後で変更するための呼吸の余裕がたくさんある場合は、順序の最も重要な側面が変更されない限り)システムの呼び出し/評価)。
ただし、フレームごとにタイルマップ全体を再計算するにはコストがかかります。したがって、システムでそれらを更新するために行われたすべての変更を追跡するために、リストが必要になります。OOPの方法では、これはタイルマップコンポーネントによってカプセル化できます。たとえば、SetTile()メソッドは、呼び出されるたびに頂点配列を更新します。
これはデータ指向の問題であることを除いて、私はこれを完全には理解していませんでした。また、このようなパフォーマンスの落とし穴を回避するために、メモ化を含むECSでのデータの表現と保存に関する落とし穴はありません(ECSの最大の落とし穴は、一般化されたECSの最適化の最も困難な側面)。ロジックとデータが「純粋な」ECSで分離されているという事実は、そうでなければOOP表現でキャッシュ/メモ化できたであろうものを突然再計算しなければならないという意味ではありません。これは、私が非常に重要なことをざっと見ない限り、重要な/無関係なポイントです。
「純粋な」ECSを使用しても、このデータをタイルマップコンポーネントに格納できます。唯一の大きな違いは、この頂点配列を更新するロジックがどこかのシステムに移動することです。
のような別のコンポーネントを作成する場合は、ECSを利用して、エンティティからのこのキャッシュの無効化と削除を簡素化することもできますTileMapCache
。キャッシュが必要であるが、TileMap
コンポーネントを含むエンティティでは使用できない時点で、キャッシュを計算して追加できます。無効化または不要になった場合は、ECSを介して削除できます。そのような無効化と削除のために特別にコードを記述する必要はありません。
コンポーネント間での依存関係は、システムに隠されていますが、まだ存在しています
「純粋な」担当者のコンポーネント間に依存関係はありません(依存関係がシステムによってここに隠されていると言っても、私はまったく正しいとは思いません)。データは、いわばデータに依存していません。ロジックはロジックに依存します。また、「純粋な」ECSは、システムが動作するために必要なデータとロジックの絶対最小サブセット(多くの場合はなし)に依存するような方法でロジックを作成する傾向があります。実際のタスクに必要な機能よりはるかに多くの機能。純粋なECS権限を使用している場合、最初に理解する必要があるのは、デカップリングの利点であると同時に、カプセル化、特に情報の非表示についてOOPで理解したことすべてに疑問を投げかけることです。
デカップリングとは、具体的には、システムが機能するために必要な情報がほとんどないことを意味します。モーションシステムは、a Particle
やのようなはるかに複雑なものについて知る必要さえありませんCharacter
(システムの開発者は、必ずしもそのようなエンティティのアイデアがシステムに存在することさえ知る必要さえありません)。構造体の少数のフロートと同じくらい簡単な位置コンポーネントのような最小限のデータについて知る必要があるだけです。それは、純粋なインターフェースIMotion
がそれと一緒に運ぶ傾向があるものよりもさらに少ない情報と少ない外部依存です。これは主に、各システムが動作するために必要な最小限の知識によるものであり、ECSを非常に予期せぬ設計変更に後から対応することで、あらゆる場所でカスケードインターフェースの破損に直面することがありません。
変更が連鎖的な破損を引き起こさないシステムにロジックが厳密にローカライズされていないため、提案する「純粋でない」アプローチはその利点をいくらか減少させます。ロジックは、複数のシステムによってアクセスされるコンポーネントにある程度集中化されます。コンポーネントは、それを使用できるすべてのさまざまなシステムのインターフェース要件を満たす必要があり、すべてのシステムがより多くの(依存する)知識を持つ必要があるようになりました。それが厳密にそのコンポーネントで動作する必要があるよりも情報。
データへの依存
ECSについて論争の的となっていることの1つは、抽象インターフェースへの依存関係である可能性のあるものを、生データだけで置き換える傾向があることです。これは一般に、あまり望ましくなく、より緊密な形式の結合と見なされます。しかし、ECSが非常に有益なゲームのような種類のドメインでは、多くの場合、システムの中央レベルでそのデータを使用して実行できることを設計するよりも、データ表現を事前に設計して安定性を保つ方が簡単です。これは、のようなCOMスタイルの純粋なインターフェイスアプローチをより多く利用するコードベースのベテランのベテランの間でさえ、私が辛抱強く観察したものですIMotion
。
開発者は、この中央インターフェイスの機能を追加、削除、または変更する理由を見つけ続けました。変更はIMotion
、使用されたシステム内のすべての以降の場所とともに実装されたすべての単一のクラスを破壊する傾向があるため、非常にコストがかかりましたIMotion
。その間ずっと、非常に多くの痛みを伴うカスケード型の変更がありましたが、実装さIMotion
れたオブジェクトはすべて、フロートの4x4マトリックスを格納するだけであり、インターフェイス全体は、それらのフロートの変換方法とアクセス方法に関係していました。データ表現は最初からずっと安定しており、この集中化されたインターフェースは、予期しない設計のニーズによって変更される傾向があり、そもそも存在しなければ、多くの苦痛を回避できたはずです。
これはすべて、グローバル変数と同じくらい嫌なことですが、ECSがこのデータをシステムを通じてタイプによって明示的に取得されるコンポーネントに編成する方法の性質により、そうすることができますが、コンパイラーは、情報の非表示、アクセスおよび変更する場所などを強制できませんデータは一般に非常に明示的で明白であり、不変式を効果的に維持し、システム間でどのような変換と副作用が発生するかを予測します(実際には、特定のドメインでOOPよりも間違いなく単純で予測可能な方法でシステムはフラットな種類のパイプラインに変わります)。
最後に、純粋なECSでアニメーションをどのように処理するかについて質問したいと思います。現在、私はアニメーションを、0と1の間の進行状況に基づいてエンティティを操作するファンクタとして定義しています。アニメーションコンポーネントには、アニメーションのリストを持つアニメーターのリストがあります。次に、更新機能で、現在アクティブなアニメーションをエンティティに適用します。
私たちはみなここで実用主義者です。gamedevでさえ、おそらく矛盾するアイデア/答えを得るでしょう。最も純粋なECSでさえ比較的新しい現象であり、先駆的な領域であり、人々が猫の皮を剥く方法について最も強い意見を必ずしも形成しているわけではありません。私の直感的な反応は、レンダリングシステムが表示するアニメーションコンポーネントのこの種のアニメーションの進行を増分するアニメーションシステムですが、それは特定のアプリケーションとコンテキストの多くのニュアンスを無視しています。
ECSを使用してもそれは特効薬ではありません。新しいシステムを追加したり、いくつかを削除したり、新しいコンポーネントを追加したり、既存のシステムを変更してその新しいコンポーネントタイプを取得したりする傾向があります。物事はまだ最初はまだ正しい。しかし、私の場合の違いは、特定のデザインニーズを事前に予測できない場合、中心的な変更は行わないことです。私はあちこちに行き、新しいニーズに対処するために多くのコードを変更する必要があるカスケード破損の波及効果を取得していません。これはかなりの時間の節約になります。特定のシステムに腰を下ろすと、それに関連するコンポーネント(単なるデータ)以外のことについて何も知る必要がないので、頭がより楽になります。