ターン制ゲームでの世界の状態とアニメーションの分離


9

ターン制ゲームでアニメーションと世界の状態の分離をどのように扱いますか?現在、2Dグリッドベースのゲームに取り組んでいます。以下のコードは、わかりやすくするために簡略化されています。

アクターが移動するとき、クリーチャーがアニメートして新しい位置に移動する間、ターンのフローを一時停止したいと思います。そうしないと、画面が世界の状態より大幅に遅れて、奇妙な外観になります。また、ゲームの流れを妨げないアニメーションが必要です。パーティクルエフェクトは、ゲームプレイに影響を与えずに複数のターンにわたって展開できます。

私がやったことは、ブロッキングアニメーションとノンブロッキングアニメーションと呼ばれる2種類のアニメーションを紹介することです。プレイヤーが移動したい場合、実行されるコードは

class Actor {
    void move(PositionInfo oldPosition, PositionInfo newPosition) {
        if(isValidMove(oldPosition, newPosition) {
             getGame().pushBlockingAnimation(new MoveAnimation(this, oldPosition, newPosition, ....));
             player.setPosition(newPosition);
        }
    }
}

次に、メインの更新ループが行います。

class Game {
    void update(float dt) {
        updateNonBlockingAnimations(dt); //Effects like particle explosions, damage numbers, etc. Things that don't block the flow of turns.
        if(!mBlockingAnimations.empty()) {
            mBlockingAnimations.get(0).update(dt);
        } else {
             //.. handle the next turn. This is where new animations will be enqueued..//
        }
        cleanUpAnimations(); // remove finished animations
    }
}

...アニメーションはアクターの画面位置を更新します。

私が実装しているもう1つのアイデアは、複数のブロッキングアニメーションが同時に更新される並行ブロッキングアニメーションを作成することですが、次のターンはそれらがすべて完了するまで発生しません。

これは物事を行うための健全な方法のように見えますか?誰かが何か提案、または他の同様のゲームがそのようなことをする方法への参照さえありますか?

回答:


2

あなたのシステムがあなたのために働くなら、私はそれをしない理由はないと思います。

そうですね、理由の1つは、「player.setPosition(newPosition);」の結果を簡単に判断できないと、面倒になるかもしれません。もう。

例(構成するだけ):トラップの上にsetPositionを置いてプレーヤーを移動するとします。「player.setPosition」自体への呼び出しは、トラップリスナーコードなどの他の呼び出しをトリガーします。トラップリスナーコードは、それ自体の上に「痛むスプラッシュ」を表示するブロッキングアニメーションをプッシュします。

問題が表示されます。スプラッシュは、プレイヤーがトラップに向かって移動すると同時に表示されます。(ここではこれを望まないが、移動が完了した直後にトラップアニメーションを再生したいとします;))。

したがって、これらの「pushBlockingAnimation」呼び出しの後に行うことを覚えておいて、アクションのすべての結果を維持できる限り、すべてが問題ありません。

ゲームロジックを「何かをブロックする」にラップし始める必要がある場合があります。これは、アニメーションが終了した後に実行するためにキューに入れることができるクラスです。または、コールバック「callThisAfterAnimationFinishes」をpushBlockingAnimationに渡し、setPositionをコールバック関数に移動します。

別のアプローチは、スクリプト言語です。たとえば、Luaには「coroutine.yield」があり、特別な状態で関数を返すため、後で呼び出して次のステートメントに進むことができます。このようにすると、コードの「通常の」プログラムフローの外観を乱雑にすることなく、アニメーションの終わりがゲームロジックを実行するのを簡単に待つことができます。(コードは、コールバックを渡したり、複数の関数にゲームロジックを散らかしたりするのではなく、「playAnimation(); setPosition();」のように見えることを意味します)。

Unity3D(および私が知っている少なくとも1つの他のゲームシステム)は、C#の「yield return」ステートメントを備えており、これをC#コードで直接シミュレートします。

// instead of simple call to move(), you have to "iterate" it in a foreach-like loop
IEnumerable<Updatable> move(...) {
    // playAnimation returns an object that contain an update() and finished() method.
    // the caller will not iterate over move() again until the object is "finished"
    yield return getGame().playAnimation(...)
    // the next statement will be executed *after* the animation finished
    player.setPosition(newPosition);
}

もちろん、このスキームでは、move()の呼び出しによってすべてのダーティーな作業が行われます。この手法は、だれがどこから呼び出したのかが正確にわかる特定の関数にのみ適用することになるでしょう。したがって、これは、他のエンジンがどのように編成されているかを基本的な考えとしてのみ考えてください。おそらく、現在の特定の問題に対しては、あまりにも複雑すぎます。

実際の問題が発生するまでは、pushBlockingAnimationのアイデアに固執することをお勧めします。次に変更します。;)


お返事を書いていただきありがとうございます。本当に感謝しています。
mdkess
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.