エンティティとコンポーネントにメソッドを保存するのはなぜ悪い考えですか?(他のいくつかのエンティティシステムの質問と共に。)


16

これは私が答えたこの質問のフォローアップですが、これはより具体的なテーマに取り組んでいます。

この答えは、記事よりもエンティティシステムをよりよく理解するのに役立ちました。

エンティティシステムに関する(はい)記事を読みましたが、次のように伝えられました。

エンティティは単なるidとコンポーネントの配列です(記事では、コンポーネントにエンティティを保存することは物事を行う良い方法ではないと述べていますが、代替手段は提供していません)。
コンポーネントは、特定のエンティティで何ができるかを示すデータの断片です。
システムは「メソッド」であり、エンティティのデータの操作を実行します。

これは多くの状況で実際に実用的と思われますが、コンポーネントが単なるデータクラスであるという部分が気になります。たとえば、Entity SystemにVector2Dクラス(Position)を実装するにはどうすればよいですか?

Vector2Dクラスはデータを保持します:x座標とy座標ですが、その有用性に重要なメソッドもあり、クラスを2つの要素配列から区別します。:例の方法がありadd()rotate(point, r, angle)substract()normalize()、および他のすべての標準的な、便利な、と(Vector2Dクラスのインスタンスである)の位置が持つべきであることが絶対に必要なメソッド。

コンポーネントが単なるデータホルダーである場合、これらのメソッドを持つことはできません!

おそらくポップアップする可能性のあるソリューションの1つは、システム内に実装することですが、それは非常に直感に反するようです。これらのメソッドは、私が実行したいものであり、完全で使用可能な状態になっています。MovementSystemエンティティの位置の計算を実行するように指示する高価なメッセージセットを読み取るのを待ちたくありません!

そして、記事は非常にはっきりと述べているだけのシステムが持つべきすべての機能を、私は見つけることができるそのための唯一の説明は、「OOPを避けるため」でした。まず第一に、エンティティやコンポーネントでメソッドを使用することを控えるべき理由がわかりません。メモリのオーバーヘッドは実質的に同じであり、システムと組み合わせた場合、これらは非常に簡単に実装し、興味深い方法で組み合わせる必要があります。たとえば、システムは、実装自体を知っているエンティティ/コンポーネントにのみ基本的なロジックを提供できます。あなたが私に尋ねると-これは基本的にESとOOPの両方から利点を取ります。これは記事の著者によるとできないことですが、私にとっては良い習慣のようです。

このように考えてください。ゲームにはさまざまな種類の描画可能なオブジェクトがあります。昔ながらの画像、アニメーション(update()getCurrentFrame()など)、これらのプリミティブ型の組み合わせ、およびそれらのすべては、単純に提供できるdraw()だけで、その後、エンティティのスプライトがどのように実装されるかを気にする必要はありませんレンダリングシステムに方法をインターフェース(描画)と位置について。そして、レンダリングとは関係のないアニメーション固有のメソッドを呼び出すアニメーションシステムのみが必要になります。

そして、もう1つだけ...コンポーネントの保存に関して、配列の代替物は本当にありますか?Entityクラス内の配列以外にコンポーネントを保存する場所は他にありません...

たぶん、これはより良いアプローチです:コンポーネントをエンティティの単純なプロパティとして保存します。たとえば、位置コンポーネントはに接着されentity.positionます。

他の唯一の方法は、異なるエンティティを参照する、システム内にある種の奇妙なルックアップテーブルを持つことです。しかし、それは非常に非効率的で、単にコンポーネントをエンティティに格納するよりも開発が複雑に思えます。


アレクサンドルは、あなただけの別のバッジを取得するために多くの編集を行っていますか?それはいたずらないたずらなので、それは大量の古代の糸をぶつけ続けます。
ジョッキング

回答:


25

コンポーネント内のデータにアクセス、更新、または操作するための簡単なメソッドを用意することはまったく問題ないと思います。コンポーネントにとどまらない機能は論理的な機能だと思います。ユーティリティ関数は問題ありません。エンティティコンポーネントシステムは単なるガイドラインであり、従う必要のある厳密なルールではないことに注意してください。彼らに従うためにあなたの邪魔をしないでください。あなたがそれを1つの方法で行う方が理にかなっていると思うなら、その方法でそれをしてください:)

編集

明確にするために、あなたはOOPを避けないことが目標です。これは、最近使用されているほとんどの一般的な言語ではかなり難しいでしょう。継承を最小化しようとしていますが、これはOOPの大きな側面ですが、必須ではありません。Object-> MobileObject-> Creature-> Bipedal-> Human type inheritanceを取り除きたい。

ただし、いくつかの継承があれば大丈夫です!継承の影響を強く受ける言語を扱っている場合、そのいずれも使用しないことは非常に困難です。たとえばComponent、他のすべてのコンポーネントが拡張または実装するクラスまたはインターフェイスを使用できます。Systemクラスでも同じです。これにより、作業が非常に簡単になります。私は強くあなたが見て取るお勧めアルテミスの枠組みを。オープンソースであり、いくつかのサンプルプロジェクトがあります。それらを開いて、それがどのように機能するかを確認してください。

Artemisの場合、エンティティは単純な配列に格納されます。ただし、それらのコンポーネントは1つまたは複数の配列(エンティティとは別)に格納されます。最上位レベルの配列は、コンポーネントタイプごとに下位レベルの配列をグループ化します。したがって、各コンポーネントタイプには独自の配列があります。下位レベルの配列には、エンティティIDによってインデックスが付けられます。(今、私はそれをそのように行うかどうかはわかりませんが、それはここで行われている方法です)。ArtemisはエンティティIDを再利用するため、最大エンティティIDは現在のエンティティ数よりも大きくなりませんが、コンポーネントが頻繁に使用されるコンポーネントでない場合は、まばらな配列を持つことができます。とにかく、私はあまり離れてそれを選びません。エンティティとそのコンポーネントを保存するこの方法は機能しているようです。あなた自身のシステムを実装する上で、素晴らしい最初のパスになると思います。

エンティティとコンポーネントは別のマネージャーに保存されます。

エンティティに独自のコンポーネントを保存させるという戦略(entity.position)は、エンティティコンポーネントのテーマに反するものですが、それが最も理にかなっていると感じれば、まったく受け入れられます。


1
うーん、状況が大幅に簡素化されました、ありがとう!「後で後悔する」という魔法が起こっていると思ったのですが、見えませんでした!
jcora

1
Na、エンティティコンポーネントシステムで完全に使用します。共通の親であるgaspから継承するコンポーネントもあります。後悔するのは、そのような方法を使わずに回避しようとした場合だけだと思います。あなたにとって最も意味のあることをすることがすべてです。継承を使用するか、コンポーネントにメソッドを配置することが理にかなっている場合は、それを選択します。
マイケルハウス

2
このテーマに関する最後の答えから学びました。免責事項:これがその方法だと言っているのではありません。:)
マイケルハウス

1
ええ、新しいパラダイムを学ぶことがどれほど大変なことか知っています。幸いなことに、古いパラダイムの側面を使用して物事を簡単にすることができます!ストレージ情報で回答を更新しました。Artemisを見るとEntityManager、物が保管されている場所を確認してください。
マイケルハウス

1
いいね!それが完了したら、それはかなり甘いエンジンになります。頑張ってください!興味深い質問をしてくれてありがとう。
マイケルハウス

10

「その」記事は私が特に同意するものではないので、私の答えはやや批判的なものになると思います。

これは多くの状況で実際に実用的と思われますが、コンポーネントが単なるデータクラスであるという部分が気になります。たとえば、Entity SystemにVector2Dクラス(Position)を実装するにはどうすればよいですか?

プログラムの中でエンティティID、コンポーネント、またはシステム以外のものが存在しないことを保証するのではなく、複雑な継承ツリーを使用したり、さらに悪いことに可能なすべての機能を1つのオブジェクトに入れます。これらのコンポーネントおよびシステムを実装するには、ほとんどの言語でクラスとして最も適切に表現されるベクトルのような通常のデータが確実にあります。

これがOOPではないことを示唆している記事のビットは無視してください。これは、他のアプローチと同じようにOOPです。ほとんどのコンパイラまたは言語ランタイムがオブジェクトメソッドを実装するとき、それは基本的に他の関数と同じです。ただし、thisまたはと呼ばれる隠し引数selfがあります。コンポーネントベースのシステムでは、エンティティIDを使用して、特定のエンティティの関連コンポーネント(およびデータ)の場所を見つけることができます。したがって、エンティティIDはthis / selfポインターと同等であり、概念は基本的に同じもので、わずかに再配置されています。

また、この記事には、システムにのみ機能が必要であることが明確に記載されており、そのための唯一の説明は「OOPを回避する」ことでした。まず第一に、エンティティやコンポーネントでメソッドを使用することを控えるべき理由がわかりません。

良い。メソッドは、コードを整理する効果的な方法です。「OOPを回避する」という考え方から遠ざける重要なことは、機能を拡張するためにどこでも継承を使用することを避けることです。代わりに、機能をコンポーネントに分割し、それらを組み合わせて同じことを行うことができます。

このように考えてください。ゲームにはさまざまな種類の描画可能なオブジェクトがあります。単純な古い画像、アニメーション(update()、getCurrentFrame()など)、これらのプリミティブ型の組み合わせ、およびそれらのすべては、単にレンダーシステムにdraw()メソッドを提供できます[...]

コンポーネントベースのシステムの考え方は、これらに個別のクラスを持たず、単一のオブジェクト/エンティティクラスを持ち、画像はImageRendererを持つオブジェクト/エンティティであり、アニメーションはオブジェクト/ AnimationRendererなどを持つエンティティ。関連するシステムはこれらのコンポーネントをレンダリングする方法を知っているため、Draw()メソッドを持つ基本クラスは必要ありません。

[...]これにより、エンティティのスプライトがどのように実装されるかを気にする必要がなくなり、インターフェース(描画)と位置のみが気になります。そして、レンダリングとは関係のないアニメーション固有のメソッドを呼び出すアニメーションシステムのみが必要になります。

もちろんですが、これはコンポーネントではうまく機能しません。次の3つの選択肢があります。

  • すべてのコンポーネントはこのインターフェイスを実装し、何も描画されない場合でもDraw()メソッドを備えています。すべての機能についてこれを行うと、コンポーネントは非常に見苦しくなります。
  • 描画するものがあるコンポーネントのみがインターフェースを実装します-しかし、誰がDraw()を呼び出すコンポーネントを決定しますか?システムは、サポートされているインターフェイスを確認するために、何らかの方法で各コンポーネントを照会する必要がありますか?これはエラーが発生しやすく、一部の言語では実装が難しい場合があります。
  • コンポーネントは、所有システムによってのみ処理されます(リンクされた記事の考え方です)。この場合、システムはどのクラスまたはオブジェクトタイプを使用しているかをシステムが正確に認識しているため、インターフェイスは無関係です。

そして、もう1つだけ...コンポーネントの保存に関して、配列の代替物は本当にありますか?Entityクラス内の配列以外にコンポーネントを保存する場所は他にありません...

システムにコンポーネントを保存できます。配列は問題ではありませんが、コンポーネントを保存する場所は問題です。


+1別の視点をありがとう。このようなあいまいな主題を扱うとき、いくつかを得るのは良いことです!システムにコンポーネントを保存している場合、それはコンポーネントが1つのシステムでしか変更できないことを意味しますか?たとえば、描画システムと移動システムの両方が位置コンポーネントにアクセスします。どこに保管しますか?
マイケルハウス

まあ、それらはそれらのコンポーネントへのポインタのみを保存しますが、それは私がどこかに関係している限り可能です...また、なぜコンポーネントをシステムに保存するのですか?それに利点はありますか?
jcora

私は正しいですか、@ Kylotan?それは...私はそれを行うだろうかだ、それは論理的なようだ
jcora

Adam / T-Machineの例では、コンポーネントごとに1つのシステムがあることを意図していますが、システムは他のコンポーネントに確実にアクセスして変更できます。(これは、コンポーネントのマルチスレッド化の利点を妨げますが、それは別の問題です。)
Kylotan

1
システムにコンポーネントを保存すると、そのシステムの参照の局所性が向上します-そのシステムは(一般的に)そのデータでのみ動作します。また、システム全体とそのデータを1つのコアまたはプロセッサ(またはMMOの別のコンピューター)に配置できるため、同時実行性にも役立ちます。繰り返しますが、これらの利点は、1つのシステムが複数のタイプのコンポーネントにアクセスするときに減少するため、コンポーネント/システムの責任をどこで分割するかを決定する際に考慮する必要があります。
キロタン

2

ベクトルはデータです。関数はユーティリティ関数に似ています-データのインスタンスに固有ではなく、すべてのベクトルに個別に適用できます。それについての素晴らしい考え方は次のとおりです。これらの関数は静的メソッドとして書き直すことができますか?もしそうなら、それは単なるユーティリティです。


私はそれを知っていますが、問題はメソッドの呼び出しが速く、システム、またはエンティティの位置を操作する必要があるかもしれない他のものによってその場で実行できることです。私はそれを説明しました、それをチェックしてください、そして、質問はこれだけではありません、私は信じています。
jcora
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.