ゲームにおける描画とロジックの分離


11

私は今、ゲーム開発をいじり始めている開発者です。私は.Netの男なので、XNAをいじり、iPhone用のCocos2dで遊んでいます。私の質問は本当にもっと一般的です。

簡単なPongゲームを作成しているとしましょう。私が持っていると思いますBallクラスとPaddleクラスを。ビジネスの世界の開発から来た私の最初の本能は、これらのクラスのいずれにも描画または入力処理コードがないことです。

//pseudo code
class Ball
{
   Vector2D position;
   Vector2D velocity;
   Color color;
   void Move(){}
}

ballクラスでは、入力を処理したり、描画を処理したりするものはありません。次に、別のクラス、私のGameクラス、またはScene.m(Cocos2dの)私がボールを新しくし、ゲームループ中に必要に応じてボールを操作します。

ただし、XNAとCocos2dの両方の多くのチュートリアルで、次のようなパターンが見られます。

//pseudo code
class Ball : SomeUpdatableComponent
{
   Vector2D position;
   Vector2D velocity;
   Color color;
   void Update(){}
   void Draw(){}
   void HandleInput(){}
}

私の質問は、これは正しいですか?これは、人々がゲーム開発で使用するパターンですか?Ballクラスにすべてをやらせることは、私が慣れ親しんだことすべてに何らかの形で反します。さらに、この2番目の例では、Ball移動方法がわかっているので、Paddle?のBall知識が必要Paddleですか?私の最初の例では、Gameクラスは、両方への参照であろうBallPaddle、その後、いくつかにそれらのオフの両方を出荷CollisionDetectionマネージャか何かが、各個々のコンポーネントは、それ自体で、すべてをしなければどのように私は、さまざまなコンポーネントの複雑さに対処するのですか?(私は私が理にかなっていると思います...)


2
残念ながら、それは多くのゲームで実際に行われている方法です。それがベストプラクティスであることを意味するとは思わないでしょう。あなたが読んでいるチュートリアルの多くは初心者を対象としている可能性があるため、モデル/ビュー/コントローラーまたはヨークのパターンを読者に説明し始めることはありません。
tenpn

@tenpn、あなたのコメントはあなたがこれが良い習慣だとは思わないことを示唆しているようです、私はあなたがさらに説明できるかどうか疑問に思いましたか?各タイプに非常に固有のコードを含むメソッドがあるため、これが最も適切な配置であると常に思っていました。でも、私自身はほとんど経験がないので、あなたの考えを聞きたいです。
sebf

1
@sebfデータ指向の設計に興味がある場合は機能せず、オブジェクト指向の設計が好きな場合は機能しません。アンクルボブのSOLID princplesを参照してください:butunclebob.com/ArticleS.UncleBob.PrinciplesOfOodを
tenpn

@tenpn。リンクされている記事とドキュメントは非常に興味深いものです。ありがとうございます。
sebf

回答:


10

私の質問は、これは正しいですか?これは、人々がゲーム開発で使用するパターンですか?

ゲーム開発者は、機能するものなら何でも使用する傾向があります。厳密ではありませんが、出荷されます。

Ballクラスにすべてをやらせることは、私が慣れ親しんだことすべてに何らかの形で反します。

だから、あなたが持っているもののためのより一般化されたイディオムはhandleMessages / update / drawです。メッセージを多用するシステム(予想どおり長所と短所があります)では、ゲームエンティティは、気になるメッセージをすべて取得し、それらのメッセージに対してロジックを実行してから、それ自体を描画します。

このdraw()呼び出しは、エンティティがputPixelまたはそれ自体の中の何かを呼び出すことを実際には意味しない場合があります。場合によっては、描画を担当するコードによって後でポーリングされるデータを更新するだけのこともあります。より高度なケースでは、レンダラーによって公開されたメソッドを呼び出すことによって、それ自体を「描画」します。私が今取り組んでいる技術では、オブジェクトはレンダラー呼び出しを使用してそれ自体を描画し、その後、すべてのフレームでレンダリングスレッドがすべてのオブジェクトによるすべての呼び出しを整理し、状態の変化などを最適化して、すべてを描画します(すべてこれはゲームロジックとは別のスレッドで発生するため、非同期レンダリングが行われます)。

責任の委任はプログラマ次第です。単純なピンポンゲームの場合、各オブジェクトが独自の描画(低レベルの呼び出しで完了)を完全に処理することは理にかなっています。それはスタイルと知恵の問題です。

さらに、この2番目の例では、私のボールは動き回る方法を知っていますが、パドルとの衝突検出をどのように処理しますか?ボールはパドルの知識が必要ですか?

したがって、ここで問題を解決する1つの自然な方法は、すべてのオブジェクトが衝突をチェックするためのある種のパラメーターとして他のすべてのオブジェクトを受け取るようにすることです。これはスケーリングが不十分で、奇妙な結果になる可能性があります。

各クラスになんらかのCollidableインターフェースを実装してから、ゲームの更新フェーズですべてのCollidableオブジェクトをループして衝突を処理し、必要に応じてオブジェクトの位置を設定する高レベルのコードを用意します。

繰り返しますが、ゲーム内のさまざまなものに対する責任を委任する必要があるかどうかについて、感覚を養う(または決心する)必要があります。レンダリングは孤立したアクティビティであり、真空のオブジェクトで処理できます。衝突と物理は一般的にできません。

最初の例では、GameクラスはBallとPaddleの両方への参照を持ち、それらの両方をいくつかのCollisionDetectionマネージャーなどに送信しますが、個々のコンポーネントがそれぞれの場合、さまざまなコンポーネントの複雑さをどのように処理しますか全部?

これについては、上記を参照してください。インターフェースを簡単にサポートする言語(Java、C#など)を使用している場合、これは簡単です。C ++を使用している場合、これは...興味深いかもしれません。私はメッセージングを使用してこれらの問題(クラスはメッセージを処理し、他は無視する)、コンポーネント構成のような他の人々を解決するのが好きです。

繰り返しになりますが、機能し、最も簡単です。


2
ああ、そして常に覚えておいてください。YAGNI(あなたはそれを必要としないでしょう)はここで本当に重要です。考えられるすべての問題を処理するための理想的な柔軟なシステムを考え出そうとすると、多くの時間を浪費し、その後、何も実行できなくなります。つまり、考えられるすべての結果に対して優れたマルチプレイヤーバックボーンが本当に必要な場合は、CORBAを使用しますよね?:)
ChrisE

5

最近、「エンティティシステム」を使用してシンプルなSpace Invadorsゲームを作成しました。これは、属性と動作を非常にうまく分離するパターンです。それを完全に理解するのに数回の反復が必要でしたが、設計されたいくつかのコンポーネントを取得すると、既存のコンポーネントを使用して新しいオブジェクトを作成することが非常に簡単になります。

あなたはこれを読むべきです:

http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/

それは非常に知識のある人によって頻繁に更新されます。また、具体的なコード例を含む唯一のエンティティシステムの説明でもあります。

私の反復は次のようになりました:

最初の反復には、Adamの説明どおりの「EntitySystem」オブジェクトがありました。しかし、私のコンポーネントにはまだメソッドがありました。「レンダリング可能な」コンポーネントにはpaint()メソッドがあり、positionコンポーネントにはmove()メソッドなどがありました。エンティティを具体化し始めたとき、メッセージの受け渡しを開始する必要があることに気付きましたコンポーネントとコンポーネントの更新の実行の順序付け...

それで、私は戻ってT-machinesブログを読みました。コメントスレッドには多くの情報が含まれており、それらの中で、コンポーネントには動作がないことを強調しており、動作はエンティティシステムによって提供されます。このように、コンポーネント間のメッセージを渡したり、コンポーネントの更新を注文したりする必要はありません。これは、システム実行のグローバルな順序によって注文が決定されるためです。OK。多分それはあまりにも抽象的です。

とにかく、イテレーション2の場合、これはブログから収集したものです。

EntityManager-コンポーネントの「データベース」として機能し、特定のタイプのコンポーネントを含むエンティティを照会できます。これは、インメモリデータベースによってバックアップすることもでき、高速アクセスが可能です。詳細については、t-machine part 5を参照してください。

EntitySystem-各システムは本質的に、一連のエンティティで動作する単なるメソッドです。各システムは、エンティティのコンポーネントx、y、zを使用して、作業を完了させます。したがって、コンポーネントx、y、zのエンティティーについてマネージャーにクエリを実行し、その結果をシステムに渡します。

エンティティ-longのような単なるID。エンティティは、一連のコンポーネントインスタンスを「エンティティ」にグループ化するものです。

コンポーネント-一連のフィールド....動作なし!ビヘイビアーを追加し始めると、単純なSpace Invadorsゲームでも面倒になり始めます。

編集:ちなみに、 'dt'は最後のメインループ呼び出しからのデルタ時間です

だから私のメインのInvadorsループはこれです:

Collection<Entity> entitiesWithGuns = manager.getEntitiesWith(Gun.class);
Collection<Entity> entitiesWithDamagable =
manager.getEntitiesWith(BulletDamagable.class);
Collection<Entity> entitiesWithInvadorDamagable = manager.getEntitiesWith(InvadorDamagable.class);

keyboardShipControllerSystem.update(entitiesWithGuns, dt);
touchInputSystem.update(entitiesWithGuns, dt);
Collection<Entity> entitiesWithInvadorMovement = manager.getEntitiesWith(InvadorMovement.class);
invadorMovementSystem.update(entitiesWithInvadorMovement);

Collection<Entity> entitiesWithVelocity = manager.getEntitiesWith(Velocity.class);
movementSystem.move(entitiesWithVelocity, dt);
gunSystem.update(entitiesWithGuns, System.currentTimeMillis());
Collection<Entity> entitiesWithPositionAndForm = manager.getEntitiesWith(Position.class, Form.class);
collisionSystem.checkCollisions(entitiesWithPositionAndForm);

最初は少し奇妙に見えますが、非常に柔軟です。最適化も非常に簡単です。コンポーネントタイプが異なると、異なるバッキングデータストアを使用して、検索を高速化できます。「フォーム」クラスの場合は、衝突検出のアクセスを高速化するために、それをクワッドツリーでサポートすることができます。

私はあなたのようです。私はベテランの開発者ですが、ゲームを書いた経験がありません。私はいくつかの時間をかけて与えられた開発パターンを調査しましたが、これは私の目に留まりました。それは決して物事を行う唯一の方法ではありませんが、非常に直感的で堅牢であることがわかりました。このパターンは、シリーズ「ゲームプログラミングジェム」の第6巻-http : //www.amazon.com/Game-Programming-Gems/dp/1584500492で正式に議論されたと思います。私は自分で本を読んだことはありませんが、ゲームプログラミングの事実上のリファレンスであると聞いています。


1

ゲームをプログラミングするときに従うべき明確な規則はありません。ゲーム全体で同じデザインパターンに従っている限り、どちらのパターンも完全に受け入れ可能です。ゲームにはさらに多くのデザインパターンがあり、そのうちのいくつかはOOPの上にさえないことに驚くでしょう。

ここで、私の好みに合わせて、最小限のオブジェクトを持ちながら、コードを動作(1つのクラス/スレッドで描画し、別のクラス/スレッドで更新)で分離することを好みます。並列化の方が簡単で、同時により少ないファイルで作業していることがよくあります。これは、手続き型の方法です。

しかし、ビジネスの世界から来ている場合は、自分ですべてを行う方法を知っているクラスを書くほうが快適でしょう。

繰り返しますが、自分にとって最も自然だと思うものを書くことをお勧めします。


あなたは私の質問を誤解したと思います。私は自分ですべてを行うクラスを持つのが嫌いだと言っています。私は...入力などの取り扱い、しかし、それはゲームループについて何も知らない、ただボールを論理的に表現する必要がありますボールのように落ちた
BFree

公平に言うと、ビジネスプログラミングでは、それを煮詰めると、2つのトレインがあります。それ自体をロードし、永続化し、ロジックを実行するビジネスオブジェクトと、DTO + AppLogic +リポジトリ/永続性レイヤー...
Nate

1

「Ball」オブジェクトには属性と動作があります。オブジェクトの固有の属性と動作を独自のクラスに含めることは理にかなっていると思います。Ballオブジェクトの更新方法はパドルの更新方法とは異なるため、これらがクラスに含まれることを保証する独自の動作であるのは理にかなっています。図面と同じです。多くの場合、パドルの描画方法とボールの描画方法には固有の違いがあり、個別のオブジェクトの描画メソッドを変更して、これらのニーズに合わせて、別のクラスで条件評価を実行して解決するよりも簡単だと思います。

Pongはオブジェクトと動作の点で比較的単純なゲームですが、数十または数百のユニークなゲームオブジェクトに入ると、2番目の例がすべてをモジュール化するのが少し簡単になる場合があります。

ほとんどの人は、サービスまたは静的クラスのいずれかを介してすべてのゲームオブジェクトで結果を利用できる入力クラスを使用します。


0

あなたがビジネスの世界でおそらく慣れているのは、抽象化と実装を分離することだと思います。

ゲーム開発者の世界では、通常、速度の観点から考える必要があります。オブジェクトの数が多いほど、コンテキストスイッチが発生し、現在の負荷によっては処理が遅くなる可能性があります。

私は志望のゲーム開発者ですが、プロの開発者なので、これは私の推測にすぎません。


0

いいえ、それは「正しい」または「間違っている」のではなく、単なる簡略化です。

プレゼンテーションとロジックを強制的に分離するシステムで作業している場合を除き、一部のビジネスコードはまったく同じです。

ここでは、VB.NETの例で、「ビジネスロジック」(数値を追加)がGUIイベントハンドラーで記述されています-http://www.homeandlearn.co.uk/net/nets1p12.html

個々のコンポーネントがすべて自分で処理する場合、さまざまなコンポーネントの複雑さをどのように処理しますか?

複雑になったら、それを除外することができます。

class Ball
{
    GraphicalModel ballVisual;
    void Draw()
    {
        ballVisual.Draw();
    }
};

0

開発における私の経験から、あなたが協力するグループで認められた実践がない限り、物事を行うための正しいまたは間違った方法はありません。ビヘイビアーやスキンなどを分離することを嫌う開発者からの反対に会いました。そして、太陽の下ですべてを含むクラスを作成する私の予約について、彼らは同じことを言うと確信しています。あなたはそれを書くだけでなく、明日と将来の日にあなたのコードを読むことができなければならなくて、それをデバッグし、そしておそらく次の反復でそれを構築する必要があることを覚えておいてください。あなた(そしてチーム)に役立つパスを選択する必要があります。他の人が使用するルートを選択すると(直感に反します)、速度が低下します。

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