フレームレートがオブジェクトの速度に影響しています


9

私はJavaでゼロからゲームエンジンを構築する実験をしており、いくつか質問があります。私のメインゲームループは次のようになります。

        int FPS = 60;
        while(isRunning){
            /* Current time, before frame update */
            long time = System.currentTimeMillis();
            update();
            draw();
            /* How long each frame should last - time it took for one frame */
            long delay = (1000 / FPS) - (System.currentTimeMillis() - time);
            if(delay > 0){
                try{
                    Thread.sleep(delay);
                }catch(Exception e){};
            }
        }

ご覧のとおり、フレームレートを60FPSに設定していdelayます。これは計算に使用されます。遅延により、各フレームが次のフレームをレンダリングする前に同じ時間かかることが保証されます。私のupdate()関数x++では、次のように描画するグラフィックスオブジェクトの水平方向の値を増加させています。

bbg.drawOval(x,40,20,20);

私を混乱させるのはスピードです。FPS150 に設定すると、レンダリングされた円は速度を非常に速く通過しますがFPS、30に設定すると、画面を半分の速度で移動します。フレームレートは、レンダリングの「滑らかさ」だけに影響し、レンダリングされるオブジェクトの速度には影響しませんか?大部分が欠けていると思いますが、説明が必要です。


4
ゲームループについての良い記事を以下に示します。タイムステップを修正します
Kostya Regent

2
補足として、通常、ループの外側ですべてのループを実行する必要のないものを配置しようとします。コードでは、1000 / FPS除算が行われ、ループの前に結果が変数割り当てられwhile(isRunning)ます。これは、何回か無駄に何かを行うためのいくつかのCPU命令を節約するのに役立ちます。
ヴァイランクール

回答:


21

フレームごとに1ピクセルずつ円を移動しています。レンダリングループが30 FPSで実行される場合、円がピクセル/秒で30移動することは大きな驚きではありません。

この問題に対処するには、基本的に3つの方法があります。

  1. ただ1つのフレームレートを選び、それに固執します。 これは、多くの古いゲームが行ったものです。50または60 FPSの固定レートで実行され、通常は画面のリフレッシュレートに同期され、その固定時間内に必要なすべてを実行するようにゲームロジックを設計します。何らかの理由でそれが起こらなかった場合、ゲームはフレームをスキップする(またはクラッシュする可能性がある)だけで、描画とゲームの物理の両方を半分の速度に実質的に遅くします。

    具体的には、ゲームのような使用される機能というハードウェアスプライト衝突検出がほとんどに持っていた彼らのゲームロジックが密接に固定レートでのハードウェアで行われていたレンダリング、に縛られたので、このような作品。

  2. ゲームの物理学に可変タイムステップを使用します。 基本的に、これはゲームループを次のように書き直すことを意味します。

    long lastTime = System.currentTimeMillis();
    while (isRunning) {
        long time = System.currentTimeMillis();
        float timestep = 0.001 * (time - lastTime);  // in seconds
        if (timestep <= 0 || timestep > 1.0) {
            timestep = 0.001;  // avoid absurd time steps
        }
        update(timestep);
        draw();
        // ... sleep until next frame ...
        lastTime = time;
    }

    そして、内部update()では、変数のタイムステップを考慮に入れるために物理式を調整します。

    speed += timestep * acceleration;
    position += timestep * (speed - 0.5 * timestep * acceleration);

    この方法の1つの問題は、物理を(主に)タイムステップから独立せることは難しい場合があることです。プレイヤーがジャンプできる距離をフレームレートに依存したくない場合。上で示した式は、一定の加速、たとえば重力下でうまく機能します(そして、リンクされたポストの1つは、加速度が時間とともに変化しても非常にうまく機能します)が、可能な限り完璧な物理式を使用しても、フロートで作業すると、特に、「数値ノイズ」が少し発生し、正確な再生が不可能になる場合があります。それがあなたが望むかもしれないと思うなら、あなたは他の方法を好むかもしれません。

  3. 更新と描画のステップを分離します。 ここでは、固定タイムステップを使用してゲームの状態を更新し、各フレーム間でさまざまな数の更新を実行するという考え方です。つまり、ゲームループは次のようになります。

    long lastTime = System.currentTimeMillis();
    while (isRunning) {
        long time = System.currentTimeMillis();
        if (time - lastTime > 1000) {
            lastTime = time;  // we're too far behind, catch up
        }
        int updatesNeeded = (time - lastTime) / updateInterval;
        for (int i = 0; i < updatesNeeded; i++) {
            update();
            lastTime += updateInterval;
        }
        draw();
        // ... sleep until next frame ...
    }

    知覚される動きをよりスムーズにするために、draw()前のゲーム状態と次のゲーム状態の間でオブジェクトの位置などをスムーズに補間する方法が必要になる場合もあります。つまり、次のように、正しい補間オフセットをdraw()メソッドに渡す必要があります。

        int remainder = (time - lastTime) % updateInterval;
        draw( (float)remainder / updateInterval );  // scale to 0.0 - 1.0

    また、update()メソッドが実際にゲームの状態を1ステップ先に計算する必要があり(高次のスプライン補間を実行する場合は、場合によってはいくつか)、以前のオブジェクトの位置を保存してから更新し、draw()メソッドが補間できるようにする必要があります。それらの間の。(オブジェクトの速度と加速度に基づいて予測された位置を推定することも可能ですが、特にオブジェクトが複雑な方法で移動し、予測が失敗することが多い場合、これはぎくしゃくした見えます。)

    補間の利点の1つは、一部の種類のゲームでは、スムーズなモーションの錯覚を維持しながら、ゲームロジックの更新レートを大幅に下げることができることです。たとえば、1秒あたり30回から60回の補間フレームを描画しながら、ゲーム状態を1秒あたり5回だけ更新できる場合があります。これを行う際に、ゲームロジックを描画にインターリーブする(つまり、update()メソッドにパラメーターを設定して、完全な更新のx%だけを実行してから戻るように指示する)、および/またはゲームの物理演算を実行することを検討することもできます/ロジックとレンダリングコードを別々のスレッドで(同期の不具合に注意!)

もちろん、これらの方法をさまざまな方法で組み合わせることも可能です。たとえば、クライアントサーバーマルチプレイヤーゲームでは、サーバー(何も描画する必要がない)に一定の時間ステップで更新を実行させ(物理特性と正確な再現性を維持するため)、クライアントに予測更新(パフォーマンスが向上するように、不一致の場合は可変タイムステップでサーバーによってオーバーライドされます。補間と可変タイムステップ更新を効果的に混在させることもできます。たとえば、今説明したクライアントサーバーシナリオでは、クライアントがサーバーよりも短い更新タイムステップを使用することにそれほど意味がありません。そのため、クライアントタイムステップに下限を設定し、描画ステージで補間して、 FPS。

(編集:ゲームループの実行中にコンピューターが一時的に中断されたり、1秒以上フリーズしたりする場合など、ばかげた更新間隔/カウントを回避するためのコードが追加されました。その必要性を思い出してくれたMooing Duckに感謝します。 。)


1
お時間を割いていただき、ありがとうございました。本当にありがとうございました。私は#3のアプローチが本当に好きです。それは私にとって最も理にかなっています。2つの質問、updateIntervalは何によって定義されているのですか、なぜそれで除算するのですか?
Carpetfizz 2015

1
@Carpetfizz:updateIntervalゲームの状態を更新する間隔をミリ秒単位で指定します。たとえば、1秒あたり10回の更新の場合、次のように設定しupdateInterval = (1000 / 10) = 100ます。
Ilmari Karonen

1
currentTimeMillis単調な時計ではありません。nanoTime代わりに使用してください。ただし、ネットワーク時間をゲーム内の速度と混乱させるために同期させたい場合は除きます。
user253751

@MooingDuck:よくわかりました。直したと思う。ありがとう!
Ilmari Karonen

@IlmariKaronen:実際、コードを見ると、それだけの方が簡単かもしれませんwhile(lastTime+=updateInterval <= time)。しかしそれは単なる考えであり、修正ではありません。
Mooing Duck

7

現在、フレームがレンダリングされるたびにコードが実行されています。フレームレートが指定したフレームレートよりも高いまたは低い場合、更新のタイミングが同じではないため、結果が変化します。

これを解決するには、デルタタイミングを参照してください。

Delta Timingの目的は、複雑なグラフィックスや大量のコードを処理しようとするコンピューターでの遅延の影響を排除することです。遅延に関係なく、最終的に同じ速度で移動するようにオブジェクトの速度まで加算します。

これをする:

現在と最後の呼び出しの間の時間をミリ秒単位で保持するタイマーを毎秒フレームごとに呼び出すことによって行われます。

次に、デルタ時間に、時間ごとに変更する値を掛ける必要があります。例えば:

distanceTravelledSinceLastFrame = Speed * DeltaTime

3
また、最小デルタタイムと最大デルタタイムに上限を設定します。コンピュータが休止状態になってから再開した場合、画面から起動しないようにする必要があります。奇跡が現れtime()て同じものが2回返される場合、div / 0エラーが発生して処理が無駄になることは望ましくありません。
Mooing Duck

@MooingDuck:それは非常に良い点です。私はそれを反映するために自分の答えを編集しました。(通常、通常のゲーム状態の更新ではタイムステップで何も除算するべきではないので、ゼロのタイムステップ安全であるはずですが、それを可能にすると、ほとんどまたはまったくゲインがないために潜在的なエラーの追加のソースが追加されるため、回避されます。)
Ilmari Karonen

5

これは、フレームレートを制限するためですが、更新はフレームごとに1回だけです。したがって、ゲームが60 fpsのターゲットで実行されると仮定すると、1秒あたり60回のロジック更新を取得します。フレームレートが15 fpsに低下した場合、1秒あたり15のロジック更新しかありません。

代わりに、これまでに経過したフレーム時間を累積してから、所定の時間間隔ごとにゲームロジックを1回更新します。たとえば、ロジックを100 fpsで実行するには、累積10ミリ秒ごとに1回更新を実行します(そして、カウンター)。

経過時間に基づいてロジックを更新する(ビジュアルに優れた)代替を追加します。


1
すなわちupdate(elapsedSeconds);
Jon

2
そして、内部では、位置+ =速度*経過秒数。
Jon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.