複数のフレームを完了するゲームアクション


20

これまでにゲームプログラミングをしたことがないのですが、かなり簡単な質問です。

メインループが次のように見えるテトリスゲームを作成しているとします。

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            remove all complete rows
            move rows down so there are no gaps
            if we can spawn a new block
                spawn a new current block
            else
                game over

ゲーム内のすべてがこれまで瞬時に起こる-物事が瞬時に起動されている、私があれば行が即座などしかし、何が削除されていない事が瞬時に(すなわちアニメーションのこと)が起こるしたいですか?

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            ?? animate complete rows disappearing (somehow, wait over multiple frames until the animation is done)
            ?? animate rows moving downwards (and again, wait over multiple frames)
            if we can spawn a new block
                spawn a new current block
            else
                game over

私のPongクローンでは、これは問題ではありませんでした。すべてのフレームでボールを動かし、衝突をチェックしていたからです。

どうすればこの問題を回避できますか?確かにほとんどのゲームには、1フレーム以上かかるアクションが含まれ、他のアクションはアクションが完了するまで停止します。

回答:


11

これに対する従来の解決策は有限状態マシンであり、いくつかのコメントで提案されています。

私は有限状態マシンが嫌いです。

確かに、それらはシンプルであり、すべての言語でサポートされていますが、作業するのは非常に苦痛です。すべての操作は大量のバグを起こしやすいコピーアンドペーストコードを必要とし、小さな方法でエフェクトを微調整することはコードの大きな変更になる可能性があります。

それらをサポートする言語を使用できる場合は、コルーチンをお勧めします。次のようなコードを記述できます。

function TetrisPieceExplosion()
  for brightness = 0, 1, 0.2 do
    SetExplosionBrightness(brightness)
    coroutine.yield()
  end

  AllowNewBlockToFall()

  SpawnABunchOfParticles()

  RemoveBlockPhysics()

  for transparency = 0, 1, 0.5 do
    SetBlockTransparency(transparency)
    coroutine.yield()
  end

  RemoveBlockGraphics()
end

明らかに疑似コードですが、これは特殊効果の単純な線形説明であるだけでなく、アニメーションの終了中に新しいブロック簡単にドロップできることは明らかです。ステートマシンを使用してこれを達成することは、一般に恐ろしいことです。

私の知る限り、この機能はC、C ++、C#、Objective C、またはJavaでは簡単に使用できません。これは、ゲームロジックすべてにLuaを使用する主な理由の1つです。


他のOOP言語でこれらの行に沿って何かを実装することもできます。ある種のActionクラスと実行するアクションのキューを想像してください。アクションが完了したら、それをキューから削除し、次のアクションなどを実行します。ステートマシンよりもはるかに柔軟です。
bummzack

3
それは機能しますが、あなたはすべてのユニークなアクションのアクションから派生することを見ています。また、プロセスがキューにうまく収まることを前提としています。終了条件が未定義の分岐またはループが必要な場合、キューソリューションはすぐに故障します。ステートマシンアプローチよりも確かにクリーンですが、コルーチンは読みやすさでまだ
勝っ

確か

コルーチンは揺れ動くが、言語としてのLuaは他の多くの方法でひどいことをしているので、お勧めできない。
-DeadMG

(ネストされた関数呼び出しからではなく)トップレベルでのみ譲る必要がある限り、C#で同じことを達成できますが、そうです、Luaコルーチンがロックします。
11

8

Mike McShaffryによるGame Coding Completeから取っています。

彼は「プロセスマネージャー」について話します。これは、実行する必要のあるタスクのリストに要約されます。たとえば、プロセスは、剣を描くアニメーション(AnimProcess)、ドアを開くアニメーション、または場合によっては行を非表示にするアニメーションを制御します。

プロセスはプロセスマネージャーのリストに追加され、各フレームで繰り返され、各フレームでUpdate()が呼び出されます。非常によく似たエンティティですが、アクション用です。リストが終了したら、リストから削除するキルフラグがあります。

それらについてのもう一つのすてきなことは、次のプロセスへのポインタを持っていることによって、彼らがどのようにリンクできるかです。このように、アニメーション行プロセスは実際には次のもので構成されます。

  • 行が消えるためのAnimationProcess
  • ピースを削除するMovementProcess
  • スコアにポイントを追加するScoreProcess

(プロセスは1回使用のものである可能性があるため、条件付きで、またはX時間にわたって使用できます)

詳細が必要な場合は、お尋ねください。


3

アクションの優先キューを使用できます。アクションと時間を押し込みます。各フレームで時間を取得し、その時間より前に指定された時間を持つすべてのアクションをポップオフして実行します。ボーナス:アプローチはうまく並列化され、実際にほぼすべてのゲームロジックをこの方法で実装できます。


1

前のフレームと現在のフレームの時間差を常に知る必要がある場合、2つのことを行う必要があります。

-モデルを更新するタイミングを決定します。テトリスでは、行の削除が開始された時点で、行との衝突を避けたいため、アプリケーションの「モデル」から行を削除します。

-その後、遷移状態にあるオブジェクトを、一定期間にわたってアニメーション/イベントを解決する別のクラスに処理する必要があります。テトリスの例では、行をゆっくりとフェードアウトさせます(各フレームの不透明度を少し変更します)。不透明度が0になったら、行の上にあるすべてのブロックを1つ下に転送します。

これは最初は少し複雑に思えるかもしれませんが、これを理解するために、異なるクラスで多くを抽象化するようにしてください。これにより簡単になります。また、テトリスの行の削除など、時間がかかるイベントが「Fire and Forget」という種類のものであることを確認し、自動的に実行する必要があるすべてを処理する新しいオブジェクトを作成します。シーングラフから自分自身を削除します。


また、場合によっては、重い計算が1つの物理タイムステップの許容時間を超える可能性があります(衝突検出や経路計画など)。これらの場合、割り当てられた時間が使用されたときに計算から飛び出し、次のフレームで計算を続行できます。
ネイラー

0

ゲームを「有限状態マシン」と考える必要があります。ゲームはいくつかの状態のいずれかになります:あなたの場合、「入力を期待している」、「ピースが下に移動している」、「行が爆発している」。

状態によって異なることを行います。たとえば、「ピースが下に移動する」間、プレイヤーの入力を無視し、代わりにピースを現在の行から次の行にアニメーション化します。このようなもの:

if state == ACCEPTING_INPUT:
    if player presses any key:
        handle input
    row_timer = row_timer - time_since_last_frame
    if row_timer < 0:
        state = MOVING_PIECE_DOWN
elif state == MOVING_PIECE_DOWN:
    piece.y = piece.y + piece.speed*time_since_last_frame
    if piece.y >= target_piece_y:
        piece.y = target_piece_y
        state = ACCEPTING_INPUT
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.