【Unity】オブジェクトを進行方向に回転させる方法

こじゃらこじゃら

キャラクターなどを進行方向に振り向かせたい場合はどうすればいいの?

このはこのは

移動量の計算回転のクォータニオンの計算を活用すれば良いわ。詳しく解説していくね。

オブジェクトを進行方向に振り向かせる方法の解説記事です。

本記事では、次のようにオブジェクトを進行方向に向き続けるような動きを実現することを目標とします。

実装の流れは以下のようになります。

実装の流れ
  • 移動量を進行方向(ベクトル)として計算
  • 進行方向に向く回転(クォータニオン)の計算
  • 上記をオブジェクトの回転に反映

進行方向は移動量として求めることができます。

進行方向を向くようなクォータニオンの計算は、一見すると複雑ですが、Unityが提供するヘルパーAPIを用いれば比較的楽に実装できます。

本記事では、このような進行方向に回転させる動きをスクリプトで実装する方法を解説します。また、滑らかに(徐々に)に回転させるような動きの実装方法についても紹介します。

動作環境
  • Unity 2022.1.5f1

スポンサーリンク

前提条件

移動するオブジェクトがシーン上に配置されているものとします。

このオブジェクトに対して、進行方向に回転するスクリプトを適用していくものとします。

進行方向(移動量)の計算

進行方向は、過去の位置から移動量のベクトルとして計算できます。

進行方向は長さ1に正規化すると都合の良い場合がありますが、本記事では不要のため正規化は行わず、移動量をそのまま進行方向として扱うこととします。

移動量は次式で求めることができます。

移動量 = 現在フレーム位置 – 前フレーム位置

スクリプトでは、次のような形で移動量を計算します。

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

・・・(中略)・・・

// 毎フレーム処理
private void Update()
{
    // 現在フレーム位置
    var position = transform.position;

    // 移動量計算
    var delta = position - _prevPosition;

    // 次のUpdateで使うための前フレーム位置更新
    _prevPosition = position;
}

そして、この移動量は進行方向として、次に解説する回転の計算で使用します。

進行方向を向く回転の計算

進行方向に向くような回転はクォータニオンで表せます。このようなクォータニオンは、Quaternion.LookRotationメソッドから取得できます。

public static Quaternion LookRotation(Vector3 forward, Vector3 upwards = Vector3.up);

第1引数forwardには、振り向く前方のベクトルを指定します。

第2引数upwardsには、上向きのベクトルを指定します。省略された場合は、Vector3.upという上向きを表すベクトル(0, 1, 0)が指定されます。

進行方向を振り向くサンプルスクリプト

以下、移動する方向にオブジェクトを回転させるサンプルスクリプトです。

RotateToMovementDirection.cs
using UnityEngine;

public class RotateToMovementDirection : MonoBehaviour
{
    private Transform _transform;

    // 前フレームのワールド位置
    private Vector3 _prevPosition;

    private void Start()
    {
        _transform = transform;

        _prevPosition = _transform.position;
    }

    private void Update()
    {
        // 現在フレームのワールド位置
        var position = _transform.position;

        // 移動量を計算
        var delta = position - _prevPosition;

        // 次のUpdateで使うための前フレーム位置更新
        _prevPosition = position;

        // 静止している状態だと、進行方向を特定できないため回転しない
        if (delta == Vector3.zero)
            return;

        // 進行方向(移動量ベクトル)に向くようなクォータニオンを取得
        var rotation = Quaternion.LookRotation(delta, Vector3.up);

        // オブジェクトの回転に反映
        _transform.rotation = rotation;
    }
}

上記スクリプトをRotateToMovementDirection.csという名前で保存し、回転処理を適用したいゲームオブジェクトにアタッチすると機能するようになります。

進行方向に回転させる際、オブジェクトが静止している状態だと進行方向が定まらないため、この場合は処理しないように対策しています。 [1]

// 静止している状態だと、進行方向を特定できないため回転しない
if (delta == Vector3.zero)
    return;

実行結果

進行方向に向くときは、移動するオブジェクトの上側がワールド座標上向きになるように計算されています。これは、Quaternion.LookRotationメソッドの第2引数にVector3.upが指定されているためです。

滑らかに回転させる

先述の例では、進行方向に瞬時に回転するようになっていました。

滑らかに回転させたい場合回転速度に制限を設けたりダンピングさせながら角度を徐々に変化させるような計算を行えば良いです。

以下、滑らかに進行方向に回転させるようにしたサンプルスクリプトです。

RotateToMovementDirectionSmooth.cs
using UnityEngine;

public class RotateToMovementDirectionSmooth : MonoBehaviour
{
    // 最大の回転角速度[deg/s]
    [SerializeField] private float _maxAngularSpeed = Mathf.Infinity;
    
    // 進行方向に向くのにかかるおおよその時間[s]
    [SerializeField] private float _smoothTime = 0.1f;

    private Transform _transform;

    // 前フレームのワールド位置
    private Vector3 _prevPosition;

    private float _currentAngularVelocity;

    private void Start()
    {
        _transform = transform;

        _prevPosition = _transform.position;
    }

    private void Update()
    {
        // 現在フレームのワールド位置
        var position = _transform.position;

        // 移動量を計算
        var delta = position - _prevPosition;

        // 次のUpdateで使うための前フレーム位置更新
        _prevPosition = position;

        // 静止している状態だと、進行方向を特定できないため回転しない
        if (delta == Vector3.zero)
            return;

        // 進行方向(移動量ベクトル)に向くようなクォータニオンを取得
        var targetRot = Quaternion.LookRotation(delta, Vector3.up);

        // 現在の向きと進行方向との角度差を計算
        var diffAngle = Vector3.Angle(_transform.forward, delta);
        // 現在フレームで回転する角度の計算
        var rotAngle = Mathf.SmoothDampAngle(
            0,
            diffAngle,
            ref _currentAngularVelocity,
            _smoothTime,
            _maxAngularSpeed
        );
        // 現在フレームにおける回転を計算
        var nextRot = Quaternion.RotateTowards(
            _transform.rotation,
            targetRot,
            rotAngle
        );

        // オブジェクトの回転に反映
        _transform.rotation = nextRot;
    }
}

上記スクリプトをRotateToMovementDirectionSmooth.csという名前で保存し、回転を適用させたいゲームオブジェクトにアタッチすると機能するようになります。

必要に応じて、インスペクターからパラメータを調整してください。

実行結果

急カーブでも滑らかに振り向くようになりました。

スクリプトの解説

滑らかに回転させるための方法はいくつか存在しますが、例ではMathf.SmoothDampAngleメソッドを使用して次に回転すべき角度を計算しています。

// 進行方向(移動量ベクトル)に向くようなクォータニオンを取得
var targetRot = Quaternion.LookRotation(delta, Vector3.up);

// 現在の向きと進行方向との角度差を計算
var diffAngle = Vector3.Angle(_transform.forward, delta);
// 現在フレームで回転する角度の計算
var rotAngle = Mathf.SmoothDampAngle(
    0,
    diffAngle,
    ref _currentAngularVelocity,
    _smoothTime,
    _maxAngularSpeed
);

Mathf.SmoothDampAngleメソッドは、目標値に滑らかに一致させるような値を求めることができる関数で、目標値に一致するまでの時間や最大の速さなどを設けることが可能です。

SmoothDampAngleを含めたSmoothDamp系メソッドの使い方については、以下記事で詳しく解説していますので、必要な方はご覧ください。

そして、以下処理で現在の回転から目標の回転にSmoothDampAngleメソッドで計算した角度分だけ回転したクォータニオンを求めています。

// 現在フレームにおける回転を計算
var nextRot = Quaternion.RotateTowards(
    _transform.rotation,
    targetRot,
    rotAngle
);

Quaternion.RotateTowardsメソッドは、第1引数のクォータニオンから第2引数のクォータニオンに向かった回転を返すメソッドで、第3引数には最大の回転角度を指定できます。

オブジェクトの前と上の基準を変更する

ここまで解説した例では、移動するオブジェクトのローカル空間のz軸正方向を正面、y軸正方向を上として計算していました。

これらの基準軸を変更したい場合、次のように回転の補正を行えば良いです。

Vector3 forward;
Vector3 up;

・・・(中略)・・・

// 回転補正計算
var offsetRot = Quaternion.Inverse(Quaternion.LookRotation(forward, up));

// 進行方向(移動量ベクトル)に向くようなクォータニオンを取得
// 回転補正→振り向きの順に回転操作を行う
var targetRot = Quaternion.LookRotation(delta, Vector3.up) * offsetRot;

以下、向きの基準変更に対応したサンプルスクリプトです。滑らかな回転にも対応しています。

RotateToMovementDirectionOffset.cs
using UnityEngine;

public class RotateToMovementDirectionOffset : MonoBehaviour
{
    // 最大の回転角速度[deg/s]
    [SerializeField] private float _maxAngularSpeed = Mathf.Infinity;

    // 進行方向に向くのにかかるおおよその時間[s]
    [SerializeField] private float _smoothTime = 0.1f;

    // オブジェクトの正面
    [SerializeField] private Vector3 _forward = Vector3.forward;

    // オブジェクトの上向き
    [SerializeField] private Vector3 _up = Vector3.up;

    private Transform _transform;

    // 前フレームのワールド位置
    private Vector3 _prevPosition;

    private float _currentAngularVelocity;

    private void Start()
    {
        _transform = transform;

        _prevPosition = _transform.position;
    }

    private void Update()
    {
        // 現在フレームのワールド位置
        var position = _transform.position;

        // 移動量を計算
        var delta = position - _prevPosition;

        // 次のUpdateで使うための前フレーム位置更新
        _prevPosition = position;

        // 静止している状態だと、進行方向を特定できないため回転しない
        if (delta == Vector3.zero)
            return;
        
        // 回転補正計算
        var offsetRot = Quaternion.Inverse(Quaternion.LookRotation(_forward, _up));

        // 進行方向(移動量ベクトル)に向くようなクォータニオンを取得
        // 回転補正→振り向きの順に回転操作を行う
        var targetRot = Quaternion.LookRotation(delta, Vector3.up) * offsetRot;

        // 現在の向きと進行方向との角度差を計算
        // 前方を指定された軸にするように変更している
        var diffAngle = Vector3.Angle(_transform.TransformDirection(_forward), delta);
        // 現在フレームで回転する角度の計算
        var rotAngle = Mathf.SmoothDampAngle(
            0,
            diffAngle,
            ref _currentAngularVelocity,
            _smoothTime,
            _maxAngularSpeed
        );
        // 現在フレームにおける回転を計算
        var nextRot = Quaternion.RotateTowards(
            _transform.rotation,
            targetRot,
            rotAngle
        );

        // オブジェクトの回転に反映
        _transform.rotation = nextRot;
    }
}

上記スクリプトをRotateToMovementDirectionOffset.csという名前で保存し、移動対象のオブジェクトにアタッチし、各種設定を行います。

以下は、前方をy軸正方向、上をx軸正方向にした設定例です。

実行結果

向きの基準が変わっていることが確認できます。

スクリプトの解説

以下処理で回転補正を求めています。

// 回転補正計算
var offsetRot = Quaternion.Inverse(Quaternion.LookRotation(_forward, _up));

Quaternion.Inverseメソッドは、指定されたクォータニオンの逆回転を求めるAPIです。変更された基準軸を戻すような操作を行うことで、今まで通りの回転処理が行えるようになります。

そして、以下処理で回転補正のあとに進行方向に回転するようなクォータニオンを求めています。

// 進行方向(移動量ベクトル)に向くようなクォータニオンを取得
// 回転補正→振り向きの順に回転操作を行う
var targetRot = Quaternion.LookRotation(delta, Vector3.up) * offsetRot;

また、基準軸の変更に伴い、正面方向のベクトル計算も変わっています。

// 現在の向きと進行方向との角度差を計算
// 前方を指定された軸にするように変更している
var diffAngle = Vector3.Angle(_transform.TransformDirection(_forward), delta);

特定の軸周りで回転させる

例えばプレイヤーが操作するキャラクターなどを進行方向に向かせたい場合、水平方向(y軸周り)の振り向きに限定したほうが良いでしょう。

以下、指定された軸周りに限定して回転させるようにした例です。滑らかな回転や基準軸の指定処理も含まれています。

RotateToMovementDirectionAxis.cs
using UnityEngine;

public class RotateToMovementDirectionAxis : MonoBehaviour
{
    // 最大の回転角速度[deg/s]
    [SerializeField] private float _maxAngularSpeed = Mathf.Infinity;

    // 進行方向に向くのにかかるおおよその時間[s]
    [SerializeField] private float _smoothTime = 0.1f;

    // オブジェクトの正面
    [SerializeField] private Vector3 _forward = Vector3.forward;

    // オブジェクトの上向き
    [SerializeField] private Vector3 _up = Vector3.up;

    // 回転軸
    [SerializeField] private Vector3 _axis = Vector3.up;

    private Transform _transform;

    // 前フレームのワールド位置
    private Vector3 _prevPosition;

    private float _currentAngularVelocity;

    private void Start()
    {
        _transform = transform;

        _prevPosition = _transform.position;
    }

    private void Update()
    {
        // 現在フレームのワールド位置
        var position = _transform.position;

        // 移動量を計算
        var delta = position - _prevPosition;

        // 次のUpdateで使うための前フレーム位置更新
        _prevPosition = position;

        // 静止している状態だと、進行方向を特定できないため回転しない
        if (delta == Vector3.zero)
            return;

        // 回転補正計算
        var offsetRot = Quaternion.Inverse(Quaternion.LookRotation(_forward, _up));

        // ワールド空間の前方ベクトル取得
        var forward = _transform.TransformDirection(_forward);

        // 回転軸と垂直な平面に投影したベクトル計算
        var projectFrom = Vector3.ProjectOnPlane(forward, _axis);
        var projectTo = Vector3.ProjectOnPlane(delta, _axis);
        
        // 軸周りの角度差を求める
        var diffAngle = Vector3.Angle(projectFrom, projectTo);

        // 現在フレームで回転する角度の計算
        var rotAngle = Mathf.SmoothDampAngle(
            0,
            diffAngle,
            ref _currentAngularVelocity,
            _smoothTime,
            _maxAngularSpeed
        );
        
        // 軸周りでの回転の開始と終了を計算
        var lookFrom = Quaternion.LookRotation(projectFrom);
        var lookTo = Quaternion.LookRotation(projectTo);

        // 現在フレームにおける回転を計算
        var nextRot = Quaternion.RotateTowards(lookFrom, lookTo, rotAngle) * offsetRot;

        // オブジェクトの回転に反映
        _transform.rotation = nextRot;
    }
}

上記をRotateToMovementDirectionAxis.csという名前で保存し、移動するオブジェクトにアタッチし、各種パラメータの設定を行います。

以下は、y軸周りで回転させるようにした設定例です。

実行結果

勾配のある移動でも常にy軸周りで振り向くようになっています。

スクリプトの解説

以下のコードで回転軸と垂直な平面に投影したベクトルを求めています。

// 回転軸と垂直な平面に投影したベクトル計算
var projectFrom = Vector3.ProjectOnPlane(forward, _axis);
var projectTo = Vector3.ProjectOnPlane(delta, _axis);

projectFrom現在のオブジェクトの向きの投影ベクトルprojectTo目標の向きの投影ベクトルです。

次の処理で、軸周りの回転角度を計算しています。

// 軸周りの角度差を求める
var diffAngle = Vector3.Angle(projectFrom, projectTo);

// 現在フレームで回転する角度の計算
var rotAngle = Mathf.SmoothDampAngle(
    0,
    diffAngle,
    ref _currentAngularVelocity,
    _smoothTime,
    _maxAngularSpeed
);

そして、軸周りでの回転の開始と終了のクォータニオンを計算します。

// 軸周りでの回転の開始と終了を計算
var lookFrom = Quaternion.LookRotation(projectFrom);
var lookTo = Quaternion.LookRotation(projectTo);

最終的な回転の計算は以下のようになります。

// 現在フレームにおける回転を計算
var nextRot = Quaternion.RotateTowards(lookFrom, lookTo, rotAngle) * offsetRot;

回転補正の適用は先の例と同じ要領で出来ます。

さいごに

オブジェクトを進行方向に回転させる方法を紹介しました。

進行方向をベクトルとして計算し、現在のオブジェクトの向きを進行方向に回転させるようなクォータニオンを計算することで求めることで実現可能です。

また、滑らかに回転させる方法はやや複雑ですが、こちらもクォータニオンを駆使することで実現できます。

関連記事

参考サイト

スポンサーリンク