Help us understand the problem. What is going on with this article?

【Unity】Time.deltaTimeの正しい使い方わかってる?適当に掛ければいいてもんじゃない!

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;
    }
}

1.gif

(それぞれパラメータを変えて動作させた結果)

例4: Lerpで使う例

Time.deltaTimeをよくMathf.LerpVector3.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で迷うことはないでしょう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした