質問

(Android用)私が作成している2Dゲームでは、GameObjectがいくつかのゲームコンポーネントオブジェクトを保持するコンポーネントベースのシステムを使用しています。 GameComponentsは、入力コンポーネント、レンダリングコンポーネント、弾丸エミッティングコンポーネントなどのものです。現在、GameComponentsにはそれらを所有して変更できるオブジェクトへの参照がありますが、GameObject自体にはコンポーネントのリストがあり、オブジェクトが更新されたときに更新できる限り、コンポーネントが何であるかを気にしません。

コンポーネントには、GameObjectが知る必要がある情報がある場合があります。たとえば、衝突検出の場合、GameObjectは衝突検出サブシステムとともにそれ自体を登録し、別のオブジェクトと衝突するときに通知されます。衝突検出サブシステムは、オブジェクトの境界ボックスを知る必要があります。 XとYをオブジェクトに直接保存します(いくつかのコンポーネントで使用されているため)が、幅と高さは、オブジェクトのビットマップを保持するレンダリングコンポーネントに対してのみ知られています。その情報を取得するGameObjectのメソッドGetBoundingBoxまたはGetWidthが欲しいです。または、一般的に、コンポーネントからオブジェクトに情報を送信したいと思います。ただし、私の現在のデザインでは、GameObjectはリストにどのような特定のコンポーネントがあるかを知りません。

この問題を解決するためのいくつかの方法を考えることができます。

  1. コンポーネントの完全な汎用リストを持つ代わりに、GameObjectに重要なコンポーネントの一部に特定のフィールドを持たせることができます。たとえば、RenderingComponentと呼ばれるメンバー変数を持つことができます。オブジェクトの幅を取得する必要があるときはいつでも使用します renderingComponent.getWidth(). 。このソリューションは依然としてコンポーネントの一般的なリストを可能にしますが、それらの一部を異なって処理します。そして、より多くのコンポーネントを照会する必要があるため、いくつかの例外的なフィールドがあることになります。一部のオブジェクトには、レンダリングコンポーネントさえありません。

  2. GameObjectのメンバーとして必要な情報を用意しますが、コンポーネントの更新を許可します。したがって、オブジェクトの幅と高さはデフォルトで0または-1ですが、レンダリングコンポーネントは更新ループの正しい値に設定できます。これはハックのように感じられ、すべてのオブジェクトがそれらを必要としていなくても、利便性のために多くのことをGameObjectクラスにプッシュすることになるかもしれません。

  3. コンポーネントに、どのような種類の情報を照会できるかを示すインターフェイスを実装します。たとえば、レンダリングコンポーネントは、GetWidthやGetheightなどのメソッドを含むHassizeインターフェイスを実装します。 GameObjectが幅を必要とする場合、Hassizeインターフェイスを実装するかどうかを確認するコンポーネントにループします( instanceof Javaのキーワード、OR is C#)。これはより一般的なソリューションのように思えます。不利な点の1つは、コンポーネントを検索するには時間がかかる場合があることです(しかし、ほとんどのオブジェクトには3つまたは4つのコンポーネントのみがあります)。

この質問は特定の問題に関するものではありません。それは私のデザインで頻繁に出てきて、私はそれを処理するための最良の方法は何だろうと思っていました。これはゲームであるため、パフォーマンスはやや重要ですが、オブジェクトあたりのコンポーネントの数は一般に小さい(最大値は8)。

短いバージョン

ゲームのコンポーネントベースのシステムでは、デザインを一般的に保ちながら、コンポーネントからオブジェクトに情報を渡す最良の方法は何ですか?

役に立ちましたか?

解決

この質問のバリエーションは、gamedev.netで週に3、4回(ゲームオブジェクトは通常「エンティティ」と呼ばれます)。これまでのところ、最良のアプローチに関するコンセンサスはありません。いくつかの異なるアプローチが実行可能であることが示されているので、私はそれについてあまり心配することはありません。

ただし、通常、問題はコンポーネント間の通信を考慮しています。人々がコンポーネントからエンティティに情報を取得することを心配することはめったにありません - エンティティが必要な情報を知っている場合、おそらく、アクセスする必要があるコンポーネントの種類と、そのコンポーネントでどのプロパントまたは方法を呼び出す必要があるかを正確に知っていると思われます。データ。アクティブではなくリアクティブである必要がある場合は、コールバックを登録するか、コンポーネントにコンポーネントに何かが変更されたときにエンティティに知らせるためにオブザーバーパターンを設定し、その時点で値を読み取ります。

完全に一般的なコンポーネントはほとんど役に立たない。彼らは何らかの既知のインターフェイスを提供する必要があります。そうしないと、それらが存在することはほとんどありません。それ以外の場合は、無型の値の大規模な連想配列を持っているだけでなく、それを使用することもできます。 Java、Python、C#、およびその他のわずかに高いレベルの言語では、C ++よりも少し高いレベルの言語を使用して、リフレクションを使用して、コンポーネント自体にタイプ情報とインターフェイス情報をエンコードすることなく、特定のサブクラスを使用するより一般的な方法を提供できます。

コミュニケーションについては:

一部の人々は、エンティティには常に既知のコンポーネントタイプ(各インスタンスがいくつかの可能なサブクラスの1つである)が含まれるため、他のコンポーネントへの直接参照をつかみ、パブリックインターフェイスを介して読み取り/書き込みできると仮定しています。

一部の人々は、パブリッシュ/サブスクライブ、信号/スロットなどを使用して、コンポーネント間の任意の接続を作成しています。これはもう少し柔軟性があるように思えますが、最終的には、これらの暗黙の依存関係を知っている何かが必要です。 (そして、これがコンパイル時に知られている場合、なぜ以前のアプローチを使用しないのですか?)

または、エンティティ自体にすべての共有データを配置し、それを共有通信領域として使用することができます( ブラックボードシステム ai)各コンポーネントが読み取りおよび書き込みできること。これには、通常、特定のプロパティが予想していない場合に存在しないことに直面して、ある程度の堅牢性が必要です。また、それは並列性に役立ちませんが、それが小さな埋め込みシステムに対する大きな懸念であるとは思いません...?

最後に、一部の人々は、エンティティがまったく存在しないシステムを持っています。コンポーネントはサブシステム内に住んでおり、エンティティの唯一の概念は特定のコンポーネントのID値です - レンダリングコンポーネント(レンダリングシステム内)とプレーヤーコンポーネント(プレーヤーシステム内)が同じIDを持っている場合、あなたは想定できます。前者は後者の図面を処理します。ただし、これらのコンポーネントのいずれかを集約するオブジェクトはありません。

他のヒント

他の人が言ったように、ここに常に正しい答えはありません。さまざまなゲームは、さまざまなソリューションに向けられます。さまざまな種類のエンティティを備えた大きな複雑なゲームを構築する場合、コンポーネント間の何らかの抽象的なメッセージを備えたよりデカップされた一般的なアーキテクチャは、あなたが得る保守性のために努力する価値があるかもしれません。同様のエンティティを備えたよりシンプルなゲームの場合、その状態のすべてをGameObjectに押し上げるだけで最も理にかなっています。

境界ボックスをどこかに保存する必要があり、衝突コンポーネントのみがそれを気にかけている特定のシナリオのために、私は次のようにします。

  1. 衝突コンポーネント自体に保存します。
  2. 衝突検出コードをコンポーネントで直接動作させます。

したがって、衝突エンジンをGameObjectsのコレクションを介して繰り返して相互作用を解決する代わりに、衝突補償のコレクションを通じて直接反復します。衝突が発生すると、それを親GameObjectにプッシュするのはコンポーネント次第です。

これにより、いくつかの利点が得られます。

  1. GameObjectから衝突固有の状態を残します。
  2. 衝突コンポーネントがないGameObjectsを繰り返すことをお勧めします。 (視覚効果や装飾などの非相互作用オブジェクトがたくさんある場合、これによりまともな数のサイクルを節約できます。)
  3. オブジェクトとそのコンポーネントの間を歩くサイクルを燃やすことを控えます。オブジェクトを繰り返す場合は、実行します getCollisionComponent() それぞれに、そのポインターフォローはキャッシュミスを引き起こす可能性があります。すべてのオブジェクトに対してすべてのフレームに対してそれを行うと、多くのCPUを燃やすことができます。

あなたが興味があるなら、私はこのパターンについてもっと持っています ここ, 、あなたはその章にあるもののほとんどをすでに理解しているように見えますが。

使用する」イベントバス"。(おそらくそのままコードを使用できないことに注意してくださいが、基本的なアイデアを与えるはずです)。

基本的に、すべてのオブジェクトがリスナーとして登録し、「xが発生した場合、知りたい」と言う中心的なリソースを作成します。ゲームで何かが起こると、責任あるオブジェクトはイベントXをイベントバスに送信することができ、すべての興味深いパーティーが気付くでしょう。

編集]より詳細な議論については、 メッセージの合格 (ありがとう snk_kid これを指摘するために)。

1つのアプローチは、コンポーネントのコンテナを初期化することです。各コンポーネントはサービスを提供でき、他のコンポーネントからのサービスが必要になる場合があります。プログラミング言語と環境に応じて、この情報を提供する方法を考え出す必要があります。

最も単純な形式では、コンポーネント間に1対1の接続がありますが、1対1の接続も必要です。例 CollectionDetector 実装するコンポーネントのリストがあります IBoundingBox.

初期化中、コンテナはコンポーネント間の接続を配線し、実行時に追加コストはありません。

これはソリューションに近い3)、コンポーネント間の接続は一度だけ配線され、ゲームループのすべての反復でチェックされないことを期待してください。

.NETの管理拡張性フレームワーク この問題の素晴らしい解決策です。私はあなたがAndroidで開発するつもりであることを理解していますが、あなたはまだこのフレームワークからいくらかのインスピレーションを得るかもしれません。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top