ひとことで
描画のフレームレートに関係なく、物理の計算をいつも同じ時間刻み(例えば 1/60 秒)で進める方法です。描画 1 回の間にたまった時間を、決まった刻みで何回分か消化し、余った時間は次に持ち越します。そのうえで、直前 2 回の物理の結果を補間して描くと、結果は安定したまま、どんなフレームレートでもなめらかに見えます。
ゲームでの使いどころ
- ジャンプの高さや飛距離をそろえる:フレームレートによってジャンプの高さが変わると、60 fps の人は届くのに 30 fps の人は届かない足場ができてしまいます。
- リプレイと対戦:同じ入力から同じ結果が出る(決定的)ことが、リプレイ、ゴースト、ロールバック方式のネット対戦の前提になります。
- 物理エンジンの安定:ばねや拘束は時間刻みが大きくなると発散しやすくなります。刻みを固定すれば、安定するパラメータを一度決めればよくなります。
- 引っかかり(ヒッチ)対策:読み込みなどで 1 フレームが 0.2 秒になっても、物理は 1/60 秒ずつ進むので、壁のすり抜けが起こりにくくなります。
Unity の FixedUpdate、Unreal の物理サブステッピングが、この仕組みです。
しくみ
Glenn Fiedler の記事「Fix Your Timestep!」で広く知られた形です。
- 経過時間をためる:描画 1 回ごとに、実際に経過した時間を「貯金」に足します。
- 決まった刻みで消化する:貯金が刻み以上ある間、物理を 1 ステップずつ進めます。60 fps の描画と 60 Hz の物理なら毎回 1 ステップ、30 fps なら 2 ステップ、144 fps なら 0〜1 ステップになります。
- 補間して描く:貯金の残りは「まだ物理が追いついていない時間」です。直前の状態と今の状態を、残りの割合 で線形補間して描きます。
補間なしで最新の物理の状態をそのまま描くと、描画と物理のタイミングがずれるため、あるフレームでは 2 ステップ分、次のフレームでは 0 ステップ分動く、というばらつきが生じ、カクついて見えます(ジャダー)。
実装のポイント
- 物理と描画の状態を分ける:補間のために、物理の「前回の状態」と「今回の状態」を両方保持します。描画用の位置は、物理の位置とは別に持ちます。
- 補間するのは見た目だけ:当たり判定やゲームのロジックは、補間前の物理の状態で行います。
- 回転はクォータニオンの球面補間:位置は線形補間、回転は球面線形補間で補います。
- 死のスパイラルを防ぐ:物理 1 ステップの処理が刻みより重いと、貯金が増え続けて処理が追いつかなくなります。1 フレームで消化する回数や、ためる時間に上限を設けます。
- 入力は物理のステップで読む:ジャンプの入力などを描画フレームで処理すると、ステップ数が 0 のフレームで取りこぼします。入力をためておき、物理のステップで消費します。
つまずきやすい点
- 補間で 1 ステップ遅れる:補間は「前回と今回の間」を描くので、表示は最大 1 ステップ分だけ過去になります。60 Hz なら約 16 ms で、たいていは問題になりませんが、入力の遅延として気になる場合は外挿(先読み)を使うこともあります。
- 可変ステップのほうが単純に見える:
position += velocity * dtを描画ごとに実行するだけでも動くので、問題に気付くのが遅れがちです。低いフレームレートや引っかかりのある環境でテストします。 - テレポートは補間しない:リスポーンやワープで位置が大きく飛んだときは、前回の状態も同じ位置にしないと、1 フレームだけ途中の位置が描かれます。
デモで試すこと
- 3 つのカプセルは同じジャンプをくり返しています。左下の数値は、本来の高さ(白い点線)に対する直前のジャンプの高さです。赤(可変)は、フレーム時間がばらつくたびに高さが毎回ぶれます。
- 黄(固定)と青緑(固定 + 補間)は、いつも同じ高さです。ただし黄は横に進む動きがカクつき、青緑はなめらかです。「再生速度」を下げると違いがはっきり見えます。
- 右下の図は、描画(白)と物理の更新(黄)のタイミングです。「描画のフレームレート」と「物理の更新回数」をずらすと、描画 1 回に物理が 0 回や 2 回になるフレームが出てきます。
- 「フレーム時間のばらつき・引っかかり」をオフにして、描画 60 fps・物理 60 回/秒にそろえると、3 つとも同じ動きになります。