「一定のゲーム速度最大FPS」ゲームループで非常に混乱


12

最近、ゲームループに関する次の記事を読みました:http : //www.koonsolo.com/news/dewitters-gameloop/

推奨される最後の実装は、私を深く混乱させます。私はそれがどのように機能するか理解していません、そしてそれは完全な混乱のように見えます。

私は原則を理解しています:ゲームを一定の速度で更新し、残っているものはできるだけ多くゲームをレンダリングします。

私はあなたが使用できないと考えています:

  • 25ティックの入力を取得
  • 975ティックのゲームをレンダリングする

あなたは2番目の最初の部分の入力を取得するだろうし、これは奇妙に感じるので、アプローチ?それとも、記事で何が起こっているのですか?


基本的に:

while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)

それはどのように有効ですか?

彼の価値を仮定しましょう。

MAX_FRAMESKIP = 5

メインゲームループが言う前に、初期化の直後に割り当てられたnext_game_tickを想定しましょう... 500。

最後に、ゲームにSDLとOpenGLを使用しており、OpenGLはレンダリングのみに使用されているためGetTickCount()、SDL_Initが呼び出されてからの時間が返されると仮定します。

SDL_GetTicks -- Get the number of milliseconds since the SDL library initialization.

ソース:http : //www.libsdl.org/docs/html/sdlgetticks.html

著者もこれを想定しています:

DWORD next_game_tick = GetTickCount();
// GetTickCount() returns the current number of milliseconds
// that have elapsed since the system was started

whileステートメントを展開すると、次のようになります。

while( ( 750 > 500 ) && ( 0 < 5 ) )

next_game_tick割り当てられてから時間が経過したため、750 。loopsあなたが記事で見ることができるようにゼロです。

whileループに入ったので、ロジックを実行して入力を受け入れましょう。

やだやだやだ。

whileループの最後に、メインゲームループ内にあることを思い出します。

next_game_tick += SKIP_TICKS;
loops++;

whileコードの次の反復がどのように見えるかを更新しましょう

while( ( 1000 > 540 ) && ( 1 < 5 ) )

1000は、GetTickCount()が呼び出されるループの次の挿入に到達する前に、入力を取得して処理を行う時間が経過したためです。

540、なぜならコードでは1000/25 = 40、したがって、500 + 40 = 540

1ループが1回反復されたため

5、あなたは理由を知っています。


だから、このWhileループは明らかに依存しMAX_FRAMESKIPているのでTICKS_PER_SECOND = 25;、ゲームが正しく実行されることを意図している方法ではありませんか?

これをコードに実装したときに、ユーザー入力を処理し、記事の作者が彼のサンプルコードで持っているものにゲームを描画するように関数の名前を変更しただけで、正しく追加できることは驚きではありませんでした。

fprintf( stderr, "Test\n" );ゲームが終了するまで印刷されないwhileループの内側に配置しました。

このゲームループは、可能な限り高速にレンダリングしながら、1秒間に25回実行されることが保証されていますか?

私にとっては、何か巨大なものを見逃していない限り、それは...何もないように見えます。

そして、このwhileループの構造は、おそらく1秒間に25回実行されてから、記事の冒頭で前に述べたとおりにゲームを更新しているのではないでしょうか?

その場合、次のような単純なことができないのはなぜですか。

while( loops < 25 )
{
    getInput();
    performLogic();

    loops++;
}

drawGame();

そして、他の方法で補間をカウントします。

私の非常に長い質問は許してください。しかし、この記事は私にとって良いことよりも害をもたらしました。今、私はひどく混乱しています-そして、これらのすべての質問のために、適切なゲームループを実装する方法がわかりません。


1
暴言は記事の著者に向けられた方が良い。あなたの客観的な質問はどこですか?
アンコ

3
このゲームループは有効ですか?私のテストでは、1秒間に25回実行するための正しい構造がありません。その理由を説明してください。また、これは暴言ではありません、これは一連の質問です。絵文字を使用する必要がありますが、怒っているように見えますか?
tsujp

2
あなたの質問は「このゲームループについて何を理解していないのか」に要約され、大胆なフォントの単語がたくさんあるので、少なくとも腹立たしいものとして出てきます。
キルビネーター

@Kirbinatorそれはありがたいことですが、この記事で私が異常だと思うものをすべて根底に収めようとしていたので、浅く空っぽの質問ではありません。とにかく、私はいくつかの有効なポイントを持っていると思うと思います-結局それは教えることを試みているが、そのような良い仕事をしていない記事です。
tsujp

7
誤解しないでください、それは良い質問です、ちょうど80%短くすることができます。
キルビネーター

回答:


8

私は著者が小さなエラーを犯したと思う:

while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)

あるべき

while( GetTickCount() < next_game_tick && loops < MAX_FRAMESKIP)

つまり、まだ次のフレームを描画する時間ではなく、MAX_FRAMESKIPほど多くのフレームをスキップしていない限り、待機する必要があります。

また、彼next_game_tickがループ内で更新する理由もわかりませんが、それは別のエラーだと思います。フレームの開始時に、次のフレームがいつあるべきかを決定できます(固定フレームレートを使用する場合)。これnext game tickは、更新およびレンダリング後の残り時間に依存しません。

著者はまた別の一般的なエラーを作ります

残っているものは何でも可能な限りゲームをレンダリングします。

これは、同じフレームを複数回レンダリングすることを意味します。著者はそれを認識しています:

ゲームは毎秒50回安定して更新され、レンダリングは可能な限り高速に行われます。レンダリングが1秒間に50回以上行われると、後続のフレームは同じになるため、実際の視覚的なフレームは1秒間に最大50フレームで表示されることに注意してください。

これにより、GPUが不必要な作業を行うだけで、レンダリングに予想よりも長い時間がかかると、次のフレームで意図したよりも遅く作業を開始する可能性があるため、OSに譲って待機する方がよいでしょう。


+投票。んー。これは確かに、その後何をすべきかについて私を困惑させます。あなたの洞察力に感謝します。私はおそらく遊び回るでしょう、問題は本当にFPSを制限するか、FPSを動的に設定することに関する知識とその方法です。私の考えでは、ゲームは誰にとっても同じペースで実行されるように、固定入力処理が必要です。これは単純な2D Platformer MMOのみです(非常に長い目で見れば)
tsujp

ループは正しいように見えますが、そのためにnext_game_tickをインクリメントしています。低速および高速のハードウェアで一定の速度でシミュレーションを実行し続けるためにあります。レンダリングコードを「できるだけ速く」(または物理学よりも高速に)実行するのは、レンダリングコードに補間があり、高速オブジェクトなど(記事の終わり)をよりスムーズにする場合にのみ意味がありますが、出力デバイス(画面)が表示できるものよりも多くレンダリングしています。
ドッティ

したがって、彼のコードは有効な実装になりました。そして、私たちはこの無駄で生きなければなりませんか?または、私はこれを調べることができる方法があります。
tsujp

OSが制御をいつ戻すかを十分に把握している場合は、OSに譲歩して待機することをお勧めします。
カイロタン

この答えに投票することはできません。彼は補間要素を完全に失っており、答えの後半全体が無効になっています。
-AlbeyAmakiir

4

私はそれを少し簡素化するのが最善かもしれません:

while( game_is_running ) {

    current = GetTickCount();
    while(current > next_game_tick) {
        update_game();

        next_game_tick += SKIP_TICKS;
    }
    display_game();
}

whilemainloop内のloopは、今までの場所から現在の場所までシミュレーションステップを実行するために使用されます。update_game()関数は常にSKIP_TICKS、最後の呼び出しから時間が経過したと仮定する必要があります。これにより、低速および高速のハードウェア上でゲームの物理が一定の速度で実行されます。

移動next_game_tick量だけ増加さSKIP_TICKSせると、現在の時間に近づきます。これが現在の時間より大きくなると、中断(current > next_game_tick)し、メインループは現在のフレームをレンダリングし続けます。

レンダリング後、次の呼び出しGetTickCount()は新しい現在時刻を返します。この時間がそれよりも長い場合はnext_game_tick、シミュレーションで既に1-Nステップより遅れているため、同じ一定速度でシミュレーションのすべてのステップを実行する必要があります。この場合、それが低い場合、同じフレームを再度レンダリングします(補間がない場合)。

元のコードでは、離れすぎている場合(MAX_FRAMESKIP)ループの数が制限されていました。これは実際に何かを表示するだけで、たとえば、一時停止から再開したり、ゲームが長時間デバッガーで一時停止した場合(GetTickCount()時間中に停止しないと仮定)、時間に追いつくまでロックされているようには見えません。

あなたが内部補間を使用していない場合に無駄な同じフレームレンダリングを取り除くにはdisplay_game()、次のようなifステートメントでそれをラップすることができます:

while (game_is_running) {
    current = GetTickCount();
    if (current > next_game_tick) {
        while(current > next_game_tick) {
            update_game();

            next_game_tick += SKIP_TICKS;
        }
    display_game();
    }
    else {
    // could even sleep here
    }
}

これはこれに関する良い記事でもあります:http : //gafferongames.com/game-physics/fix-your-timestep/

また、fprintfゲームが終了したときに出力がフラッシュされなかったという理由だけの場合もあります。

私の英語についてすみません。


4

彼のコードは完全に有効に見えます。

while最後のセットのループを考えてみましょう:

// JS / pseudocode
var current_time = function () { return Date.now(); }, // in ms
    framerate = 1000/30, // 30fps
    next_frame = current_time(),

    max_updates_per_draw = 5,

    iterations;

while (game_running) {

    iterations = 0;

    while (current_time() > next_frame && iterations < max_updates_per_draw) {
        update_game(); // input, physics, audio, etc

        next_frame += framerate;
        iterations += 1;
    }

    draw();
}

「ゲームの実行中に現在の時間を確認します-実行中のフレームレートの集計よりも大きく、5フレーム未満の描画をスキップしてから、描画をスキップして入力を更新し、物理学:シーンを描画し、次の更新反復を開始する」

更新が行われるたびに、理想的なフレームレートで「next_frame」時間を増やします。その後、再び時間を確認します。現在の時刻がnext_frameを更新する時間よりも短い場合は、更新をスキップして、取得したものを描画します。

current_timeの方が大きい場合(どこかで問題が発生したり、マネージ言語のガベージコレクションが大量に発生したり、C ++でマネージメモリを実装したりしたため、最後の描画プロセスに非常に時間がかかったと想像してください)描画がスキップnext_frameされ、更新が時間どおりになるまで別の余分なフレームで更新されるか、プレイヤーが見ることができるように絶対に描画しなければならない十分なフレームの描画をスキップします彼らがしていること。

マシンが超高速である場合、またはゲームが超シンプルでcurrent_timeある場合、next_frame頻度が低くなる可能性があります。つまり、これらの時点では更新されていません。

ここで補間が行われます。同様に、ループの外側で宣言された別のブールを使用して、「ダーティ」スペースを宣言できます。

更新ループ内で、を設定しdirty = true、実際に更新を実行したことを示します。

次に、単に呼び出すのdraw()ではなく、あなたは言うでしょう:

if (is_dirty) {
    draw(); 
    is_dirty = false;
}

その後、スムーズなモーションのための補間が欠落していますが、(状態間の補間ではなく)更新が実際に行われている場合にのみ更新していることを確認しています。

勇気があるなら、「Fix Your Timestep!」という記事があります。GafferOnGamesによる。
問題へのアプローチは少し異なりますが、私はそれをほとんど同じことをするよりきれいなソリューションだと考えています(言語の機能と、ゲームの物理計算をどの程度気にするかによって異なります)。

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