MRが楽しい

MRやVRについて学習したことを書き残す

指定のトランスフォームを中心にオブジェクトを回転させながら追従させる その2

本日は Unity の小ネタ枠です。
指定のトランスフォームを中心にオブジェクトを回転させながら追従させる方法を記事にします。
今回は2つの異なるトランスフォームを参照して動的に回転半径と回転軸を変動させてみます。
f:id:bluebirdofoz:20210707233550j:plain

前提条件

前回記事の続きです。
bluebirdofoz.hatenablog.com

上記の記事では回転の半径と回転軸をInspectorで指定していました。
今回は更に別のトランスフォームを参照し、その動きに合わせて動的に回転の半径と回転軸を変動させてみます。

Vector3.Dot

2つのベクトルの内積を算出します。
本記事では2オブジェクト間のベクトルとY軸の平行を検出するために利用します。
docs.unity3d.com

public static float Dot (Vector3 lhs, Vector3 rhs);
引数 説明
lhs ベクトル1
rhs ベクトル2

Mathf.Abs

数値の絶対値を求めます。
本記事では2オブジェクト間のベクトルとY軸の平行を検出するために利用します。
docs.unity3d.com

public static float Abs (float f);
引数 説明
f 数値

サンプルシーン

以下の通り、サンプルスクリプトを修正しました。
開始位置となるスタートオブジェクトと、回転の中心となるターゲットオブジェクトを指定します。
実行中は2つのオブジェクト間の距離を回転半径、オブジェクト間のベクトルのY軸方向の直交ベクトルを回転軸とします。
・RotateAroundTargetFromStart.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateAroundTargetFromStart : MonoBehaviour
{
    [SerializeField, Tooltip("開始位置オブジェクト")]
    private GameObject StartObject;

    [SerializeField, Tooltip("ターゲットオブジェクト")]
    private GameObject TargetObject;

    [SerializeField, Tooltip("速度係数")]
    private float SpeedFactor = 0.1f;

    [Tooltip("回転軸")]
    private Vector3 RotateAxis = Vector3.up;

    [Tooltip("半径距離")]
    private float RadiusDistance = 1.0f;

    private void Start()
    {
        // 各オブジェクト位置を取得する
        Vector3 startPosition = StartObject.transform.position;
        Vector3 targetPosition = TargetObject.transform.position;

        // 初期位置はスタートオブジェクトと同じ位置に設定する
        this.transform.position = startPosition;

        // 2オブジェクト間の距離を半径距離とする
        RadiusDistance = CalcRadiusDistance(startPosition, targetPosition);

        // 2オブジェクトを結ぶベクトルのY軸方向の直交ベクトルを回転軸とする
        RotateAxis = CalcRotateAxis(startPosition, targetPosition);
    }

    // Update is called once per frame
    void Update()
    {
        if (StartObject == null || TargetObject == null) return;

        // 各オブジェクト位置を取得する
        Vector3 startPosition = StartObject.transform.position;
        Vector3 targetPosition = TargetObject.transform.position;
        Vector3 selfPosition = this.transform.position;

        // ターゲットオブジェクトの軸方向の移動量はそのまま追従する
        Vector3 diffVector = selfPosition - targetPosition;
        float diffMagnitude = diffVector.magnitude;
        float dot = Vector3.Dot(diffVector, RotateAxis);

        // 回転軸との内積から回転軸方向への移動量を求める
        // 離れすぎるとこの演算でエラーが出るので要改良
        selfPosition -= RotateAxis.normalized * (diffMagnitude * dot);

        // 2オブジェクト間の距離を半径距離とする
        RadiusDistance = CalcRadiusDistance(startPosition, targetPosition);

        // 現在の距離と半径距離の差分を取得する
        float diffDistance = Vector3.Distance(selfPosition, targetPosition) - RadiusDistance;

        // 指定半径の距離だけ近づく(or離れる)
        this.transform.position = Vector3.MoveTowards(selfPosition, targetPosition, diffDistance);

        // 2オブジェクトを結ぶベクトルのY軸方向の直交ベクトルを回転軸とする
        RotateAxis = CalcRotateAxis(startPosition, targetPosition);

        // 指定オブジェクトを中心に回転する
        this.transform.RotateAround(
            targetPosition,
            RotateAxis,
            360.0f / (1.0f / SpeedFactor) * Time.deltaTime
            );
    }

    /// <summary>
    /// 回転半径を計算する
    /// </summary>
    /// <param name="a_StartPos">開始オブジェクト位置</param>
    /// <param name="a_TargetPos">ターゲットオブジェクト位置</param>
    /// <returns>回転半径</returns>
    private float CalcRadiusDistance(Vector3 a_StartPos, Vector3 a_TargetPos)
    {
        // 2点間の距離を回転半径とする
        return Vector3.Distance(a_StartPos, a_TargetPos);
    }

    /// <summary>
    /// 回転軸ベクトルを計算する
    /// </summary>
    /// <param name="a_StartPos">開始オブジェクト位置</param>
    /// <param name="a_TargetPos">ターゲットオブジェクト位置</param>
    /// <returns>回転軸ベクトル</returns>
    private Vector3 CalcRotateAxis(Vector3 a_StartPos, Vector3 a_TargetPos)
    {
        // 2オブジェクトを結ぶベクトルのY軸方向の直交ベクトルを回転軸とする
        Vector3 diffVector = (a_StartPos - a_TargetPos).normalized;

        // 2点間のベクトルとY軸が平行な場合は回転軸をZ軸で指定する
        if (Mathf.Abs(Vector3.Dot(diffVector, Vector3.up)) == 1.0f)
        {
            return Vector3.forward;
        }

        // 側面方向のベクトルを取得するため、2点間のベクトルとY軸との直交ベクトルを求める
        Vector3 sideVector = Vector3.Cross(diffVector, Vector3.up);

        // 2点間のベクトルと側面方向のベクトルの直交ベクトルを回転軸とする
        return Vector3.Cross(diffVector, sideVector);
    }
}

f:id:bluebirdofoz:20210707233642j:plain

シーンに Sphere オブジェクトを作成し、本スクリプトを追加します。
f:id:bluebirdofoz:20210707233653j:plain

追従させるターゲットとして Cube オブジェクトを配置し手指定します。
開始位置のターゲットには[MainCamera]を指定します。
f:id:bluebirdofoz:20210707233703j:plain

動作確認

シーンを再生して動作を確認します。
Sphere オブジェクトがカメラ位置から cube オブジェクトを中心にして回転します。
f:id:bluebirdofoz:20210707233714j:plain

常にカメラと cube オブジェクト間のベクトルの直交ベクトルを回転軸にするため、Sphere はカメラが動いても、カメラと cube の直線上を回り込むように回転します。
f:id:bluebirdofoz:20210707233724j:plain