対象物(ターゲット)の方向を向く(2D)

2024年4月10日

3DプログラムではLookAtで可能ですが、2Dの場合、思うような動作になりません。下記のようにすることでtargetの方向に向きを変えることができます。

特定の座標に向けて向きを変える

サンプル動画

使うメソッドは、次のものになります
aからbへの回転を作成します

Quaternion.FromToRotation(a, b)

具体的には、次のように使います
Y座標(Vector3.up)をターゲットの方向(toDirection)に向かせるものになります

Quaternion.FromToRotation(Vector3.up, toDirection)

Vector3.upは、new Vector3(0, 1 , 0)です
真上のY軸を回転させて、toDirection(ベクトル)の方向に向かせる処理になります。

using UnityEngine;
public class LookAt2D : MonoBehaviour
{
    [SerializeField]
    GameObject target;

    void Update()
    {
        // 対象物へのベクトルを算出
        Vector3 toDirection = target.transform.position - transform.position;
        // 対象物へ回転する
        transform.rotation = Quaternion.FromToRotation(Vector3.up, toDirection);
    }
}
using UnityEngine;

public class LookAt2D : MonoBehaviour
{
    [SerializeField]
    private GameObject target; // 対象のGameObjectへの参照

    private void Update()
    {
        RotateTowardsTarget();
    }

    /// <summary>
    /// オブジェクトをターゲットの方向に回転させる。
    /// </summary>
    private void RotateTowardsTarget()
    {
        // ターゲットが設定されていなければ、処理をスキップ
        if (target == null) return;

        // ターゲットへの方向を算出
        Vector3 targetDirection = CalculateDirectionTowardsTarget();

        // ターゲットの方向へ回転させる
        AlignRotationToTarget(targetDirection);
    }

    /// <summary>
    /// ターゲットへの方向ベクトルを計算する。
    /// </summary>
    /// <returns>ターゲットへの方向ベクトル。</returns>
    private Vector3 CalculateDirectionTowardsTarget()
    {
        return target.transform.position - transform.position;
    }

    /// <summary>
    /// 計算された方向にオブジェクトを回転させる。
    /// </summary>
    /// <param name="direction">ターゲットへの方向。</param>
    private void AlignRotationToTarget(Vector3 direction)
    {
        transform.rotation = Quaternion.FromToRotation(Vector3.up, direction);
    }
}

リファクタリングのポイント

  • メソッドの分割: Updateメソッド内で行われていた処理を、RotateTowardsTarget, CalculateDirectionTowardsTarget, および AlignRotationToTarget の3つのメソッドに分割しました。これにより、各メソッドの責務が明確になり、コードの読みやすさが向上します。
  • nullチェックの追加: RotateTowardsTarget メソッドの冒頭で target が null でないかをチェックすることで、エラーの可能性を低減しました。
  • 明確な変数名とコメントの使用: コードの各部分にコメントを追加し、変数名をより明確にすることで、コードの意図を理解しやすくしました。これにより、他の開発者がコードを読んで理解する時間が短縮されます。
  • SerializeFieldの使用: インスペクターから直接 target を設定できるように SerializeField 属性を使用していますが、これは元のコードからの変更ではありません。ただし、変数のアクセス修飾子を private に設定することで、オブジェクトのカプセル化を保持しています。

これらの変更により、コードの整理と構造の明確化が図られ、将来的な拡張やメンテナンスが容易になります。

実行結果

タイアモンドオブジェクトにアタッチされているLookAt2DスクリプトのTargetフィールドに正方形を代入(アウトレット接続)します。これが追跡対象となるターゲットになります

ゆっくり向きを変える

Mathf.LerpAngleとQuaternion.Eulerを使用する方法

Mathf.LerpAngleは、二つの角度の間を補間することができる関数で、Quaternion.Eulerは、Euler角をQuaternionに変換する関数です。具体的には、現在の角度とターゲットの角度を計算して、LerpAngleで補間することで、ゆっくりとターゲットの方向を向くことができます。

public Transform target;
public float rotationSpeed = 2f;
 
void Update()
{
    Vector2 direction = target.position - transform.position;
    float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
    Quaternion targetRotation = Quaternion.Euler(0, 0, angle);
    transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
}
using UnityEngine;

public class SmoothRotation : MonoBehaviour
{
    public Transform target; // ターゲットへの参照
    public float rotationSpeed = 2f; // 回転のスピード

    private void Update()
    {
        RotateTowardsTargetSmoothly();
    }

    /// <summary>
    /// ターゲットに向けてオブジェクトをゆっくりと回転させる。
    /// </summary>
    private void RotateTowardsTargetSmoothly()
    {
        if (target == null) return; // ターゲットが設定されていなければ、処理をスキップ

        // ターゲットに向けた回転を計算
        Quaternion targetRotation = CalculateTargetRotation();

        // 現在の回転から目標の回転へと、線形補間を使用して滑らかに回転させる
        SmoothlyInterpolateRotation(targetRotation);
    }

    /// <summary>
    /// ターゲットに向けた回転を計算する。
    /// </summary>
    /// <returns>ターゲットに向けた回転。</returns>
    private Quaternion CalculateTargetRotation()
    {
        Vector2 direction = target.position - transform.position;
        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        return Quaternion.Euler(0, 0, angle);
    }

    /// <summary>
    /// 指定された回転へとオブジェクトを滑らかに回転させる。
    /// </summary>
    /// <param name="targetRotation">目標の回転。</param>
    private void SmoothlyInterpolateRotation(Quaternion targetRotation)
    {
        transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
    }
}

リファクタリングのポイント

  • メソッドの分割: オブジェクトをターゲットに向けて回転させる処理を、RotateTowardsTargetSmoothlyCalculateTargetRotationSmoothlyInterpolateRotationという3つのメソッドに分割しました。これにより、コードがより構造化され、各部分の責任が明確になります。
  • 明確な命名: メソッドと変数の命名を明確にすることで、コードを読む人がそれぞれの部分が何をするのかを容易に理解できるようにしました。
  • 条件チェックの追加: RotateTowardsTargetSmoothlyメソッドの最初に、targetnullでないかのチェックを追加しました。これにより、targetが設定されていない場合に不要な計算をスキップできます。
  • コメントの追加: コードの各部分に説明コメントを追加しました。これにより、コードの目的や機能がより明確に伝わります。

このリファクタリングを通じて、コードの読みやすさと保守性が向上し、将来の変更や拡張が容易になります。また、各メソッドが単一の責務を持つようになり、再利用性も向上します。

Vector2.AngleとMathf.MoveTowardsAngleを使用する方法

Vector2.Angleは、二つのベクトルの角度を計算する関数で、Mathf.MoveTowardsAngleは、現在の角度から目標の角度に向かってゆっくりと角度を変化させる関数です。具体的には、現在の角度とターゲットの角度を計算して、MoveTowardsAngleで補間することで、ゆっくりとターゲットの方向を向くことができます。

public Transform target;
public float rotationSpeed = 2f;
 
void Update()
{
    Vector2 direction = target.position - transform.position;
    float angle = Vector2.Angle(Vector2.right, direction);
    float targetAngle = direction.y < 0 ? -angle : angle;
    float currentAngle = transform.eulerAngles.z;
    float newAngle = Mathf.MoveTowardsAngle(currentAngle, targetAngle, rotationSpeed * Time.deltaTime);
    transform.rotation = Quaternion.Euler(0, 0, newAngle);
}
using UnityEngine;

public class SmoothRotation : MonoBehaviour
{
    public Transform target; // ターゲットへの参照
    public float rotationSpeed = 2f; // 回転のスピード

    private void Update()
    {
        RotateTowardsTarget();
    }

    /// <summary>
    /// ターゲットに向けてオブジェクトをゆっくりと回転させる。
    /// </summary>
    private void RotateTowardsTarget()
    {
        if (target == null) return; // ターゲットが設定されていなければ、処理をスキップ

        // ターゲットに向けて必要な新しい角度を計算
        float targetAngle = CalculateTargetAngle();

        // 現在の角度からターゲットの角度へと、指定された速度で移動
        float newAngle = MoveTowardsTargetAngle(targetAngle);

        // 新しい角度でオブジェクトを回転させる
        transform.rotation = Quaternion.Euler(0, 0, newAngle);
    }

    /// <summary>
    /// ターゲットに向けた角度を計算する。
    /// </summary>
    /// <returns>ターゲットに向けた角度。</returns>
    private float CalculateTargetAngle()
    {
        Vector2 direction = target.position - transform.position;
        float angle = Vector2.Angle(Vector2.right, direction);
        return direction.y < 0 ? -angle : angle;
    }

    /// <summary>
    /// 現在の角度からターゲットの角度へと移動する。
    /// </summary>
    /// <param name="targetAngle">ターゲットの角度。</param>
    /// <returns>新しい角度。</returns>
    private float MoveTowardsTargetAngle(float targetAngle)
    {
        float currentAngle = transform.eulerAngles.z;
        return Mathf.MoveTowardsAngle(currentAngle, targetAngle, rotationSpeed * Time.deltaTime);
    }
}

リファクタリングのポイント

  • メソッドの分割: ターゲットに向けて回転する処理を、複数の小さなメソッドに分割しました。これにより、各メソッドが単一の責任を持ち、コードの再利用性とテストのしやすさが向上します。
  • 明確な命名: メソッドや変数の命名をより具体的にしました。これにより、コードを読むだけでその機能や目的が理解しやすくなります。
  • 条件チェックの追加: RotateTowardsTargetメソッドの始めに、targetnullでないことを確認するチェックを追加しました。これにより、targetが未設定の場合にエラーが発生するのを防ぎます。
  • コメントの追加: コードの各部分にコメントを追加して、その目的や機能を説明しました。これにより、他の開発者がコードの意図をより簡単に理解できるようになります。

これらの変更により、コードの可読性、メンテナンス性が向上し、他の開発者がコードを理解しやすくなります。

どちらの方法でも、2Dの場合でもターゲットの方向を向くことができます。ただし、Mathf.LerpAngleを使用する方法では、ターゲットとの距離によっては正しい角度が得られない場合がありますので、その点に注意して使用してください。また、Mathf.MoveTowardsAngleを使用する方法では、角度の変化量を調整することで、滑らかな動きを実現できます。

Quaternion.Slerpを使用する方法

フレームごとにオブジェクトの回転を少しずつターゲットの方向に近づけていきます。球面線形補間は、回転の補間に特化しており、一般に回転のスムージングにはQuaternion.Slerpが推奨されます。

using UnityEngine;

public class LookAt2D : MonoBehaviour
{
    [SerializeField]
    private GameObject target; // ターゲットオブジェクトへの参照

    [SerializeField]
    private float rotationSpeed = 5f; // オブジェクトの回転速度

    private void Update()
    {
        if (target == null) return; // ターゲットが設定されていなければ処理をスキップ

        // ターゲットへの方向を算出し、その方向の正規化ベクトルを使用して目標の回転を計算
        Vector3 targetDirection = target.transform.position - transform.position;
        Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, targetDirection.normalized);

        // 現在の回転から目標の回転へと球面線形補間を使用して滑らかに回転させる
        transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
    }
}
using UnityEngine;

public class LookAt2D : MonoBehaviour
{
    [SerializeField]
    private GameObject target; // ターゲットオブジェクトへの参照。Inspectorから設定可能。

    [SerializeField]
    private float rotationSpeed = 5f; // オブジェクトの回転速度。Inspectorから調整可能。

    private void Update()
    {
        RotateTowardsTargetSlowly();
    }

    /// <summary>
    /// ターゲットに対してゆっくりと回転する処理。
    /// </summary>
    private void RotateTowardsTargetSlowly()
    {
        // ターゲットが設定されていなければ、このメソッドの処理をスキップ
        if (target == null) return;

        // ターゲットへの方向を計算し、目標の回転を得る
        Vector3 targetDirection = CalculateDirectionTowardsTarget();
        Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, targetDirection.normalized);
        
        // 現在の回転から目標の回転へと、球面線形補間を使用して滑らかに回転させる
        transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
    }

    /// <summary>
    /// ターゲットへの方向ベクトルを計算する。
    /// </summary>
    /// <returns>ターゲットへの方向ベクトル。</returns>
    private Vector3 CalculateDirectionTowardsTarget()
    {
        // ターゲットの位置から現在のオブジェクトの位置を引くことで、方向ベクトルを計算
        return target.transform.position - transform.position;
    }
}

リファクタリングのポイント

  • 回転速度の追加: rotationSpeed変数を追加して、オブジェクトがどれだけ速く回転するかを制御できるようにしました。この値を調整することで、回転のスムーズさを変更できます。
  • Quaternion.Slerpの使用: transform.rotationを設定する際にQuaternion.Slerpを使用しています。この関数は、現在の回転と目標となる回転との間を滑らかに補間します。rotationSpeed * Time.deltaTimeは補間の割合を決定し、フレームレートに依存しない一定の速度でオブジェクトが回転するようにします。
  • この修正により、オブジェクトはターゲットに向けてゆっくりと回転し、自然な動きを実現できます。rotationSpeedの値を変更することで、回転の速さを簡単に調整できます。

これらの変更により、コードの可読性、メンテナンス性が向上し、他の開発者がコードを理解しやすくなります。

using UnityEngine;

public class LookAt2D : MonoBehaviour
{
    [SerializeField]
    private GameObject target; // ターゲットオブジェクトへの参照。Inspectorから設定可能。

    [SerializeField]
    private float rotationSpeed = 5f; // オブジェクトの回転速度。Inspectorから調整可能。

    private void Update()
    {
        RotateTowardsTargetSlowly();
    }

    /// <summary>
    /// ターゲットに対してゆっくりと回転する処理。
    /// </summary>
    private void RotateTowardsTargetSlowly()
    {
        // ターゲットが設定されていなければ、このメソッドの処理をスキップ
        if (target == null) return;

        // ターゲットへの方向を計算し、目標の回転を得る
        Vector3 targetDirection = CalculateDirectionTowardsTarget();
        Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, targetDirection.normalized);
        
        // 現在の回転から目標の回転へと、球面線形補間を使用して滑らかに回転させる
        transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
    }

    /// <summary>
    /// ターゲットへの方向ベクトルを計算する。
    /// </summary>
    /// <returns>ターゲットへの方向ベクトル。</returns>
    private Vector3 CalculateDirectionTowardsTarget()
    {
        // ターゲットの位置から現在のオブジェクトの位置を引くことで、方向ベクトルを計算
        return target.transform.position - transform.position;
    }
}
  • 回転速度の追加: rotationSpeed変数を追加して、オブジェクトがどれだけ速く回転するかを制御できるようにしました。この値を調整することで、回転のスムーズさを変更できます。
  • Quaternion.Slerpの使用: transform.rotationを設定する際にQuaternion.Slerpを使用しています。この関数は、現在の回転と目標となる回転との間を滑らかに補間します。rotationSpeed * Time.deltaTimeは補間の割合を決定し、フレームレートに依存しない一定の速度でオブジェクトが回転するようにします。

この修正により、オブジェクトはターゲットに向けてゆっくりと回転し、自然な動きを実現できます。rotationSpeedの値を変更することで、回転の速さを簡単に調整できます。

Unity,小技

Posted by hidepon