ゲームオブジェクトのすべてのインスタンスにDrawableGameComponentを使用することの短所は何ですか?


25

私は多くの場所でDrawableGameComponentsを「レベル」や、キャラクターやタイルなどの代わりに使用する代わりに保存する必要があることを読みました(この男がここで言うよう)。しかし、なぜそうなのか理解できません。私はこの投稿を読んで、それは私にとって非常に理にかなっていますが、これらは少数派です。

私は通常、このようなことにはあまり注意を払わないでしょうが、この場合、どうやらこれが進むべき道ではないと思われる大多数の人々が知りたいと思います。たぶん私は何かが欠けています。


なぜ、2年後、あなたは私の回答から「受け入れられた」マークを削除したのか興味がありますか?通常、私は本当に気にしないだろう-しかし、この場合にはVeraShackleの原因と腹立たしい :(トップにバブルに私の答えの不実表示を
アンドリュー・ラッセル

@AndrewRussell私はこのスレッドを少し前に読んで(いくつかの回答と投票により)、あなたの答えが正しいかどうかについて意見を述べる資格がないと思った。しかし、あなたが言うように、VeraShackleの答えが最も賛成票を持つものであるとは思わないので、あなたの答えを再び正しいものとしてマークします。
木名賢治

1
私はここで答えを次のように要約することができます:「使用するDrawableGameComponentと、メソッドシグネチャと特定の保持データモデルの単一セットにロックされます。通常、これらは最初は間違った選択です。「しかし...私はここで何が起こっているかを教えてみましょう
アンドリュー・ラッセル

1
DrawableGameComponentコアXNA APIの一部です(これも主に歴史的な理由による)。初心者のプログラマーはそれが「あり」、「機能」し、それ以上何も知らないのでそれを使用します。彼らは私の答えが「間違っている」と言っており、人間の心理学の性質上、それを拒否し、他の「サイド」を選んでいます。これは、VeraShackleの議論による非回答です。私はすぐにそれを呼び出さなかったため、たまたま勢いを取り戻しました(それらのコメントを参照)。私の最終的な反論は十分だと思います。
アンドリューラッセル

1
誰かが私の投票の正当性に賛成票に基づいて質問することに興味がある場合:(GDSEは前述の初心者と同じです)VeraShackleの答えは近年、私のものよりもいくつかの賛成票を獲得することができましたが、忘れないでください主にXNA上で、ネットワーク全体でさらに数千の賛成票を持っていること。複数のプラットフォームでXNA APIを実装しました。そして、私はXNAを使用するプロのゲーム開発者です。私の答えの血統は申し分のないものです。
アンドリューラッセル

回答:


22

「ゲームコンポーネント」は、XNA 1.0設計のごく初期の部分でした。これらは、WinFormsのコントロールのように機能するはずでした。アイデアは、GUI(タイマーなどの非視覚的なコントロールを追加するビットのようなもの)を使用してゲームにドラッグアンドドロップし、それらにプロパティを設定できるようにすることでした。

カメラやFPSカウンターなどのコンポーネントを使用できますか?...または...?

分かりますか?残念ながら、このアイデアは実際のゲームでは実際には機能しません。ゲームに必要なコンポーネントの種類は、実際には再利用できません。「プレーヤー」コンポーネントを持っているかもしれませんが、他のすべてのゲームの「プレーヤー」コンポーネントとは非常に異なります。

XNA 1.0より前にGUIがプルされたのは、この理由のためであり、時間の単純な不足のためだったと思います。

しかし、APIはまだ使用可能です。使用しない理由は次のとおりです。

基本的には、ゲーム開発者として最も簡単に記述および読み取り、デバッグ、保守できるものになります。ロードコードで次のようなことを行います。

player.DrawOrder = 100;
enemy.DrawOrder = 200;

単にこれを行うよりもはるかに明確ではありません:

virtual void Draw(GameTime gameTime)
{
    player.Draw();
    enemy.Draw();
}

特に、実際のゲームで次のような結果になる場合があります。

GraphicsDevice.Viewport = cameraOne.Viewport;
player.Draw(cameraOne, spriteBatch);
enemy.Draw(cameraOne, spriteBatch);

GraphicsDevice.Viewport = cameraTwo.Viewport;
player.Draw(cameraTwo, spriteBatch);
enemy.Draw(cameraTwo, spriteBatch);

(確かに、同様のServicesアーキテクチャを使用してカメラとspriteBatchを共有できます。しかし、それは同じ理由で悪い考えでもあります。)

このアーキテクチャ-またはその欠如 -は、はるかに軽量で柔軟です!

数行変更するだけで、描画順序を簡単に変更したり、複数のビューポートを追加したり、レンダーターゲットエフェクトを追加したりできます。他のシステムでそれを行うには、それらのすべてのコンポーネント(複数のファイルに分散している)が何をしているか、そしてどのような順序で行うかを考える必要があります。

宣言型プログラミングと命令型プログラミングの違いのようなものです。ゲーム開発には、命令型プログラミングの方がはるかに望ましいことがわかります。


2
私は彼らがコンポーネントベースのプログラミングのためのものであるという印象を受けました。これは明らかにゲームプログラミングに広く使われています。
BlueRaja-ダニーPflughoeft

3
XNAゲームコンポーネントクラスは、実際にはそのパターンに直接適用できません。そのパターンは、複数のコンポーネントからオブジェクトを構成することです。XNAクラスは、オブジェクトごとに1つのコンポーネントを対象としています。設計に完全に適合する場合は、必ず使用してください。ただし、それらを中心にデザインを構築しないでください!ここで私の答えも参照してください-私が言及した場所を探してくださいDrawableGameComponent
アンドリューラッセル

1
GameComponent / Serviceアーキテクチャはそれほど有用ではなく、Officeのような概念やWebアプリケーションの概念がゲームフレームワークに押し込まれているように聞こえます。しかしplayer.DrawOrder = 100;、描画順序には命令型よりもメソッドを使用する方がずっと好きです。私は今、Flixelゲームを終了しています。これは、オブジェクトをシーンに追加する順序でオブジェクトを描画します。FlxGroupシステムはこれの一部を軽減しますが、何らかのZインデックスの並べ替えが組み込まれていると便利です。
michael.bartnett

@ michael.bartnett Flixelは保持モードAPIであり、XNA(明らかに、ゲームコンポーネントシステムを除く)は即時モードAPIであることに注意してください。保持モードAPIを使用する場合、または独自の APIを実装する場合、描画順序をその場で変更する機能は間違いなく重要です。実際、描画順序をオンザフライで変更する必要があることは、何かを保持モードにする良い理由です(ウィンドウシステムの例が思い浮かびます)。ただし、何かをイミディエイトモードとして記述できる場合、プラットフォームAPI(XNAなど)がそのように構築されている場合は、IMOを使用する必要があります。
アンドリューラッセル


26

うーん。ここで少しバランスをとるためにこれに挑戦しました。

Andrewの返信には5件の請求があるようですので、順番に対処していきます。

1)コンポーネントは古く、放棄されたデザインです。

コンポーネントデザインパターンのアイデアは、XNAよりもずっと前から存在していました。本質的に、それは、独自のUpdateおよびDraw動作のホームである、包括的なゲームオブジェクトではなく、オブジェクトを作成するという単純な決定です。コンポーネントコレクションのオブジェクトの場合、ゲームはこれらのメソッド(および他のいくつかのメソッド)を適切なタイミングで自動的に呼び出します。ゲームオブジェクト自体がこのコードを「知る」必要はありません。異なるゲーム(および異なるゲームオブジェクト)でゲームクラスを再利用したい場合、オブジェクトのこの分離は依然として基本的に良いアイデアです。AppHubからのXNA4.0の最新の(専門的に作成された)デモをざっと見てみると、Microsoftがまだこのデモの背後にしっかりといる(私の意見では当然)という印象を確認できます。

2)Andrewは、再利用可能なゲームクラスを2つ以上考えることはできません。

できます。実際、負荷を考えることができます!現在の共有ライブラリを確認したところ、80以上の再利用可能なコンポーネントとサービスで構成されています。あなたに風味を与えるために、あなたは持つことができます:

  • ゲームの状態/スクリーンマネージャー
  • ParticleEmitters
  • 描画可能なプリミティブ
  • AIコントローラー
  • 入力コントローラー
  • アニメーションコントローラー
  • ビルボード
  • 物理および衝突マネージャー
  • カメラ

特定のゲームアイデアには、このセットから少なくとも5〜10個が必要です-保証-1行のコードで完全に新しいゲームで完全に機能することができます。

3)明示的な描画呼び出しの順序で描画順序を制御することは、コンポーネントのDrawOrderプロパティを使用するよりも望ましいです。

まあ、私はこれについてアンドリューに基本的に同意します。ただし、コンポーネントのすべてのDrawOrderプロパティをデフォルトのままにしておくと、コレクションに追加する順序で明示的に描画順序を制御できます。これは、「可視性」の観点では、手動描画呼び出しの明示的な順序付けとまったく同じです。

4)オブジェクトのDrawメソッドのロードを自分で呼び出すことは、既存のフレームワークメソッドによって呼び出されるよりも「軽量」です。

どうして?さらに、最も些細なプロジェクトを除き、UpdateロジックとDrawコードは、いずれの場合もパフォーマンスヒットという点でメソッド呼び出しを完全に覆い隠します。

5)デバッグ時には、個々のオブジェクトのファイルにコードを置くよりも、1つのファイルにコードを置く方が望ましいです。

まず、Visual Studioでデバッグしているとき、ディスク上のファイルに関するブレークポイントの場所はほとんど目に見えません-IDEはドラマなしで場所を処理します。ただし、Skyboxの描画に問題があることも少し考えてみてください。スカイボックスのDrawメソッドに起こっている、本当に(おそらく非常に長い)でコードを描画するスカイボックス位置よりも面倒なマスターゲームオブジェクトのdrawメソッドを?いや、実際はそうではない-実際、私はそれがまったく反対だと主張するだろう。

だから、要約すると-ここでアンドリューの議論を長く注意深く見てください。彼らが実際に絡み合ったゲームコードを書くための実質的な根拠を与えているとは思わない。分離されたコンポーネントパターン(賢明に使用される)の利点は膨大です。


2
私はこの投稿に気付いただけで、強い反論をしなければなりません。再利用可能なクラスには問題ありません!(描画および更新メソッドなどがある場合があります。)描画/更新順序(#3で同意する)の明示的な制御が失われるため、ゲームアーキテクチャを提供するために()を使用することで特定の問題を抱えていますインターフェースの(あなたが対処しない)。DrawableGameComponent
アンドリューラッセル

1
あなたのポイント4は、パフォーマンスを意味するために「軽量」という言葉を使用することです。実際には、アーキテクチャの柔軟性を明確に意味します。残りのポイント#1、#2、特に#5は、ほとんど/すべてのコードをメインゲームクラスに押し込むべきだと思う不条理なストローマンを立てているようです!アーキテクチャに関する私のより詳細な考えについては、ここ私の答えを読んでください
アンドリューラッセル

5
最後に、もしあなたが私の答えに答えて、間違っていると言ったら、つまずくのではなく、それについての通知を見るように私の答えに返信を追加することは丁寧だったでしょうそれから2か月後。
アンドリューラッセル

私は誤解したかもしれませんが、これは質問にどのように答えますか?スプライトにGameComponentを使用する必要がありますか?
スーパーベスト


4

アンドリューには少し同意しませんが、すべての時間と場所があると思います。私はDrawable(GameComponent)SpriteやEnemyのような単純なものには使用しません。ただし、SpriteManagerまたはに使用しEnemyManagerます。

Andrewが描画と更新の順序について言ったことに戻るとSprite.DrawOrder、多くのスプライトにとって非常に紛らわしいと思います。また、比較的容易に設定するDrawOrderUpdateOrderしている管理者のための小さい(または合理的)量(Drawable)GameComponents

マイクロソフトが本当に厳格で誰も使用していなかったら、Microsoftはそれらを廃止したと思います。しかし、役割が正しければ、彼らは完全にうまく機能するからです。私自身Game.Servicesは、バックグラウンドで更新されるそれらを開発に使用しています。


2

私もこれについて考えていました、そして、それは私に起こりました:例えば、あなたのリンクの例を盗むために、RTSゲームで、あなたはすべてのユニットをゲームコンポーネントインスタンスにすることができました。はい。

ただし、ユニットのリストを保持し、必要に応じてユニットを描画および更新するUnitManagerコンポーネントを作成することもできます。そもそもユニットをゲームコンポーネントにしたいのは、コードを区分化するためだと思いますが、このソリューションはその目標を適切に達成するでしょうか?


2

コメントでは、古い答えの要約版を投稿しました:

使用するDrawableGameComponentと、メソッドシグネチャの単一セットと特定の保持データモデルにロックされます。通常、これらは最初は間違った選択であり、ロックインは将来のコードの進化を大きく妨げます。

ここで、現在のプロジェクトからコードを投稿することで、なぜそうなのかを説明します。


ゲームワールドの状態を維持するルートオブジェクトには、Update次のようなメソッドがあります。

void Update(UpdateContext updateContext, MultiInputState currentInput)

Updateゲームがネットワーク上で実行されているかどうかに応じて、にいくつかのエントリポイントがあります。たとえば、ゲームの状態を前の時点にロールバックしてから再シミュレートすることができます。

次のような追加のアップデートのようなメソッドもいくつかあります。

void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)

ゲームの状態には、更新するアクターのリストがあります。しかし、これは単なるUpdate呼び出し以上のものです。物理を処理する前後に追加のパスがあります。また、レベルトランジションだけでなく、アクターのスポーン/破壊の遅延のためのいくつかの派手なものもあります。

ゲームの状態以外に、独立して管理する必要があるUIシステムがあります。ただし、UIの一部の画面は、ネットワークコンテキストでも実行できる必要があります。


描画の場合、ゲームの性質上、これらのメソッドから入力を受け取るカスタムソートおよびカリングシステムがあります。

virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)

そして、何を描画するかがわかると、自動的に呼び出します:

virtual void Draw(DrawContext drawContext, int tag)

tag一部のアクターは他のアクターを保持できるため、このパラメーターが必要です。したがって、保持されているアクターの上下に描画できる必要があります。並べ替えシステムは、これが正しい順序で行われるようにします。

描画するメニューシステムもあります。そして、HUDを中心に、ゲームを中心にUIを描画するための階層システム。


次に、これらの要件をDrawableGameComponent提供するものと比較します。

public class DrawableGameComponent
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
    public int UpdateOrder { get; set; }
    public int DrawOrder { get; set; }
}

DrawableGameComponent上記のシステムを使用しているシステムから取得する合理的な方法はありません。(そして、それらは控えめな複雑さのゲームにとっても珍しい要件ではありません)。

また、これらの要件は、プロジェクトの開始時に単に発生したものではないことに注意することが非常に重要です。彼らは何ヶ月もの開発作業を経て進化しました。


人々が一般的に行うことは、彼らがDrawableGameComponentルートを開始する場合、彼らはハックで構築を開始することです:

  • メソッドを追加する代わりに、独自のベースタイプにsomeいダウンキャストを行います(それを直接使用しないのはなぜですか)。

  • Draw/ をソートして呼び出すためのコードを置き換える代わりにUpdate、順序番号をその場で変更するためにいくつかの非常に遅い処理を行います。

  • そして最悪なのは、メソッドにパラメーターを追加する代わりに、スタックではないメモリー位置で「パラメーター」を「渡す」ことです(これの使用Servicesは一般的です)。これは複雑で、エラーが発生しやすく、遅く、壊れやすく、スレッドセーフではないため、ネットワークを追加したり、外部ツールを作成したりすると問題になる可能性があります。

    また、API境界で公開されるのではなく、「コンポーネント」内に依存関係が隠されるため、コードの再利用が難しくなります。

  • または、彼らはこれがどれほどクレイジーであるかをほとんど理解しており、DrawableGameComponentこれらの問題を回避するために「マネージャー」を始めます。「マネージャー」の境界でこれらの問題がまだあることを除いて。

    常にこれらの「マネージャー」は、必要な順序で一連のメソッド呼び出しに簡単に置き換えることができます。それ以外のものは複雑すぎます。

もちろん、これらのハックの使用を開始したら、提供する以上の機能が必要であることがわかったら、それらのハックを元に戻すには実際の作業が必要DrawableGameComponentです。「ロックインされます。そして、誘惑はしばしばハッキングを積み重ね続けることです-それは実際にはあなたを動かし、あなたが作ることができるゲームの種類を比較的単純なものに制限します。


多くの人がこの点に到達し、内臓反応を持っているようです- DrawableGameComponent「間違っている」ことを受け入れ、それを受け入れたくないからでしょう。そのため、彼らは少なくとも「動作」させることが可能であるという事実を把握しています。

もちろんそれはすることができます作られた「仕事」に!私たちはプログラマーです-物事を異常な制約の下で機能させることは、実際には私たちの仕事です。しかし、この制約は必要ではなく、有用でも合理的でもありません...


特に驚くべきことは、この問題全体を回避することは驚くほど簡単なことです。ここでは、コードも提供します。

public class Actor
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
}

List<Actor> actors = new List<Actor>();

foreach(var actor in actors)
    actor.Update(gameTime);

foreach(var actor in actors)
    actor.Draw(gameTime);

(に従ってソートに追加することのシンプルDrawOrderかつUpdateOrder。1つの簡単な方法は、二つのリスト、および使用を維持することであるList<T>.Sort()。処理するためにも簡単ですLoad/ UnloadContent。)

そのコードが実際にどのような重要ではありませんです重要なのは、私がそれを簡単に修正できることです。

に別のタイミングシステムが必要であることがわかった場合gameTime、またはより多くのパラメーター(またはUpdateContext約15個のオブジェクトを含むオブジェクト全体)が必要な場合、描画のためにまったく異なる操作順序が必要な場合、または追加のメソッドが必要な場合さまざまな更新パスについては、先に進んでそれを行うことができます。

そして、私はすぐにそれを行うことができます。私はDrawableGameComponent自分のコードを選ぶことを気にする必要はありません。さらに悪いことには、そのような必要性が次に発生したときにさらに難しくなる何らかの方法でハックします。


実際に使用するのが適切なシナリオは1つだけです。それは、XNA用のドラッグアンドドロップコンポーネントスタイルのミドルウェアをDrawableGameComponent文字通り作成および公開している場合です。それが本来の目的でした。これ-私は追加します- もやっていない

(同様に、のカスタムコンテンツタイプを作成するServicesときに使用するのは本当に意味がありますContentManager。)


私はあなたのポイントに同意しますがDrawableGameComponent、特定のコンポーネントから継承するもの、特にメニュー画面(メニュー、多くのボタン、オプションなど)、またはFPS /デバッグオーバーレイのようなものを使用しても、特に間違っていることはありませんあなたが言及しました。これらの場合、通常、描画順序をきめ細かく制御する必要はなく、手動で記述する必要のあるコードが少なくなります(コードを記述する必要がない限り、これは良いことです)。しかし、それはあなたが言及していたドラッグアンドドロップのシナリオとほぼ同じだと思います。
Groo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.