Phase 2
System Architecture Series
これまでの記事で、
を学んできました。
ここで出てくる疑問がこれです。
「フレームレートが変わったら、ゲームの速さも変わらない?」
実はその通りです。
1.なぜ問題が起きるの?
通常のループはこうなっています。
function loop(now) {
const dt = (now - lastTime) / 1000;
lastTime = now;
update(dt);
render();
requestAnimationFrame(loop);
}dt を使っているので一見問題なさそうですが…
❗ 問題点
- 60fps → dt ≒ 0.016秒
- 30fps → dt ≒ 0.033秒
- 15fps → dt ≒ 0.066秒
フレームが落ちると 一気に大きく進む ことになります。
結果:
- 当たり判定がすり抜ける
- 物理が壊れる
- 不安定な挙動になる
これを防ぐのが Fixed Time Step です。
2.Fixed Time Stepとは?
更新を「毎回同じ時間間隔」で進める方法です。
例えば:
1秒を 60回 に分ける
→ 1回 = 0.016666秒つまり、
フレームが何fpsでも
updateは常に 1/60秒ずつ進める
という設計です。
3.イメージ
❌ 可変時間(Variable)
フレーム毎に進む距離がバラバラ✅ 固定時間(Fixed)
常に同じ幅でカチカチ進む4.実装してみよう(基本形)
const fixedDelta = 1 / 60; // 60回/秒
let accumulator = 0;
let lastTime = 0;
function loop(now) {
const deltaTime = (now - lastTime) / 1000;
lastTime = now;
accumulator += deltaTime;
while (accumulator >= fixedDelta) {
update(fixedDelta);
accumulator -= fixedDelta;
}
render();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);5.何をしているの?
accumulator(貯金箱)
時間を一旦ためる箱です。
例:
- 今回のフレームで 0.033 秒進んだ
- fixedDelta は 0.016 秒
→ 2回 update する
つまり、
遅れたらその分まとめて処理する6.なぜ安定するの?
updateは常に:
1/60秒
1/60秒
1/60秒と一定間隔で進みます。
フレームレートが落ちても、
- 計算の粒度は一定
- 物理計算が壊れない
- 判定が安定する
7.注意:スパイラル・オブ・デス
もし処理が重すぎると、
遅れる
→ update回数が増える
→ さらに重くなる
→ さらに遅れるという無限ループ状態になります。
Fixed Time Stepは安定した設計ですが、処理が追いつかなくなると「遅れを取り戻そうとしてさらに重くなる」危険があります。これをスパイラル・オブ・デスと呼びます。
対策:update回数を制限する
const maxUpdates = 5;
let updateCount = 0;
while (accumulator >= fixedDelta && updateCount < maxUpdates) {
update(fixedDelta);
accumulator -= fixedDelta;
updateCount++;
}どういう意味?
「1フレームで最大5回までしかupdateしない」
つまり:
取り戻せない遅れは捨てる
という考え方。
8.実験:フレーム落ちで壊れるのはどっち?
- 左:Variable Time
- 右:Fixed Time
- Heavy LoadボタンでCPU負荷追加
観察ポイント
- Heavy Load OFF → ほぼ同じ動き
- Heavy Load ON →
- Variableはガタつく
- Fixedは安定
9.まとめ
| Variable | Fixed | |
|---|---|---|
| 実装 | 簡単 | 少し複雑 |
| 安定性 | 不安定 | 安定 |
| 物理向き | × | ◎ |
| ゲーム向き | △ | ◎ |
10.次に進むなら
Fixed Time Stepで安定したら次はこれ:
👉 #10 状態管理統合
updateとrenderをどこに置くのか?
🔎 このラボの全体像はこちら
→ UI Architecture Roadmap

コメントを残す