【Unity】オブジェクトの現在速度を取得する

こじゃらこじゃら

オブジェクトが動いている速度を知りたい場合はどうすればいいの?

このはこのは

移動量とデルタタイムから計算したり、Rigidbody.velocityプロパティにアクセスすれば良いわ。

動くゲームオブジェクトの速度を取得したい場合、主に次の2通りの方法で実現できます。

速度の取得方法
  • 前フレームからの移動量デルタタイムから計算する
  • Rigidbody.velocityプロパティから取得する

前者は、前フレームから現在フレームにかけて移動した量をデルタタイム(Time.deltaTime)で割って速度を求める方法です。スクリプトの実装が必要ですが、どの場面でも汎用的に扱える方法です。

後者は、物理演算されているオブジェクト限定になりますが、速度計算の実装をせずともプロパティから簡単に取得できます。

本記事では、上記2通りの方法でゲームオブジェクトの現在速度を取得する方法を解説していきます。

UCL UCL

この作品はユニティちゃんライセンス条項の元に提供されています

動作環境
  • Unity 2021.1.23f1

スポンサーリンク

移動量とデルタタイムから計算する

1つ目の方法はあらゆる環境でも普遍的に使える方法です。速度計算の原理を解説してからサンプルスクリプトを示す流れで解説します。

速度の求め方

速度とは、単位時間当たりにどの向きにどれくらい移動するかを表す物理量です。したがって、速度はベクトルとして得られます。

移動中のオブジェクトの速度(ベクトル)

参考:速度 – Wikipedia

ある時間\Delta tで位置が\vec{\Delta P}だけ変化した場合、時間\Delta tにおける平均速度\vec{v}は次式で表されます。

平均速度を求める式
\vec{v} = \frac{\vec{\Delta P}}{\Delta t}

厳密な速度ではなく、あくまで平均速度を求めるという点に留意する必要があります。

移動量\vec{\Delta P}は、移動後の位置P^{\prime}から移動前の位置Pを引いた結果となります。

移動量を求める式
\vec{\Delta P} = P^{\prime} - P

平均速度を求める際、移動前と移動後の位置を求める時間\Delta t短くすると、現在速度により近い値が得られます。

この\Delta tを限りなく0に近づけたものが現在速度ですが、Unityには計測できる時間間隔に限度が存在します。

この最小限度の時間間隔はTime.deltaTimeプロパティから得られます。これは、UpdateFixedUpdateイベントの前フレームからの経過時間(秒)を表します。

したがって、プログラム中では前述の式のパラメータを次の通り対応させれば良いことになります。

数式とUnityプロパティとの対応
  • P : 1フレーム前のtransform.position
  • P^{\prime} : 現在フレームのtransform.position
  • \Delta t : Time.deltaTime

実際の処理は次のようになるでしょう。

// 1フレーム前の位置
private Vector3 prevPosition;

・・・(中略)・・・

private void Update()
{
    // 現在フレームの位置
    Vector3 position = transform.position;

    // 平均速度を計算する
    Vector3 velocity = (position - prevPosition) / Time.deltaTime;
}
メモ

3次元における速度(velocity)はVector3型です。

一般に、velocity.magnitudeのような大きさだけ持つ物理量「速さ」と言い、向きを持った「速度」とは区別されます。

参考:速さ – Wikipedia

サンプルスクリプト

以下、3次元のフレーム毎の平均速度を計算する例です。計算された速度(ベクトル)を毎フレームログ出力します。

CalcVelocityExample.cs
using UnityEngine;

public class CalcVelocityExample : MonoBehaviour
{
    // 1フレーム前の位置
    private Vector3 _prevPosition;

    private void Start()
    {
        // 初期位置を保持
        _prevPosition = transform.position;
    }

    private void Update()
    {
        // deltaTimeが0の場合は何もしない
        if (Mathf.Approximately(Time.deltaTime, 0))
            return;

        // 現在位置取得
        var position = transform.position;

        // 現在速度計算
        var velocity = (position - _prevPosition) / Time.deltaTime;

        // 現在速度をログ出力
        print($"velocity = {velocity}");

        // 前フレーム位置を更新
        _prevPosition = position;
    }
}

上記スクリプトをCalcVelocityExample.csとしてUnityプロジェクトに保存し、速度を計算したいゲームオブジェクトにアタッチすると機能します。

実行結果

対象オブジェクトの速度がログ出力されることが確認できました。

オブジェクトを動かすと(0, 0, 0)以外の結果が出力されます。

スクリプトの説明

速度計算に使用する1フレーム前の位置は、別の変数に保持しておく必要があります。これは、以下フィールドに格納しています。

// 1フレーム前の位置
private Vector3 _prevPosition;

そして、速度の計算をUpdateイベント内で行いますが、Time.deltaTimeが0になった場合にゼロ除算エラーになるため、最初にチェックします。

private void Update()
{
    // deltaTimeが0の場合は何もしない
    if (Mathf.Approximately(Time.deltaTime, 0))
        return;

参考:Mathf-Approximately – Unity スクリプトリファレンス

チェックが済んだら、以下処理で速度を計算します。

// 現在位置取得
var position = transform.position;

// 現在速度計算
var velocity = (position - _prevPosition) / Time.deltaTime;

最後に、Updateイベントの最後で前フレーム位置を更新して完了です。

// 前フレーム位置を更新
_prevPosition = position;

Rigidbodyの移動速度を取得する

Rigidbodyにより物理演算されているオブジェクトの場合、Rigidbody.velocityプロパティから現在速度を取得できます。

参考:Rigidbody-velocity – Unity スクリプトリファレンス

注意点としては、物理演算されているオブジェクトの速度を取得できるものであり、transform.positionプロパティ等で直接位置を書き換えた場合は正しく速度を取得できません。

サンプルスクリプト

RigidbodyVelocityExample.cs
using UnityEngine;

public class RigidbodyVelocityExample : MonoBehaviour
{
    [SerializeField] private Rigidbody _rigidbody;

    private void FixedUpdate()
    {
        print($"velocity = {_rigidbody.velocity}");
    }
}

実行結果

物理演算でオブジェクトが動くと0以外の速度が得られるようになりました。

特定方向の速度を取得する

ここまで解説した方法は、ワールド空間における速度ベクトルを取得する方法でした。

例えばオブジェクトの前後方向など、特定方向の成分の速度を抽出したい場合を考えます。

求めたい特定方向の速度

これは、前述の速度ベクトル向きベクトル内積から求められます。

特定方向の速度を求める式
v = \vec{v} \cdot \vec{d}

ただし、

\vec{v} : ワールド空間の速度

\vec{d} : 向きベクトル

|\vec{d}| = 1

向きベクトルを長さを1に正規化してから内積を取らないと正しい結果が得られない点にご注意ください。

得られる結果は、速度ベクトルを向きベクトルに投影した位置です。

内積の図

向きベクトルに対して速度ベクトルが同じ向きの場合、内積の結果は正となります。逆向きの場合は負となります。

サンプルスクリプト

以下、指定された向きベクトルの成分の速度を抽出してログ出力する例です。

DirectionalVelocityExample.cs
using UnityEngine;

public class DirectionalVelocityExample : MonoBehaviour
{
    // 求めたい方向成分のベクトル
    [SerializeField] private Vector3 _direction = Vector3.forward;
    
    // 1フレーム前の位置
    private Vector3 _prevPosition;

    private void Start()
    {
        // 初期位置を保持
        _prevPosition = transform.position;
        
        // 方向成分のベクトルを正規化
        _direction.Normalize();
    }

    private void Update()
    {
        // deltaTimeが0の場合は何もしない
        if (Mathf.Approximately(Time.deltaTime, 0))
            return;

        // 現在位置取得
        var position = transform.position;

        // 現在速度計算
        var velocity = (position - _prevPosition) / Time.deltaTime;
        
        // 方向成分のベクトルと速度の内積を求める
        var directionalVelocity = Vector3.Dot(velocity, _direction);

        // 結果表示
        print($"directionalVelocity = {directionalVelocity}");

        // 前フレーム位置を更新
        _prevPosition = position;
    }
}

上記をDirectionalVelocityExample.csという名前でUnityプロジェクトに保存し、速度を計測したゲームオブジェクトにアタッチしてください。

実行結果

以下は向きベクトルを(0, 0, 1)とした場合です。

z軸成分の速度のみ計測できていることが確認できました。

スクリプトの説明

一つ目の例と異なる部分は以下です。

// 現在速度計算
var velocity = (position - _prevPosition) / Time.deltaTime;

// 方向成分のベクトルと速度の内積を求める
var directionalVelocity = Vector3.Dot(velocity, _direction);

元の速度velocityと抽出したい速度成分の向きベクトル_directionとの内積から結果を求めています。

Rigidbody.velocityに対しても同様に内積を取ることで計算できます。

さいごに

現在速度は、前フレームからの経過時間(Time.deltaTime)と変位(transform.positionの変化量)から計算できます。

Rigidbodyで物理演算されているオブジェクトの場合、Rigidbody.velocityから簡単に知ることができます。

オブジェクトの移動速度が分かれば、移動方向にキャラクターを回転させたり、弾を打つなど動作が可能になります。

参考サイト

スポンサーリンク