レンダリングロジックをGameObjectクラスの外に移動するための戦術


10

ゲームを作成するとき、多くの場合、すべてのエンティティが継承する次のゲームオブジェクトを作成します。

public class GameObject{
    abstract void Update(...);
    abstract void Draw(...);
}

したがって、更新ループでは、すべてのゲームオブジェクトを反復処理して状態を変更する機会を与え、次に次の描画ループですべてのゲームオブジェクトを再度反復処理して、自分自身を描画する機会を与えます。

これは、単純なフォワードレンダラーを備えた単純なゲームではかなりうまく機能しますが、モデル、複数のテクスチャ、およびゲームオブジェクト間の密結合を作成するすべての最悪の脂肪描画メソッドを格納する必要があるいくつかの巨大なゲームオブジェクトにつながることがよくあります。現在のレンダリング戦略とレンダリング関連のクラス。

レンダリング戦略をフォワードから据え置きに変更する場合、多くのゲームオブジェクトを更新する必要があります。そして、私が作成するゲームオブジェクトは、可能な限り再利用できません。もちろん、継承や構成はコードの重複との闘いに役立ち、実装の変更が少し簡単になりますが、それでもまだ足りないと感じています。

おそらく、より良い方法は、GameObjectクラスからDrawメソッドを完全に削除して、Rendererクラスを作成することです。GameObjectには、それを表現するモデルやモデルにペイントするテクスチャなど、ビジュアルに関するデータを含める必要がありますが、これを行う方法はレンダラーに任されます。ただし、レンダリングには境界線のケースが多いことが多いため、これによりGameObjectからレンダラーへの密結合が削除されますが、レンダラーはすべてのゲームオブジェクトについてすべて知っている必要があります。固く結ばれた。これはかなりの数の良い習慣に違反します。多分データ指向設計がうまくいくかもしれません。ゲームオブジェクトは確かにデータですが、レンダラーはこれによってどのように駆動されますか?よく分かりません。

だから私は途方に暮れていて、良い解決策を考えることができません。私はMVCの原則を使用しようとしましたが、以前はゲームでそれを使用する方法についていくつかのアイデアがありましたが、最近は思ったほど適切ではないようです。皆さんがこの問題にどのように取り組んでいるのか知りたいです。

とにかく要約してみましょう。次の設計目標をどのように達成できるかに興味があります。

  • ゲームオブジェクトにレンダリングロジックがない
  • ゲームオブジェクトとレンダリングエンジン間の疎結合
  • 知っているすべてのレンダラーはありません
  • レンダーエンジン間のランタイム切り替えが望ましい

理想的なプロジェクトセットアップは、互いに参照する必要のない別個の「ゲームロジック」とレンダリングロジックプロジェクトです。

John CarmackがTwitterで、実行時にレンダーエンジンを入れ替えることができ、両方のレンダラー(ソフトウェアレンダラーとハードウェアアクセラレーテッドレンダラー)を使用するようにシステムに指示することもできる柔軟性のあるシステムを持っていると私が言ったとき、この思考トレインはすべて始まりました。同時に、彼は違いを調べることができます。これまでにプログラムしたシステムは、それほど柔軟ではありません

回答:


7

分離への簡単な最初のステップ:

ゲームオブジェクトは、ビジュアルではなくデータの識別子を参照します。文字列のような単純なものを考えてみましょう。例:「human_male」

レンダラーは、「human_male」参照のロードと維持、および使用するハンドルのオブジェクトへの引き渡しを担当します。

恐ろしい疑似コードの例:

GameObject( initialization parameters )
  me.render_handle = Renderer_Create( parameters.render_string )

- elsewhere
Renderer_Create( string )

  new data handle = Resources_Load( string );
  return new data handle

- some time later
GameObject( something happens to me parameters )
  me.state = something.what_happens
  Renderer_ApplyState( me.render_handle, me.state.effect_type )

- some time later
Renderer_Render()
  for each renderable thing
    for each rendering back end
        setup graphics for thing.effect
        render it

- finally
GameObject_Destroy()
  Renderer_Destroy( me.render_handle )

混乱して申し訳ありませんが、とにかく、実際のオブジェクトなどを見ることに基づく純粋なOOPから、責任に基づくOOPへの単純な変更によって、条件が満たされます。

  • ゲームオブジェクトにレンダリングロジックはありません(オブジェクトはハンドルのみであるため、オブジェクト自体にエフェクトを適用できます)
  • ゲームオブジェクトとレンダリングエンジンの間の疎結合(完了、すべての接触は抽象ハンドルを介して行われ、適用可能な状態であり、それらの状態で何をするかではない)
  • レンダラーをすべて知っているわけではありません(完了、自分自身についてのみ知っています)
  • レンダーエンジン間のランタイム切り替えが望ましい(これは、Renderer_Render()の段階で行われる。ただし、両方のバックエンドを記述する必要がある)

クラスの単純なリファクタリングを超えて検索できるキーワードは、「エンティティ/コンポーネントシステム」と「依存性注入」であり、古い脳の歯車を回転させるための「MVC」GUIパターンである可能性があります。


これは以前にやったこととはかなり異なり、かなりの可能性を秘めているようです。幸い、私は既存のエンジンに制約されていないので、いじくり回すことができます。依存関係の注入は常に私の脳を傷つけますが、私はあなたが言及した用語も調べます。
ロイT.

2

私が自分のエンジンに対して行ったことは、すべてをモジュールにグループ化することです。だから私は私のGameObjectクラスを持っています、そしてそれは以下のハンドルを持っています:

  • ModuleSprite-スプライトの描画
  • ModuleWeapon-発砲
  • ModuleScriptingBase-スクリプト
  • ModuleParticles-パーティクルエフェクト
  • ModuleCollision-衝突の検出と応答

私が持っているので、PlayerクラスとBulletクラスを。から派生しGameObject、に追加されますScene。ただしPlayer、次のモジュールがあります。

  • ModuleSprite
  • ModuleWeapon
  • ModuleParticles
  • ModuleCollision

そしてBulletこれらのモジュールがあります:

  • ModuleSprite
  • ModuleCollision

この方法で物事を整理VehicleするVehicleLandと、a 、a 、aがVehicleWaterあり、今はが必要な「死のダイヤモンド」を回避できますVehicleAmphibious。代わりにVehicle、a ModuleWaterとaがありModuleLandます。

追加ボーナス:プロパティのセットを使用してオブジェクトを作成できます。知っておく必要があるのは、ベースタイプ(プレイヤー、敵、弾丸など)だけであり、そのタイプに必要なモジュールへのハンドルを作成します。

私のシーンでは、次のことを行います。

  • UpdateすべてのGameObjectハンドルに対してを呼び出します。
  • あるものに対して衝突チェックと衝突応答を行います ModuleCollisionハンドルのあるます。
  • 物理演算後の最終的な位置を知らせるにはUpdatePost、すべてのGameObjectハンドルのを呼び出します。
  • フラグが設定されているオブジェクトを破棄します。
  • m_ObjectsCreatedリストからリストに新しいオブジェクトを追加しますm_Objects

さらに、オブジェクトごとではなくモジュールごとに整理することもできます。次に、のリストをレンダリングしModuleSprite、一連のを更新ModuleScriptingBaseして、のリストと衝突しModuleCollisionます。


最大の構成のように聞こえます!非常に素晴らしい。ただし、ここではレンダリング固有のヒントはあまり見かけません。異なるモジュールを追加するだけで、どのようにそれを処理しますか?
ロイT.

そうそう。これがこのシステムの欠点です。特定の要件GameObject(たとえば、スプライトの「蛇」をレンダリングする方法)がある場合はModuleSprite、その特定の機能の子を作成する必要があります(ModuleSpriteSnake)か、新しいモジュールを完全に追加する(ModuleSnake)。幸い、これらは単なるポインタですが、GameObject文字通りオブジェクトが実行できるすべてのことを実行するコードを見てきました。
knight666
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.