Time.deltaTime、正しく使えていますか?
UnityにはTime.deltaTime
というプロパティが存在します。
このプロパティを計算式に掛けることで、「スペックによらずある処理の速度が一定になる」と思っている人もいるでしょう。
実際にWebを検索すると、「フレームレートが一定でないので、Time.deltaTime
を掛けないといけない」という説明が多く出てきます。
このプロパティは本当にそういう性質のものなのか。
なぜ掛けることで処理が安定するのか。
その意味をしっかり把握して、必要な場所で正しく使うようにしましょう。
そもそもTime.deltaTimeの意味とは
Time.deltaTime
は単に 直前のフレームと今のフレーム間で経過した時間[秒] を返すプロパティです。
これ自身に処理を安定化させる機能はありません。単純に秒数を返すだけです。
ではどういう場面でTime.deltaTimeを使えばよいのか
適当に計算式に混ぜ込めば処理がなめらかになるといった理解は間違いです。
答えを言ってしまうと、「右辺にTime.deltaTime
を掛けた時に、左辺と同じ単位になったら使ってもよい」です。
「単位」とは
ここでいう単位とは、「物理量」を示す単位のことです。「秒[s]」「距離[m]」「速さ[m/s]」といったものです。
単位には大事な性質があります。
- 異なる単位同士は足せない( 距離 + 秒、みたいな計算はできない )
- 異なる単位同士は掛け算できる、その場合は別の単位となる(距離 = 速度✕時間、みたいな)
- 単位を持たない場合は[1]と表記する(無次元)
Time.deltaTimeを使った例
例1: 速度と移動量
「はやさ = きょり / じかん」という式は小学生のころに習ったはずです。
その式を思い出し、それが成り立つように式を組み立てるだけです。
「なぜ速度にTime.deltaTime
を掛けないといけないのか」の答えは「実際にそのフレームで進んだ"距離"を求める必要があるため」です。
public class Sample : MonoBehaviour
{
/// <summary>
/// 速度[m/s]
/// </summary>
private Vector3 _velocity = new Vector3(1, 0, 0);
void Update()
{
// position[m] = position[m] + (velocity[m/s] * time[s])
// [m] = [m] + [(m/s * s)]
// [m] = [m] + [m] ←両辺で単位が同じになった
transform.position = transform.position + (_velocity * Time.deltaTime);
}
}
例2: 速度と速度
続いて、左辺が速度で、この速度を制御したい場合です。この場合、右辺で扱うパラメータが最初から速度であるならばTime.deltaTime
を掛けてはいけません。
これも単位を計算すればすぐわかります。
public class Sample : MonoBehaviour
{
/// <summary>
/// 速度[m/s]
/// </summary>
private Vector3 _velocity = new Vector3(1, 0, 0);
private Rigidbody _rigidbody;
void Start()
{
_rigidbody = GetComponent<Rigidbody>();
}
void Update()
{
// [m/s] = [m/s] * [s]
// [m/s] = [m] ←????????
_rigidbody.velocity = _velocity * Time.deltaTime; // 間違い
// この場合は普通に代入するだけでよい
_rigidbody.velocity = _velocity;
}
}
例3: 移動量と加速度
ちょっと複雑な例ですが、「加速度」を使って「移動量」を計算する場合です。
この場合も、それぞれの単位をしっかり考えれば難しくはありません。
public class Sample : MonoBehaviour
{
/// <summary>
/// 加速度[m/s/s]
/// </summary>
[SerializeField]
private Vector3 _acceleration = new Vector3(1, 0, 0);
/// <summary>
/// 現在の速度[m/s]
/// </summary>
private Vector3 _currentVelocity = Vector3.zero;
void Update()
{
// 現在の速度を計算する
// [m/s] = [m/s] + [m/s/s] * [s]
// [m/s] = [m/s] + [m/s]
_currentVelocity = _currentVelocity + _acceleration * Time.deltaTime;
// 速度から移動量をもとめる
// [m] = [m] + [m/s] * [s]
// [m] = [m] + [m]
transform.position = transform.position + _currentVelocity * Time.deltaTime;
}
}
(それぞれパラメータを変えて動作させた結果)
例4: Lerpで使う例
Time.deltaTime
をよくMathf.Lerp
やVector3.Lerp
と組み合わせることがあります。
この場合は、「**パラメータの変化速度[1/s]**に対してTime.deltaTime
を掛けている」思えばよいです。
public class Sample : MonoBehaviour
{
private Vector3 _startPosition = Vector3.zero;
private Vector3 _goalPosition = new Vector3(10, 0, 0);
/// <summary>
/// パラメータの変化量[1/s]
/// 10.0f、の場合は、1.0/10.0 = 0.1[s]で遷移が完了する、という意味になる
/// </summary>
private float speed = 10.0f;
// 現在の変化量
private float _progress = 0.0f;
void Update()
{
// Vector3.Lerpの第三引数に与えられるパラメータは「全体の変化の割合量[1]」
// [1] = [1/s] * [s]
_progress = _progress + speed * Time.deltaTime;
transform.position = Vector3.Lerp(_startPosition, _goalPosition, _progress);
}
}
まとめ
Time.deltaTime
は処理を安定化させる魔法のパラメータではありません。
単に経過時間を返すだけのプロパティです。
そもそも「Time.deltaTime
を掛けると処理がなめらかになる。なぜならばフレームレートが一定でないから」という説明自体が間違いだらけです。
「経過時間のパラメータが必要な計算式を(意識せずとも)実装しており、その経過時間の項にTime.deltaTime
がぴったり収まる」というだけに過ぎません。
自分が今、なんの式を書いており、どのような計算が行われているのか、をしっかり意識して書けば難しい話ではありません。
その指標となるのが「単位」であり、これを意識すればTime.deltaTime
で迷うことはないでしょう。