このアドバイスは、レンダリングに特化したものではありませんが、物事を大きく分離するシステムを考え出すのに役立つはずです。まず、位置情報とは別に「GameObject」データを保持してみてください。
単純なXYZ位置情報はそれほど単純ではない可能性があることに注意してください。物理エンジンを使用している場合、位置データはサードパーティエンジン内に保存できます。それらの間で同期する必要があり(多くの無意味なメモリコピーが必要になります)、エンジンから直接情報を照会します。ただし、すべてのオブジェクトに物理が必要なわけではありません。一部は固定されているため、単純なフロートのセットがそこで動作します。一部は他のオブジェクトにアタッチされている場合もあるため、それらの位置は実際には別の位置のオフセットです。高度なセットアップでは、コンピューター側でスクリプト、ストレージ、およびネットワークレプリケーションが必要な場合にのみ、GPUにのみ位置を保存できます。そのため、位置データにはいくつかの選択肢があります。ここでは、継承を使用するのが理にかなっています。
オブジェクトがその位置を所有するのではなく、そのオブジェクト自体がインデックスデータ構造によって所有される必要があります。たとえば、「レベル」にはオクトリー、または物理エンジンの「シーン」があります。レンダリングする(またはレンダリングシーンを設定する)場合は、カメラから見えるオブジェクトの特別な構造を照会します。
これは、適切なメモリ管理にも役立ちます。この方法では、実際にエリア内にないオブジェクトは、0.0座標またはエリア内で最後にあったときの座標を返すのではなく、意味のある位置すらありません。
object.getX()の代わりにオブジェクトの座標を保持しなくなった場合、level.getX(object)になります。レベルでオブジェクトを検索することの問題は、すべてのオブジェクトを調べて、クエリを実行するオブジェクトと一致する必要があるため、操作が遅くなる可能性があります。
それを避けるために、おそらく特別な「リンク」クラスを作成します。レベルとオブジェクトの間をバインドするもの。私はそれを「場所」と呼びます。これには、xyz座標と、レベルのハンドルとオブジェクトのハンドルが含まれます。このリンククラスは空間構造/レベルに格納され、オブジェクトはそれへの弱い参照を持ちます(レベル/場所が破壊された場合、オブジェクトの参照はnullに更新する必要があります。レベルが削除された場合、そのようにオブジェクトを「所有」します。特別なインデックス構造、含まれる場所、およびそのオブジェクトも同様です。
typedef std::tuple<Level, Object, PositionXYZ> Location;
これで、位置情報は1か所にのみ保存されます。オブジェクト、空間インデックス構造、レンダラーなどの間で複製されません。
Octreesのような空間データ構造は、多くの場合、格納するオブジェクトの座標さえ必要としません。位置は、構造自体のノードの相対位置に格納されます(これは一種の不可逆圧縮と考えることができ、高速なルックアップ時間の精度を犠牲にします)。Octreeのロケーションオブジェクトを使用すると、クエリが完了すると、実際の座標がその中に見つかります。
または、物理エンジンを使用してオブジェクトの場所または2つの場所の混合を管理している場合、Locationクラスはすべてのコードを1か所に保持しながら透過的に処理する必要があります。
もう1つの利点は、レベルへの位置と参照が同じ場所に保存されることです。object.TeleportTo(other_object)を実装して、レベルを越えて機能させることができます。同様に、AIの経路探索は別の領域に何かをたどることができます。
レンダリングに関して。レンダーは、Locationに同様のバインディングを持つことができます。そこにレンダリング固有のものがあるだろうことを除いて。この構造に「オブジェクト」または「レベル」を保存する必要はおそらくないでしょう。オブジェクトは、カラーピッキングなどのことをしようとしている場合や、その上にフローティングするヒットバーをレンダリングしようとしている場合などに役立ちますが、そうでない場合、レンダラーはメッシュなどのみを考慮します。RenderableStuffはメッシュで、バウンディングボックスなどもあります。
typedef std::pair<RenderableStuff, PositionXYZ> RenderThing;
renderer.render(level, camera);
renderer: object = level.getVisibleObjects(camera);
level: physics.getObjectsInArea(physics.getCameraFrustrum(camera));
for(object in objects) {
//This could be depth sorted, meshes could be broken up and sorted by material for batch rendering or whatever
rendering_que.addObjectToRender(object);
}
フレームごとにこれを行う必要はないかもしれませんが、カメラが現在示しているよりも大きな領域を確保することができます。キャッシュし、オブジェクトの動きを追跡して、バウンディングボックスが範囲内にあるかどうかを確認し、カメラの動きを追跡します。ただし、ベンチマークを行うまでは、そのようなものをいじり始めないでください。
物理エンジン自体も同様の抽象化を持つ可能性があります。これは、オブジェクトデータを必要とせず、衝突メッシュと物理プロパティのみを必要とするためです。
コアオブジェクトデータに含まれるのは、オブジェクトが使用するメッシュの名前です。ゲームエンジンは、オブジェクトクラスにレンダリング固有のもの(レンダリングAPI、つまりDirectX対OpenGLなど)の負担をかけることなく、好きな形式でこれを読み込むことができます。
また、さまざまなコンポーネントを分離します。これにより、物理エンジンの交換などを簡単に行えるようになります。これは、ほとんどのものが1つの場所に含まれているためです。また、ユニットテストがはるかに簡単になります。必要なのはLocationクラスだけなので、実際の偽オブジェクトを設定しなくても、物理クエリなどをテストできます。簡単に最適化することもできます。どのクラスと単一の場所でそれらを最適化するためにどのクエリを実行する必要があるかがより明確になります(たとえば、上記のlevel.getVisibleObjectは、カメラがあまり動かない場合にキャッシュできる場所です)。
m_renderable
メンバーと対話させます。そうすれば、ロジックをより適切に分離できます。物理学、ai、その他もある一般的なオブジェクトにレンダリング可能な「インターフェース」を強制しないでください。その後、レンダリング可能物を個別に管理できます。さらに物事を切り離すために、OpenGL関数呼び出しを抽象化するレイヤーが必要です。したがって、優れたエンジンがさまざまなレンダリング可能な実装内でGL API呼び出しを行うことを期待しないでください。マイクロナットシェルでそれだけです。