2Dサイドスクローラー用の「最適な」ゲームループ


14

2Dサイドスクローラーのゲームループの「最適な」(パフォーマンスに関して)レイアウトを記述することは可能ですか?このコンテキストでは、「ゲームループ」はユーザー入力を受け取り、ゲームオブジェクトの状態を更新し、ゲームオブジェクトを描画します。

たとえば、深い継承階層を持つGameObject基本クラスを持つことは、メンテナンスに適しています...次のようなことができます:

foreach(GameObject g in gameObjects) g.update();

しかし、このアプローチはパフォーマンスの問題を引き起こす可能性があると思います。

一方、すべてのゲームオブジェクトのデータと機能はグローバルになります。これはメンテナンスの頭痛の種ですが、最適に実行されるゲームループに近いかもしれません。

何かご意見は? 最適なゲームループ構造の実用的なアプリケーションに興味があります。優れたパフォーマンスと引き換えにメンテナンスの頭痛がします。


回答:


45

たとえば、深い継承階層を持つGameObject基本クラスがあると、メンテナンスに適しています...

実際、一般に、深い階層は浅い階層よりも保守性が悪く、ゲームオブジェクトの現代的なアーキテクチャスタイルは、浅い集約ベースのアプローチに向かっています。

しかし、このアプローチはパフォーマンスの問題を引き起こす可能性があると思います。一方、すべてのゲームオブジェクトのデータと機能はグローバルになります。これはメンテナンスの頭痛の種ですが、最適に実行されるゲームループに近いかもしれません。

示したループにはパフォーマンスの問題がある可能性がありますが、GameObjectクラスにインスタンスデータとメンバー関数があるため、後続のステートメントが示すように、パフォーマンスの問題はありません。むしろ、問題は、ゲーム内のすべてのオブジェクトをまったく同じものとして扱う場合、それらのオブジェクトをインテリジェントにグループ化していないことです。おそらく、それらはそのリスト全体にランダムに散らばっている可能性があります。潜在的に、そのオブジェクトのupdateメソッドのすべての呼び出し(そのメソッドがグローバル関数であるかどうか、およびオブジェクトがインスタンスデータまたは「グローバルデータ」がインデックスを作成するテーブルなどにあるかどうか)最後のループ反復での更新呼び出しとは異なります。

これにより、関連する関数を使用してメモリをページングしたりメモリからページングしたり、命令キャッシュをより頻繁に補充したりする必要が生じ、ループが遅くなるため、システムにプレッシャーがかかります。これが肉眼で(またはプロファイラーでも)観察できるかどうかは、「ゲームオブジェクト」と見なされるもの、それらの平均数、およびアプリケーションで行われている他の処理によって異なります。

コンポーネント指向のオブジェクトシステムは、集約が継承よりも望ましいという哲学を利用して、現在人気のある傾向です。このようなシステムにより、コンポーネントの「更新」ロジックを分割できる可能性があります(「コンポーネント」は、物理システムによって処理されるオブジェクトの物理的にシミュレートされた部分を表すものなど、機能の単位として大まかに定義されます) )複数のスレッドに-コンポーネントの種類によって区別-可能かつ望ましい場合は、パフォーマンスが向上する可能性があります。少なくとも、特定のタイプのすべてのコンポーネントが一緒に更新されるようにコンポーネントを編成し、CPUのキャッシュを最適に使用できます。このようなコンポーネント指向システムの例については、このスレッドで説明します

そのようなシステムは、しばしば高度に分離されており、これもメンテナンスにとって有益です。

データ指向の設計は、関連するアプローチです。オブジェクトに必要なデータを最優先事項として、そのデータを(たとえば)効果的に一括処理できるようにすることです。これは通常、同じ目的のクラスターに使用されるデータをまとめて保持し、一度に操作する組織を意味します。基本的にオブジェクト指向設計と互換性はありません。この問題については、GDSEでこのテーマに関するおしゃべりを見つけることができます。

実際には、ゲームループへのより最適なアプローチは、元の

foreach(GameObject g in gameObjects) g.update();

もっと何かのような

ProcessUserInput();
UpdatePhysicsForAllObjects();
UpdateScriptsForAllObjects();
UpdateRenderDataForAllObjects();
RenderEverything();

そのようなAの世界では、それぞれがGameObject独自のへのポインタまたは参照を持っているかもしれませんPhysicsDataか、ScriptまたはRenderData、あなたが個別にオブジェクトと対話する必要があるかもしれません例について、実際PhysicsDataScriptsRenderData、エトセトラすべて、それぞれのサブシステムによって所有されるだろう(物理シミュレータ、スクリプトホスティング環境、レンダラー)、上記のように一括処理されます。

このアプローチは魔法の弾丸ではなく、常にパフォーマンスの向上をもたらすとは限らないことに注意することが非常に重要です(ただし、一般に、深い継承ツリーよりも優れた設計です)。オブジェクトがほとんどない場合、または更新を効果的に並列化できないオブジェクトが非常に多い場合、基本的にパフォーマンスの違いがないことに特に気付くでしょう。

残念ながら、最も最適なマジックループはありません。すべてのゲームは異なり、さまざまな方法でパフォーマンスチューニングが必要になる場合があります。ですから、盲目的にインターネット上のランダムな人のアドバイスを受ける前に、物事を測定(プロファイル)することが非常に重要です。


5
良い主君、これは素晴らしい答えです。
レイヴライン

2

ゲームオブジェクトスタイルのプログラミングは小規模なプロジェクトには適していますが、プロジェクトが非常に大きくなると、深い継承階層になり、ゲームオブジェクトベースが非常に大きくなります!

例として、Position(xおよびyの場合)、displayObject(これは描画要素の場合は画像を参照)、width、heightなどの最小限の属性でゲームオブジェクトベースの作成を開始したとします。

後で、アニメーション状態を持つサブクラスを追加する必要がある場合、おそらく「アニメーション制御コード」(たとえば、currentAnimationId、このアニメーションのフレーム数など)をGameObjectBaseに移動します。オブジェクトにはアニメーションがあります。
後で静的なリジッドボディ(アニメーションがない)を追加する場合は、GameObject Baseの更新機能で「アニメーションコントロールコード」をチェックする必要があります(ただし、アニメーションがあるかどうかはチェックします)。これは重要です!)これに反して、コンポーネントベースの構造に従う場合、オブジェクトに「アニメーションコントロールコンポーネント」をアタッチします。必要に応じてアタッチします。静的ボディの場合、そのコンポーネントはアタッチしません。方法では、不必要なチェックを回避できます。

そのため、スタイルを選択する選択はプロジェクトのサイズに依存します。GameObject構造は、小規模なプロジェクトの場合に簡単に習得できます。


@ジョシュペトリー-本当に素晴らしい答え!
アヤッパ

@ジョシュペトリー-有用なリンクと本当に良い答え!(あなたのポストの横にこのコメントを配置する方法がわからない:P)
Ayyappa

通常、私の答えの下にある「コメントを追加」リンクをクリックできますが、それには現在よりも高い評価が必要です(gamedev.stackexchange.com/privileges/commentを参照)。ありがとう!
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.