ゲームロジックをアニメーションや描画ループから分離する方法は何ですか?


9

私は以前Flashゲームを作成したことがありましたが、MovieClipsなどを使用して、アニメーションをゲームロジックから分離しました。今、私はAndroid向けのゲームを作ることに挑戦していますが、これらを分離することに関するゲームプログラミング理論はまだ混乱しています。私はゲーム以外のWebアプリケーション開発のバックグラウンドを持っているので、MVCのようなパターンに精通していて、ゲームプログラミングに近づくにつれてその考え方にとらわれています。

たとえば、タイルグリッドのデータを含むゲームボードクラスに、それぞれプロパティを含むタイルクラスのインスタンスを含めることで、ゲームを抽象化するようなことをしたいと思います。ドローループにこれへのアクセスを許可し、ゲームボード上の各タイルのプロパティに基づいてゲームボードを描画させることができますが、アニメーションがどこに行くべきか正確にはわかりません。私の知る限り、アニメーションは抽象化されたゲームロジック(モデル)と描画ループ(ビュー)の間に位置しています。私のMVCの考え方では、アニメーションが実際にどこに行くことになっているのかを判断しようとするのは面倒です。モデルのようにかなりのデータが関連付けられますが、フレームに依存しないアニメーションなどを実現するには、描画ループと密接に結合する必要があるようです。

この考え方から抜け出し、ゲームにとってより意味のあるパターンについて考え始めるにはどうすればよいですか?

回答:


6

アニメーションは、ロジックとレンダリングの間で完全に分割できます。アニメーションの抽象的なデータ状態は、グラフィックAPIがアニメーションをレンダリングするために必要な情報です。

たとえば2Dゲームでは、描画する必要があるスプライトシートの現在の部分を表示する領域を示す四角形の領域にすることができます(たとえば、キャラクターのさまざまなステップを含む30 x 80x80の図面で構成されるシートがある場合)ジャンプ、座って、移動など)。これは、レンダリングに必要のないあらゆる種類のデータにすることもできますが、現在のアニメーションステップが期限切れになるまでの残り時間やアニメーションの名前( "walking"、 "standing"など)など、アニメーションの状態自体を管理するために使用できます。等)それはすべてあなたが望むように表現することができます。それがロジックの部分です。

レンダリング部分では、通常どおりにそれを行い、モデルからその長方形を取得し、レンダラーを使用して実際にグラフィックスAPIの呼び出しを行います。

コード内(ここではC ++構文を使用):

class Sprite //Model
{
    private:
       Rectangle subrect;
       Vector2f position;
       //etc.

    public:
       Rectangle GetSubrect() 
       {
           return subrect;
       }
       //etc.
};

class AnimatedSprite : public Sprite, public Updatable //arbitrary interface for classes that need to change their state on a regular basis
{
    AnimationController animation_controller;
    //etc.
    public:
        void Update()
        {
            animation_controller.Update(); //Good OOP design ;) It will take control of changing animations in time etc. for you
            this.SetSubrect(animation_controller.GetCurrentAnimation().GetRect());
        }
        //etc.
};

それがデータです。レンダラーはそのデータを取得して描画します。通常のスプライトとアニメーションのスプライトは同じ方法で描画されるため、ここで多態性を使用できます!

class Renderer
{
    //etc.
    public:
       void Draw(const Sprite &spr)
       {
           graphics_api_pointer->Draw(spr.GetAllTheDataThatINeed());
       }
};

TMV:

別の例を考えました。あなたがRPGを持っているとしましょう。たとえば、世界地図を表すモデルでは、世界でのキャラクターの位置をマップ上のタイル座標として保存する必要があります。ただし、キャラクターを移動すると、一度に数ピクセルずつ歩いて次の正方形に移動します。この「タイル間の」位置をアニメーションオブジェクトに保存しますか?キャラクターがマップ上の次のタイル座標に最終的に「到着」したときに、モデルをどのように更新しますか?

世界地図は、プレーヤーの位置を直接認識していません(Vector2fなど、プレーヤーの位置を直接格納するようなものはありません。代わりに、AnimatedSpriteから派生するプレーヤーオブジェクト自体への直接参照があります)そのため、簡単にレンダラーに渡して、必要なデータをすべて取得できます。

ただし、一般に、タイルマップはすべてを実行できるべきではありません。すべてのタイルを管理するクラス「TileMap」があり、それに渡したオブジェクトとマップ上のタイル。次に、私は別の「RPGMap」クラスを持っているか、またはそれを呼び出したいと思います。これには、タイルマップへの参照とプレーヤーへの参照の両方があり、実際のUpdate()呼び出しをプレーヤーとタイルマップ。

プレーヤーが動いたときにモデルを更新する方法は、何をしたいかによって異なります。

プレイヤーはタイル間を独立して移動できますか(ゼルダスタイル)?入力を処理し、それに応じてプレーヤーをフレームごとに移動するだけです。または、プレーヤーに「右」を押して、キャラクターが自動的に1タイル右に移動するようにしますか?RPGMapクラスがプレイヤーが目的地に到着するまでプレイヤーの位置を補間し、その間すべての移動キー入力処理をロックします。

どちらの方法でも、自分で簡単にしたい場合、すべてのモデルは、(変数の値を変更するだけでなく)実際に更新するロジックが必要な場合にUpdate()メソッドを持ちます-コントローラーを譲らないMVCパターンでは、コードを「1つ上のステップ」(コントローラー)からモデルに移動するだけで、コントローラーが行うのは、モデルのこのUpdate()メソッドを呼び出すことだけです(この場合のコントローラーはRPGMap)。ロジックコードは簡単に入れ替えることができます。クラスのコードを直接変更するか、まったく異なる動作が必要な場合は、モデルクラスから派生させ、Update()メソッドのみをオーバーライドできます。

このアプローチは、メソッド呼び出しなどを大幅に削減します。これは、純粋なMVCパターンの主な欠点の1つでした(GetThis()GetThat()を非常に頻繁に呼び出すことになります)。これにより、コードが長くなり、少し読みづらく、速度も遅い-それは、そのような多くのものを最適化するコンパイラによって処理されるかもしれませんが。


アニメーションデータを、ゲームロジックを含むクラス、ゲームループを含むクラス、または両方から分離して保持しますか?また、アニメーションデータを実際の画面描画に変換する方法を理解するのは、ループまたはループを含むクラス次第です。多くの場合、スプライトシートのセクションを表す四角形を取得し、それを使用してスプライトシートからビットマップ描画をクリップするのと同じくらい簡単ではありません。
TMV '25年

別の例を考えました。あなたがRPGを持っているとしましょう。たとえば、世界地図を表すモデルでは、世界でのキャラクターの位置をマップ上のタイル座標として保存する必要があります。ただし、キャラクターを移動すると、一度に数ピクセルずつ歩いて次の正方形に移動します。この「タイル間の」位置をアニメーションオブジェクトに保存しますか?キャラクターがマップ上の次のタイル座標に最終的に「到着」したときに、モデルをどのように更新しますか?
TMV '25年

コメントでは文字数が足りないため、質問の回答を編集しました。
TravisG

すべてを正しく理解している場合:
TMV

ビュー内に「Animator」クラスのインスタンスを含めることができ、ビューによってフレームごとに呼び出されるパブリック「update」メソッドが含まれます。updateメソッドは、その内部にあるさまざまな種類の個々のアニメーションオブジェクトのインスタンスの「update」メソッドを呼び出します。アニメーターとその内部のアニメーションには、モデルへの参照(コンストラクターに渡される)があるため、アニメーションによって変更される場合にモデルデータを更新できます。次に、描画ループで、ビューで理解して描画できる方法で、アニメーター内のアニメーションからデータを取得します。
TMV 2011年

2

必要に応じてこれで開発できますが、ループで描画するように指示される中央レンダラーがあります。のではなく

handle input

for every entity:
    update entity

for every entity:
    draw entity

私のようなシステムがあります

handle input (well, update the state. Mine is event driven so this is null)

for every entity:
    update entity //still got game logic here

renderer.draw();

レンダラークラスは、オブジェクトの描画可能なコンポーネントへの参照のリストを保持するだけです。これらは、簡単にするためにコンストラクタで割り当てられます。

あなたの例として、私はいくつかのタイルを持つGameBoardクラスを持っています。各タイルは明らかにその位置を知っており、私はある種のアニメーションを想定しています。それをタイルが所有するある種のアニメーションクラスに分解し、それ自体の参照をレンダラークラスに渡します。そこには、すべて分離されています。Tileを更新すると、アニメーションのUpdateが呼び出されます。または、タイル自体が更新されます。ときにRenderer.Draw()呼ばれて、それがアニメーションを描画します。

フレームに依存しないアニメーションは、描画ループを処理するために必要以上のものではありません。


0

私は最近自分自身でパラダイムを学んでいるので、この答えが不完全であれば、誰かがそれに追加すると確信しています。

ゲームデザインで最も理にかなっていると思われる方法論は、ロジックを画面出力から分離することです。

ほとんどの場合、マルチスレッドアプローチを使用します。そのトピックに慣れていない場合は、それ自体が質問です。wikiの入門書をご覧ください。基本的には、ゲームロジックを1つのスレッドで実行し、データの整合性を確保するためにアクセスする必要がある変数をロックする必要があります。ロジックループが信じられないほど高速である場合(スーパーメガアニメーションの3Dポン?)、スレッドを短い時間スリープさせることにより、ループが実行する頻度を修正しようとすることができます(ゲームの物理ループに関するこのフォーラムでは120 Hzが提案されています)。同時に、他のスレッドは更新された変数を使用して画面を再描画し(他のトピックでは60 hzが提案されています)、変数にアクセスする前に変数のロックを要求します。

その場合、アニメーションや遷移などが描画スレッドに入りますが、ゲームロジックスレッドは何も実行しない(または何か別の処理を実行する)必要があることを示すフラグ(おそらくグローバル状態変数)を通知する必要があります...新しいマップパラメータ)。

同時実行性について理解すると、残りの部分はかなりわかりやすくなります。同時実行の経験がない場合は、フローがどのように発生するかを理解できるように、いくつかの簡単なテストプログラムを作成することを強くお勧めします。

お役に立てれば :)

[編集]マルチスレッドをサポートしていないシステムでも、アニメーションは描画ループに入る可能性がありますが、別の何かが発生していることをロジックに通知し、処理を継続しないように状態を設定する必要があります現在のレベル/マップ/など...


1
私はここに同意しません。ほとんどの場合、特に小さなゲームの場合は、マルチスレッドを使いたくありません。
共産主義者のダック

@TheCommunistDuck十分に公正で、マルチスレッディングのオーバーヘッドと複雑さは間違いなく過剰になり、さらにゲームが小さい場合は迅速に更新できるはずです。
スティーブン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.