可変タイムステップと重力/摩擦

私はソニック物理エンジンのロジックをコピーしようとしていますが、可変タイムステップ時代(Slick2D、正確には)で、タイムステップシステム(60FPS)を使用します。

元はジャンプボタンを押すと、プレーヤーの velocity.y が-6.5に設定され、重力をモデル化するために0.21875の目盛りが velocity.y に追加されます。

ロジックの更新が呼び出されるたびに、何ミリ秒経過したかを指定するタイム・デルタ・パラメータが渡されます。私が予想していたよりももっと多くのミリ秒が経過しているなら、ターゲットフレームの '残りの部分'を扱っている場合、更新ロジックを繰り返し、 '内側のデルタ'を1以下にします。

例えば。フレームが16msを要し、16msかかる場合、ループは1回反復し、 thisMiniTick を1として渡します。デルタが16msではなく40msであれば、ループは3回実行され、1,1、および最後に0.5を通過します。

私は誤って、これらの内部更新ループのそれぞれで、 velocity.y + =(gravity * thisMiniTickRelative)を実行できると考えましたが、これは機能しません。より速いフレームレートでは、重力がかかりすぎてジャンプが高くなり、フレームレートが遅くなるとジャンプが小さくなります(ただし、目立つほど近くにはありません)。

ほぼすべてのフレームレートで動作する方法がありますか、 delta の上限と下限を設定する必要がありますか?

「内部更新」ループ:

    float timeRemaining = delta/1000f; 
    while(timeRemaining > 0)
    {
        float thisMiniTick = Math.min(timeRemaining, 1f/FRAMES_PER_SECOND);
        float thisMiniTickRelative = thisMiniTick/(1f/FRAMES_PER_SECOND);

        updateInput(container, game, thisMiniTickRelative);
        if (playerAirState)
        {
            playerVelocity.y += (GRAVITY * thisMiniTickRelative);
        }
        clampPlayerVelocity();
        playerPosition.add(playerVelocity);
        doCollisions();
        timeRemaining -= thisMiniTick;
    }
0

1 答え

delta 」の上限と下限を設定することに頼っているとは思わないでください。あなたのアプリケーションとスレッドは、あなたのアプリケーションのOSスケジューリング時間、システム上の他のすべての要求、およびあなたが気づいておく必要のあるものがあります。この課題は、シングル・タスク・オペレーティング・システムからマルチ・タスキング・オペレーティング・システムに移行した頃と同じくらい、PCゲームでは古くなっています。

Slickでは、ロジックの更新をレンダリングの更新から切り離すことができる(そして、すべきである)ので、 delta の値はアプリケーションの周りを渡されます。これを行うには、 .setMinimumLogicUpdateIntervalおよび.setMaximumUpdateInterval メソッドを使用します。

私が取り組んできたプロジェクトでは、Slickのプロジェクトも含めて、毎秒30-60回のロジックアップデート(更新の間に30.3ミリ秒から16.6ミリ秒まで)の何かがうまくいき、動きから必要な滑らかさを得ることができます。物理計算、および衝突計算が含まれます。

文字通りその意味は、毎秒30-60回のロジック更新では、次のことを実行することです。

container.setMinimumLogicUpdateInterval(16); //max 60 logic updates per second
container.setMaximumLogicUpdateInterval(31); //min 30 logic updates per second

また、 timeRemaing の値を計算しようとするのはよくある間違いですが、これをしたくないのです。あなたはどれくらいの時間が経過したかによって、どれくらい移動したかを乗算したいだけです。 30ミリ秒が経過した場合、それは約1/33秒ですので、ゲームオブジェクトを1秒間に移動する量の1/33に移動する必要があります。

float timeElapsed = delta/1000f;

playerVelocity.y += (GRAVITY * timeElapsed);

上記のように上限/下限を設定すると、 timeElapsed は常に 0.03 から 0.06 までの値になります。ゲームが停滞し、フレームレートが低下した場合でも、ロジックアップデートはこれらの範囲外には進まないでしょう。代わりに何が起こるかは、ゲーム全体が減速するように見えます(スクリーン上に過度にあった古いセガの日のように)。しかし、衝突や物理計算は期待通りに機能します。

2
追加された
詳細な答えをありがとう、それは私が把握できなかったいくつかの魔法の解決策がないことを確認するのが安心です。なぜあなたは時間を差別化アプローチと誤解していますか?私はもともと自分の速度に経過時間を掛けていましたが、衝突コードが大きな書き換えを必要とすることを意味しました。プレイヤーは1つのティックでタイル全体より多くを移動してタイルをスキップすることができました。
追加された 著者 EngineerBetter_DJ,
うん、私はそれを誇張しているかもしれませんが、 timeRemaining は、人々がその値を使って他のことをやりなおすためにどれだけの時間を費やそうとしているかということです。 AIの計算などがあります。使用できないわけではありません。さまざまな方法でこの問題にアプローチし、すべてを delta 値にフックすると、最も信頼できる結果が得られます。
追加された 著者 jefflunt,
衝突のためにあまりにも速く動くプレーヤーの場合、その問題にアプローチするにはいくつかの方法があります。 1人は、プレイヤーが横断したスペース全体で衝突計算を行うことになります(プレイヤーが3タイルを移動した場合、それらのいずれかで起こった衝突を把握して戻す必要があります)。ロジックアップデートの細かさを増やす(最小/最大のロジックアップデート間隔を調整する)か、プレーヤーをよりゆっくり動かすかのどちらかです。
追加された 著者 jefflunt,
私が近づける最後の方法の1つは、衝突境界を前方に(後方ではなく)前方に投げて、プレイヤーが近くにあるものと衝突する可能性がある場合は、より細粒の衝突を行う必要があります次の数コマの間に、プレイヤーと近くにあるアイテムとの間での検出。明らかにあなたがSonic the Hedgehogに似たゲームをしているなら、あなたは非常に速い動きに取り組んでおり、その動きはゲームプレイの中心的なものなので、本当にそれを打つことはできません。
追加された 著者 jefflunt,
サークルと他のオブジェクト(特に他のサークル)との衝突の計算が数学的に簡単であるため、ソニック(および彼のすべての友人)がコリジョンサークルで世界をナビゲートする理由の部分したがって、多面ポリゴン間の複雑な衝突よりも速い)。
追加された 著者 jefflunt,