追従・ばね・群れ

指数減衰

Exponential Damping

経過時間に合わせてカメラや照準を目標へ近づける。

1 / 10

インタラクティブデモ

デモを準備しています…

指数減衰

ドラッグで視点を回転、Ctrl(⌘)+ スクロールでズーム。タッチ操作は 2 本指です。

ひとことで

現在の値を目標へ「残りの距離の一定割合ずつ」近づける追従を、経過時間 Δt\Delta t から係数を計算することでフレームレートに依存しない形にしたものです。追従カメラ、照準、UI の値の変化など、「滑らかに追いかける」処理の最も基本的な道具です。

ゲームでの使いどころ

  • 追従カメラ:プレイヤーの位置をカメラがわずかに遅れて追う。
  • 照準・ロックオン:レティクルが敵へ吸い付くように移動する。
  • 数値の表示:HP バーの減少、スコアのカウントアップ、音量のフェード。
  • 入力の平滑化:スティックの入力やマウス移動を滑らかにしてブレを抑える。

しくみ

よく見る書き方は次のようなものです。

これは「1 フレームで残りの距離が 0.9 倍になる」という意味です。60fps なら 1 秒で 0.960≈0.0020.9^{60} \approx 0.002 倍、30fps なら 0.930≈0.040.9^{30} \approx 0.04 倍になり、フレームレートによって追従の速さが変わってしまいます。高リフレッシュレートのモニターでだけカメラが速い、というバグの典型的な原因です。

時間に対して正しく減衰させるには、「1 秒あたりの減衰率」を決め、経過時間に応じて累乗します。連続時間で書くと

dxdt=−λ (x−xtarget)⇒x(t+Δt)=xtarget+(x(t)−xtarget) e−λΔt\frac{dx}{dt} = -\lambda\,(x - x_{\text{target}}) \quad\Rightarrow\quad x(t + \Delta t) = x_{\text{target}} + (x(t) - x_{\text{target}})\,e^{-\lambda \Delta t}

なので、lerp の係数を次のようにすればよいのです。

k=1−e−λ Δt,λ=ln⁡2T1/2k = 1 - e^{-\lambda\,\Delta t}, \qquad \lambda = \frac{\ln 2}{T_{1/2}}

T1/2T_{1/2} は半減期(残りの距離が半分になるまでの時間)です。「0.25 秒で半分」のように、フレームレートと無関係な直感的な単位で調整できます。

実装のポイント

  • エンジンの関数:Unity の Vector3.SmoothDamp や Unreal の FMath::FInterpTo は同じ目的の関数です(SmoothDamp は速度も持つ臨界減衰ばね)。
  • 回転:四元数の場合は slerp(current, target, k) に同じ係数を渡します。
  • 角度のラップ:角度をそのまま減衰させると 359° → 1° で逆回りします。差を [−180°,180°)[-180°, 180°) に正規化してから計算します。

つまずきやすい点

  • いつまでも到達しない:指数減衰は理論上目標に一致しません。一定の距離より近づいたら目標値に丸めるか、完了判定に閾値を使います。
  • 動く目標には遅れが残る:一定速度で動く目標に対しては、v/λv / \lambda の距離だけ常に遅れます。遅れを減らしたいなら先読み追従やばねを使います。
  • 速度が不連続:目標が急に変わると、追従側の速度も瞬時に最大になります。動き出しを滑らかにしたいなら、速度を状態として持つ臨界減衰ばねが向きます。

デモで試すこと

  1. 赤(20fps)とピンク(120fps)の球は同じ lerp(x, 目標, 定数) ですが、ピンクのほうが明らかに速く追いつきます。左下の「残り距離」を比べてください。
  2. 水色と青の球は指数減衰です。20fps と 120fps で更新しているのに、ぴったり重なって見えます(差は 0.000 m 付近)。
  3. 「半減期」を変えると、フレームレートに関係なく追従のきびきび感だけが変わります。床をクリックして目標を置き、確かめてみてください。

参考資料

Spring-It-On ↗

分野全体の資料で、この項目を直接扱っているとは限りません。