例えばあるキャラクターが速度speed、
位置posという値を持っているとします。
で「ググーッと急ブレーキで止まる」みたいなことやらせたいからと、
毎フレームの位置と速度の更新を
下記のようにやってしまうとFPSが変化した時に挙動が変わってしまう。
float speed = 1; // 初速
Vector3 pos; // 現在位置
const float breakPow = 0.05f; // ブレーキ力。1未満。大体0.001から0.05ぐらいが実用的
// 毎フレーム呼び出し
void Update()
{
// 速度更新
speed *= (1 - breakPow); // 掛け算でブレーキ。ググーッと止まる
// 位置更新
pos.x += speed * Time.deltaTime;
}
FixedUpdateを使う、速度ではなく位置をイージングするなど対処方法はあるのですが、今回は数学で。
ということで、まず速度の変更はこうします。
// 指数関数で補間
float Interpolate(float from, float to, float pow, int fps, float t)
{
float rate = 1 - Mathf.Pow(1 - pow, fps * t);
return Mathf.Lerp(from, to, rate);
}
// 毎フレーム呼び出し
void Update()
{
const int FPS = 120; // 基準となるFPS
// FPSが変わっても大丈夫なように速度更新
speed = Interpolate(speed, 0, breakPow, FPS, Time.deltaTime);
// さて、移動量は・・・積分が必要!
//pos.x += speed * Time.deltaTime;
}
あとは位置の更新ですが、こちらは定積分が必要。
もはや積分のことなんてとっくに忘れてしまったので、
ChatGPTさんにやってもらいました。
これが結果。
// fromからtoに向かって変化する場合の[0からt]までの定積分
float CalcDefiniteIntegral(float from, float to, float pow, int fps, float t)
{
var a = to;
var b = from - to;
var p = 1 - pow;
var q = fps;
return a * t + b / (q * Mathf.Log(p)) * (Mathf.Pow(p, q * t) - 1);
}
ということでまとめるとこんな感じ。
// 毎フレーム呼び出し
void Update()
{
const int FPS = 120; // 基準となるFPS
// FPSが変わっても大丈夫なように位置更新
pos.x += CalcDefiniteIntegral(speed, 0, breakPow, FPS, Time.deltaTime);
// FPSが変わっても大丈夫なように速度計算
speed = Interpolate(speed, 0, breakPow, FPS, Time.deltaTime);
}