すべてのゲームオブジェクトを単一のリストに保存することは、許容できる設計ですか?


24

作成したすべてのゲームについて、すべてのゲームオブジェクト(行頭文字、車、プレーヤー)を単一の配列リストに配置し、ループして描画と更新を行います。各エンティティの更新コードは、そのクラス内に保存されます。

私は疑問に思っていましたが、これは物事を進める正しい方法ですか、それともより速く、より効率的な方法ですか?


4
これが.Netであると仮定して、「配列リスト」と言ったことがわかります。代わりにList <T>にアップグレードする必要があります。List <T>が型に強いことを除いて、ほとんど同じです。そのため、要素にアクセスするたびに型キャストする必要はありません。ドキュメントは次のとおりです。msdn.microsoft.com
ジョンマクドナルド

回答:


26

正しい方法はありません。記述していることは機能しますが、パフォーマンスの問題を引き起こしているのではなく、設計に関心があるので、質問しているように見えるので、おそらく十分に高速です。だから、それは確かに、正しい方法。

たとえば、各オブジェクトタイプの同種リストを維持するか、異種リストのすべてをグループ化して、キャッシュ内のコードの一貫性を改善したり、並列更新を許可したりすることで、確実に異なる方法で行うことができます。これを行うことでかなりのパフォーマンスの改善が見られるかどうかは、ゲームの範囲と規模を知らずに言うのは困難です。

また、エンティティの責任をさらに除外することもできます。つまり、エンティティは現在、更新とレンダリングの両方を行っているように思われます。これにより、個々のインターフェイスを相互に分離することで、個々のインターフェイスの保守性と柔軟性を高めることができます。繰り返しになりますが、必要な追加作業の恩恵を受けるかどうかは、あなたのゲームについて私が知っていることを知るのは難しいです(基本的には何もありません)。

パフォーマンスや保守性の問題に気付いた場合、改善の余地があるかもしれないということについて、あまり強調しすぎないことをお勧めします。


16

他の人が言ったように、それが十分に速いなら、それは十分に速いです。より良い方法があるかどうか疑問に思っているなら、自問する質問は

すべてのオブジェクトを繰り返し処理しますが、それらのサブセットのみを操作しますか?

もしそうなら、それはあなたが関連する参照だけを保持している複数のリストを持ちたいかもしれないことを示しています。たとえば、すべてのゲームオブジェクトをループしてレンダリングするが、オブジェクトのサブセットのみをレンダリングする必要がある場合、レンダリングする必要のあるオブジェクトのみに個別のレンダリング可能リストを保持する価値があります。

これにより、ループするオブジェクトの数が削減され、リスト内のすべてのオブジェクトが処理されることが既にわかっているため、不要なチェックがスキップされます。

パフォーマンスを大幅に向上できるかどうかを確認するには、プロファイルを作成する必要があります。


私はこれに同意します。通常、各フレームを更新する必要があるオブジェクトのリストのサブセットがあり、レンダリングするオブジェクトについても同じことを検討しています。ただし、これらのプロパティがオンザフライで変更できる場合、すべてのリストの管理は複雑になる可能性があります。オブジェクトがレンダリング可能からレンダリング不可に、または更新可能から更新不可になったときに、それらを一度に複数のリストに追加または削除しています。
ニックフォスター

8

特定のゲームのニーズにほぼ完全に依存します。単純なゲームでは完全に機能しますが、より複雑なシステムでは機能しません。ゲームで機能している場合は、心配する必要はありません。必要になるまで、過剰なエンジニアリングを試みないでください。

単純なアプローチが状況によって失敗する理由については、可能性は無限であり、ゲーム自体に完全に依存しているため、1つの投稿にまとめるのは困難です。他の回答では、たとえば、責任を共有するオブジェクトを別のリストにグループ化することができます。

これは、ゲームデザインに応じて行う最も一般的な変更の1つです。他のいくつかの(より複雑な)例について説明する機会がありますが、他にも多くの考えられる理由と解決策があることを忘れないでください。

まず、ゲームオブジェクトの更新とレンダリングは、ゲームによっては要件が異なる場合があるため、個別に処理する必要があることを指摘します。ゲームオブジェクトの更新とレンダリングで発生する可能性のある問題:

ゲームオブジェクトの更新

これは、私が読むことをお勧めするGame Engine Architectureから抜粋です。

オブジェクト間の依存関係が存在する場合、上記の段階的な更新手法を少し調整する必要があります。これは、オブジェクト間の依存関係により、更新の順序を管理する規則が競合する可能性があるためです。

つまり、一部のゲームでは、オブジェクトが互いに依存し、特定の更新順序を必要とする可能性があります。このシナリオでは、オブジェクトとそれらの相互依存関係を保存するために、リストよりも複雑な構造を考案する必要があるかもしれません。

ここに画像の説明を入力してください

ゲームオブジェクトのレンダリング

一部のゲームでは、シーンとオブジェクトが親子ノードの階層を作成し、正しい順序でレンダリングされ、親を基準にした変換が必要です。そのような場合、単一のリストではなく、シーングラフ(ツリー構造)などのより複雑な構造が必要になる場合があります。

ここに画像の説明を入力してください

他の理由としては、たとえば、空間分割データ構造を使用して、視錐台カリングの実行効率を向上させる方法でオブジェクトを整理することがあります。


4

答えは「はい、これで構いません」です。パフォーマンスが交渉不可能なビッグアイアンシミュレーションに取り組んだとき、すべてが1つの大きなファット(BIGおよびFAT)グローバルアレイに含まれていることに驚きました。その後、OOシステムに取り組み、2つを比較しました。非常にいものでしたが、デバッグコンパイルされたシングルスレッドプレーンオールドCの「すべてを支配する1つの配列」バージョンは、C ++で-O2コンパイルされた「きれいな」マルチスレッドOOシステムよりも数倍高速でした。その大部分は、大脂肪配列バージョンの優れたキャッシュローカリティ(スペースと時間の両方)に関係していると思います。

パフォーマンスが必要な場合、1つの大きなグローバルC配列が(経験から)上限になります。


2

基本的にあなたがしたいことは、必要に応じてリストを作成することです。一度にすべての地形オブジェクトを再描画する場合、それらのリストだけが便利です。しかし、これには価値がありますが、数万個、および地形ではないものがたくさんあります。それ以外の場合は、すべてのリストを熟読し、「if」を使用して地形オブジェクトを引き出すだけで十分に高速です。

他のリストの数に関係なく、常に1つのマスターリストが必要でした。通常、私はそれをある種のマップ、またはキー付きテーブル、またはディクショナリにし、個々のアイテムにランダムにアクセスできるようにします。ただし、単純なリストの方がはるかに簡単です。ゲームオブジェクトにランダムにアクセスしない場合、マップは必要ありません。また、適切なエントリを見つけるために数千のエントリを時折順次検索するのに時間がかかりません。ですから、他に何をするにしても、マスターリストに固執することを計画していると思います。大きくなったマップの使用を検討してください。(キーの代わりにインデックスを使用できる場合、配列に固執し、Mapよりもはるかに優れたものを持つことができます。私は常にインデックス番号の間に大きなギャップができたので、それをあきらめなければなりませんでした。)

配列について一言:信じられないほど高速です。しかし、約100,000の要素を取得すると、ガベージコレクターに適切なものを提供し始めます。スペースを一度割り当てることができ、後でそれを変更できない場合は、問題ありません。ただし、アレイを継続的に拡張している場合、メモリの大きなチャンクを継続的に割り当てて解放しているため、一度に数秒間ゲームがハングし、メモリエラーで失敗することさえあります。

速くて効率的ですか?確かに。ランダムアクセス用のマップ。本当に大きなリストの場合、ランダムアクセスが必要ない場合は、リンクリストが配列に勝ります。必要に応じて、さまざまな方法でオブジェクトを整理するための複数のマップ/リスト。更新をマルチスレッド化できます。

しかし、それはすべて多くの作業です。その単一の配列は高速でシンプルです。必要な場合にのみ他のリストや物を追加できますが、おそらくそれらは必要ないでしょう。ゲームが大きくなった場合、何がうまくいかないかだけに注意してください。実際のバージョンの10倍の数のオブジェクトを含むテストバージョンを用意して、いつトラブルに向かうのかを考えてください。(ゲーム大きくなっている場合にのみこれを行ってください。)(そして、マルチスレッドを試してみてください。多くのことを考えずに。他のすべては簡単に始められます。マルチスレッドを使用すると、入会するのに大きな代価を払うことになり、入会するための会費が高くなり、退出するのが困難になります。)

つまり、あなたがやっていることをやり続けるだけです。あなたの最大の制限はおそらくあなた自身の時間であり、あなたはそれを最大限に活用しています。


リンクされたリストは、中央から何かを追加したり削除したりしない限り、どのサイズでも配列を上回るとは本当に思いません。
ザンリンクス

@ZanLynx:そしてそれでも。私が考える新しいランタイムの最適化のヘルプアレイをたくさん-彼らはそれを必要としないこと。ただし、大きな配列で発生する可能性があるように、大量のメモリチャンクを繰り返し迅速​​に割り当てて解放すると、ガベージコレクターを結び付けることができます。(メモリの総量もここでの要因です。問題は、ブロックのサイズ、空きおよび割り当ての頻度、および使用可能なメモリの関数です。)プログラムがハングまたはクラッシュして、障害が突然発生します。通常、多くの空きメモリがあります。GCはそれを使用できません。リンクリストはこの問題を回避します。
ラルフシャピン

一方、リンクリストは、ノードがメモリ内で連続していないため、反復でキャッシュミスが発生する可能性が非常に高くなります。それはほとんどそんなにカットアンドドライではありません。再割り当ての問題は、それを行わないことで軽減できます(可能な場合は前もって割り当てることが多いためです)。また、ノードのプール割り当てにより、リンクリストのキャッシュの一貫性の問題を軽減できます(ただし、参照のフォローコストは残っていますが) 、それは比較的些細なことです)。
ジョシュ

はい、しかし配列内であっても、オブジェクトへのポインタを保存するだけではありませんか?(それがパーティクルシステムなどの短命の配列でない限り。)その場合、キャッシュミスがまだ多くあります。
トッドリーマン

1

私は、単純なゲームにやや似た方法を使用しました。次の条件に該当する場合、すべてを1つの配列に保存すると非常に便利です。

  1. 少数または既知の最大数のゲームオブジェクトがある
  2. パフォーマンスよりも単純さを優先します(つまり、ツリーなどのより最適化されたデータ構造には問題はありません)

いずれにせよ、このソリューションは、gameObject_ptr構造体を含む固定サイズの配列として実装しました。この構造体には、ゲームオブジェクト自体へのポインタと、オブジェクトが「生きている」かどうかを表すブール値が含まれていました。

ブール値の目的は、作成および削除操作を高速化することです。シミュレーションから削除されたゲームオブジェクトを破棄する代わりに、単に "alive"フラグをに設定しfalseます。

オブジェクトで計算を実行すると、コードは配列をループし、「生存」とマークされたオブジェクトで計算を実行し、「生存」とマークされたオブジェクトのみがレンダリングされます。

もう1つの簡単な最適化は、オブジェクトタイプごとに配列を整理することです。たとえば、すべての箇条書きは、配列の最後のn個のセルに存在できます。次に、インデックスの値または配列要素へのポインタでintを保存することにより、特定の型を低コストで参照できます。

言うまでもなく、このソリューションは配列が許容できないほど大きくなるため、大量のデータを含むゲームには適していません。また、未使用のオブジェクトがメモリを占有することを禁止するメモリ制約があるゲームには適していません。要するに、ある時点で持つことができるゲームオブジェクトの数に既知の上限がある場合、それらを静的配列に格納しても何も問題はありません。

これが私の最初の投稿です!それがあなたの質問に答えることを願っています!


最初の投稿!わーい!
ティリ

0

異常ではありますが、許容範囲内です。「より高速」および「より効率的」などの用語は相対的です。通常、ゲームオブジェクトを個別のカテゴリ(箇条書き用の1つのリスト、敵用の1つのリストなど)に整理して、プログラミング作業をより整理します(したがって、より効率的になります)が、コンピューターがすべてのオブジェクトをより速くループするわけではありません。


2
ほとんどのXNAゲームの点では十分です、オブジェクト整理すると、実際にそれらを繰り返し処理することができます。同じタイプのオブジェクトは、CPUの命令とデータキャッシュにヒットする可能性が高くなります。キャッシュミスは、特にコンソールで高価です。たとえば、Xbox 360では、L2キャッシュミスが発生すると約600サイクルかかります。それはテーブルに多くのパフォーマンスを残しています。これは、マネージコードがネイティブコードよりも優れている1つの場所です。時間内に近くに割り当てられたオブジェクトもメモリ内で近くなり、ガベージコレクション(圧縮)によって状況が改善されます。
慢性ゲームプログラマー

0

単一の配列とオブジェクトを言います。

だから(見るコードなしで)それらはすべて 'uberGameObject'に関連していると思います。

したがって、2つの問題があります。

1)あなたは「神」オブジェクトを持っているかもしれません-それは実際にそれが何であるかによってセグメント化されていない、たくさんのものをします。

2)このリストを繰り返し処理して、入力しますか?またはそれぞれを開梱します。これには大きなパフォーマンスの低下があります。

さまざまなタイプ(箇条書き、悪者など)を独自の厳密に型指定されたリストに分割することを検討してください。

List<BadGuyObj> BadGuys = new List<BadGuyObj>();
List<BulletsObj> Bullets = new List<BulletObj>();

 //Game 
OnUpdate(gameticks gt)
{  foreach (BulletObj thisbullet in Bullets) {thisbullet.Update();}  // much quicker.

更新ループで呼び出されるすべてのメソッドを含む何らかの一般的なベースクラスまたはインターフェースがある場合、型キャストを実行する必要はありません(例:)update()
-bummzack

0

一般的な単一のリストと各オブジェクトタイプの個別のリストとの間で妥協案を提案できます。

複数のリスト内の1つのオブジェクトへの参照を保持します。これらのリストは、基本動作によって定義されます。例えば、MarineSoldierオブジェクトは、で参照されPhysicalObjListGraphicalObjListUserControlObjListHumanObjList。そのため、物理学を処理するPhysicalObjListとき、毒でダメージを与えるプロセスを繰り返します- HumanObjList兵士が死んだ場合-からそれを削除しますが、HumanObjListそのままにしPhysicalObjListます。このメカニズムを機能させるには、オブジェクトの作成/削除時に自動挿入/削除オブジェクトを整理する必要があります。

アプローチの利点:

  • オブジェクトの型指定された編成(AllObjectsList更新時のキャストなし)
  • リストはオブジェクトクラスではなくロジックに基づいています
  • 結合された能力を持つオブジェクトに問題はありません(MegaAirHumanUnderwaterObjectそのタイプの新しいリストを作成する代わりに対応するリストに挿入されます)

PS:自己更新に関しては、複数のオブジェクトが相互作用し、それぞれが他のオブジェクトに依存している場合に問題が発生します。これを解決するには、自己更新ではなく、オブジェクトに外部的に影響を与える必要があります。

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