本記事は サムザップ Advent Calendar 2025 の16日目の記事です。
はじめに
物体の加速度運動を実装する際、FPS(フレームレート)の違いによって移動量に誤差が生じることが気になったことはないでしょうか。
この記事では、物体の移動量は「時間による速度変化のグラフ(以下、v-tグラフ)」から区分求積的に求められるという観点に基づき、誤差の原因とその軽減方法について解説します。
内容自体は基本的なことかもしれませんが、v-tグラフの面積に着目した記事はあまり見当たらなかったため、まとめてみました。
物体の移動量はv-tグラフの面積
ある物体の速度$v(t)$が時刻$t$によって連続的に変化する時、微小時間$dt$においては速度$v(t)$は一定であるとみなすことで、微小時間での移動量は$v(t)dt$と表せます。
したがって、時刻$t_1$から$t_2$までの総移動量は、その微小移動量の時間積分で計算できます。
$$
\begin{equation}
\Delta x = \int_{t_1}^{t_2} v(t) dt
\end{equation} \tag{1}
$$
この移動量は、v-tグラフにおいては区間$t_1$から$t_2$までの曲線と$t$軸で囲まれた「面積」に相当します。

離散の世界では面積はどうなる?
ゲームなどのコンピューターシミュレーションの世界では、時間は連続ではなく離散的なステップで進みます。Unityであれば、1ステップの時間幅はTime.deltaTime(可変フレームレート)やTime.fixedDeltaTime(固定フレームレート)等になります。
では、以下のよく見る加速度運動の実装は、v-tグラフでは何を求めていることになるでしょうか。
void Update()
{
// 速度を更新 v_i = v_(i-1) + a * dt
v += a * Time.deltaTime;
// 位置を更新 x_i = x_(i-1) + v_i * dt
transform.position += v * Time.deltaTime;
}
v: 速度 a: 加速度(ここでは一定とする)
上のプログラムのv * Time.deltaTimeの部分に注目すると、これは時間ステップ$i$での移動量$\Delta x_i$であり、時間ステップ$i$における速度$v_{i}$と時間幅$\Delta t$を用いて以下のように表せます。
$$
\begin{equation}
\Delta x_i = v_{i} \Delta t
\end{equation} \tag{2}
$$
これはv-tグラフ上では、長方形の面積(縦:速度$v_{i}$の絶対値、横:時間幅$\Delta t$)に相当することが分かります。
各時間ステップごとにこの長方形の面積が現在地(transform.position)に足し合わされていくため、総移動量はv-tグラフにおける「各ステップの長方形の面積の合計値」となります。

つまり、上記のよくある実装は、ある時間ステップでの移動量を「長方形の面積」として近似計算していることになります。
長方形近似でFPSが変わると何が起きる?
FPS(フレームレート)が変わる、すなわち1ステップあたりの経過時間(60FPSの場合、約1/60秒)が変わると、移動量(面積)はどのように変化するでしょうか。
FPSが高い($\Delta t$が小さい)場合と低い($\Delta t$が大きい)場合とで、v-tグラフでの長方形近似の様子を比較すると以下のようになります。

FPSが低い(時間刻みが粗い)ほうが、長方形が速度のグラフから大きくはみ出ており、総面積も大きくなってしまっていることが分かります。
このように、FPSが低くなる($\Delta t$が大きくなる)ほど、真の移動量からの誤差の蓄積は大きくなっていきます。
ここで重要なのは、誤差が生じることそのものよりも、FPSの変化によって誤差の大きさが変わり、結果としてプレイ環境(フレームレート)によって挙動が変わってしまうという点です。
FPSの違いによる誤差を軽減するには?
このFPSの違いによる移動量の誤差を軽減するにはどうすれば良いでしょうか。 v-tグラフで考えると、ある時間ステップでの移動量を長方形ではなく「台形の面積」で近似する方法が真っ先に思いつきます。

時間ステップ$i$での移動量(台形の面積)は、時間ステップ$i$での速度$v_{i}$と1つ前のステップでの速度$v_{i-1}$を用いて以下のように計算できます。
$$
\begin{equation}
\Delta x_i = \frac{1}{2} (v_{i-1} + v_{i}) \Delta t
\end{equation} \tag{3}
$$
(上底:現在の速度$v_{i}$、 下底:1ステップ前の速度$v_{i-1}$、 高さ:時間幅$\Delta t$)
これは、1つ前のステップと現在のステップの「平均速度」を使って移動量を計算していると見ることもできます。
更新部分の実装は以下のようになります。
void Update()
{
// 速度を更新 v_i = v_(i-1) + a * dt
v += a * Time.deltaTime;
// 位置を更新 x_i = x_(i-1) + 0.5 * (v_(i-1) + v_i) * dt
transform.position += 0.5f * (v_prev + v) * Time.deltaTime;
v_prev = v;
}
長方形近似と台形近似での移動量の誤差の比較
長方形近似と台形近似による移動量の誤差の違いを整理すると、以下のようになります。
| 近似方法 | 等速運動 | 等加速度運動 | 一般的な加速度運動 |
|---|---|---|---|
| 長方形近似 | 無し | 有り | 有り |
| 台形近似 | 無し | 無し | 有り(長方形近似よりは少ない) |
各運動のv-tグラフでの近似の様子を以下に示します。 左が長方形近似、右が台形近似の図です。
等速運動
速度が一定の場合は、どちらの場合も真の移動量との誤差はありません。

等加速度運動
加速度が一定(0以外)の場合は、長方形近似では面積の誤差が出ていますが、台形近似では誤差が出ていません。

一般的な加速度運動
加速度が一定でない場合は、どちらも面積の誤差が出ていますが、台形近似の方が誤差は小さくなります。

等加速度運動での台形近似の別の表現
加速度$a$が時間によらず一定である等加速度運動という条件下では、時間ステップ$i$での速度$v_{i}$は1つ前のステップでの速度$v_{i-1}$と加速度$a$を用いて、
$$
\begin{equation}
v_{i} = v_{i-1} + a \Delta t
\end{equation} \tag{4}
$$
と表せます。これを式(3)に代入すると、台形近似の式は以下のように変形できます。
\begin{aligned}
\Delta x_i = \frac{1}{2} (v_{i-1} + v_{i}) \Delta t &=\frac{1}{2} \{v_{i-1} + (v_{i-1} + a \Delta t)\} \Delta t \\
&= \frac{1}{2} (2v_{i-1} + a \Delta t) \Delta t \\
&= v_{i-1}\Delta t + \frac{1}{2}a(\Delta t)^2
\end{aligned} \tag{5}
コード化すると、以下のようになります。
void Update()
{
// 位置を更新 x_i = v_(i-1) * dt + 0.5 * a * dt^2
transform.position += v * Time.deltaTime + 0.5f * a * Time.deltaTime * Time.deltaTime;
// 速度を更新 v_i = v_(i-1) + a * dt
v += a * Time.deltaTime;
}
式(3)による実装では、1ステップ前の速度をメンバ変数でキャッシュしておく必要がありましたが、式(5)による実装ではその必要が無くなっています。
ただし、この更新式は「加速度が一定(等加速度)」という仮定が入っている点に注意してください。
まとめ
この記事では、時間ステップ毎に速度と移動量を更新していく方式において、FPSの変化によって生じる移動量誤差の原因と、その軽減方法について解説しました。
FPSの違いによる物体の挙動変化が気になる場合は、台形近似による求積方法を検討してみるのも良いかと思います。
なお、移動量の誤差には求積方法によるものだけでなく、浮動小数点の演算誤差なども含まれるため、等加速度運動であっても台形近似だけでは完全に誤差をゼロにすることはできない点に注意してください。