エンティティーコンポーネントシステムは、デカップリング/情報隠蔽のためにひどいものではありませんか?


11

タイトルは意図的に双曲線であり、それは単にパターンに不慣れなだけかもしれませんが、ここに私の推論があります:

エンティティを実装する「通常の」またはほぼ間違いなく簡単な方法は、それらをオブジェクトとして実装し、共通の動作をサブクラス化することです。古典的な問題へのこのリードは、「あるEvilTreeのサブクラスTreeEnemy?」。多重継承を許可すると、ダイヤモンドの問題が発生します。私たちは、代わりの複合機能を引く可能性TreeEnemy、さらにその神クラスへのリード線の階層までを、あるいは我々は意図的に私たちの中での行動を残すことができますTreeし、Entityそのことをクラス(彼らは極端なケースではインターフェースを作る)EvilTree自体ことを実装することができます-どのリードにコードの重複がある場合SomewhatEvilTree

Entity-Component Systemsは、TreeおよびEnemyオブジェクトを異なるコンポーネント(たとえばPositionHealthおよび)に分割してこの問題を解決し、AIの決定に従ってEntitiyの位置を変更するAIシステムなどを実装しようとしますAISystem。これまでのところは良いですがEvilTree、パワーアップを獲得してダメージを与えることができたらどうでしょうか?まず、a CollisionSystemとa が必要ですDamageSystem(おそらくこれらはすでにあるでしょう)。CollisionSystem通信するために必要なDamageSystem二つのものが衝突するたび:CollisionSystemにメッセージを送信しDamageSystem、それが健康を引くことができるようにします。ダメージもパワーアップの影響を受けるため、どこかに保存する必要があります。PowerupComponentエンティティにアタッチする新しいものを作成しますか?しかし、その後DamageSystemむしろ何も知らない何かについて知る必要があります-結局のところ、パワーアップを拾えないダメージを与えるものもあります(aなどSpike)。この回答と同様のダメージ計算にも使用されるPowerupSystemを修正するStatComponentことはできますか?しかし、現在では2つのシステムが同じデータにアクセスしています。ゲームがより複雑になると、多くのシステム間でコンポーネントが共有される無形の依存関係グラフになります。その時点で、グローバルな静的変数を使用して、すべての定型文を取り除くことができます。

これを解決する効果的な方法はありますか?私が持っていた1つのアイデアは、コンポーネントに特定の機能を持たせることでした。たとえばStatComponent attack()、デフォルトでは整数を返すだけですが、パワーアップが発生したときに構成できます:

attack = getAttack compose powerupBy(20) compose powerdownBy(40)

これはattack、複数のシステムがアクセスするコンポーネントに保存しなければならない問題を解決しませんが、少なくともそれを十分にサポートする言語があれば、関数を適切に入力できます。

// In StatComponent
type Strength = PrePowerup | PostPowerup
type Damage = Int
type PrePowerup = Int
type PostPowerup = Int
attack: Strength = getAttack //default value, can be changed by systems
getAttack: PrePowerup

// these functions can be defined in other components or in PowerupSystems
powerupBy: Strength -> PostPowerup
powerdownBy: Strength -> PostPowerup
subtractArmor: Strength -> Damage

// in DamageSystem
dealDamage: Damage -> () = attack compose subtractArmor compose hurtSomeEntity

この方法で、少なくともシステムによって追加されたさまざまな機能の正しい順序を保証します。いずれにせよ、私はここで関数型リアクティブプログラミングに急速に近づいているようですので、代わりに最初からそれを使用するべきではなかったかどうかを自問します(FRPを調べただけなので、ここで間違っているかもしれません)。ECSは複雑なクラス階層よりも改善されていると思いますが、それが理想だとは思いません。

これを解決する方法はありますか?ECSをよりきれいに分離するために欠けている機能/パターンはありますか?FRPはこの問題に厳密に適していますか?これらの問題は、私がプログラムしようとしているものの固有の複雑さから生じているのでしょうか。つまり、FRPには同様の問題がありますか?



Ericのブログが本当に恋しいです(C#の頃から)。
-OldFart

回答:


21

ECSはデータの非表示を完全に台無しにします。これはパターンのトレードオフです。

ECSはデカップリングに優れています。優れたECSを使用すると、移動システムは、速度と位置のコンポーネントを持つエンティティで動作することを宣言できます。エンティティタイプの存在や、これらのコンポーネントにアクセスする他のシステムを気にする必要はありません。これは、少なくとも、ゲームオブジェクトに特定のインターフェイスを実装させることとパワー​​を分離することで同等です。

同じコンポーネントにアクセスする2つのシステムは機能であり、問​​題ではありません。これは完全に期待されており、システムをどのようにも結合しません。システムに暗黙的な依存関係グラフがあるのは事実ですが、それらの依存関係はモデル化された世界に固有のものです。ダメージシステムがパワーアップシステムに暗黙的に依存してはならないということは、パワーアップがダメージに影響を与えないと主張することであり、おそらく間違いです。ただし、依存関係は存在しますが、システムは結合されていません。通信はstatコンポーネントを介して行われ、完全に暗黙的であるため、ダメージシステムに影響を与えることなくパワーアップシステムをゲームから削除できます。

これらの依存関係の解決とシステムの注文は、DIシステムでの依存関係の解決方法と同様に、単一の中央ロケーションで実行できます。はい、複雑なゲームにはシステムの複雑なグラフがありますが、この複雑さは固有のものであり、少なくとも含まれています。


7

システムが複数のコンポーネントにアクセスする必要があるという事実を回避する方法はほとんどありません。VelocitySystemのようなものが機能するためには、おそらくVelocityComponentとPositionComponentにアクセスする必要があります。一方、RenderingSystemもこのデータにアクセスする必要があります。何をするにしても、ある時点でレンダリングシステムはオブジェクトをレンダリングする場所を知る必要があり、VelocitySystemはオブジェクトをどこに移動するかを知る必要があります。

これに必要なのは、依存関係の明示性です。各システムは、読み取るデータと書き込むデータについて明示する必要があります。システムが特定のコンポーネントを取得する場合、明示的にのみこれを実行できる必要があります。最も単純な形式では、必要な各タイプのコンポーネント(RenderSystemにはRenderComponentsとPositionComponentsが必要)を引数として持つだけで、変更されたもの(RenderComponentsのみ)を返します。

このようにして、少なくともシステムによって追加されたさまざまな機能の正しい順序を保証します

このようなデザインで注文することができます。ECSの場合、システムが順序やそのようなものから独立していなければならないということはありません。

FRPはこの問題に厳密に適していますか?これらの問題は、私がプログラムしようとしているものの固有の複雑さから生じているのでしょうか。つまり、FRPには同様の問題がありますか?

このEntity-component-systemデザインとFRPの使用は、相互に排他的ではありません。実際、システムは、データ変換(コンポーネント)を実行するだけで、状態を持たないものと見なすことができます。

FRPは、何らかの操作を実行するために必要な情報を使用する必要があるという問題を解決しません。

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