MRが楽しい

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

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

本日は Unity の小ネタ枠です。
指定のトランスフォームを中心にオブジェクトを回転させながら追従させる方法を記事にします。
f:id:bluebirdofoz:20210705040024j:plain

前提条件

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

上記の記事の方法では回転の半径をチェックしていないため、指定のトランスフォームが大きく移動すると回転半径が変わってしまう問題が発生します。
Vector3.Distance と Vector3.MoveTowards を利用することで半径を一定に保ちながら、指定のトランスフォームを追従することができます。

Vector3.Distance

Vector3.Distance は2点間の距離を返します。
docs.unity3d.com

public static float Distance (Vector3 a, Vector3 b);
引数 説明
a 座標1
b 座標2

Vector3.MoveTowards

Vector3.Distance は移動元の座標から移動先の座標まで指定した距離を移動します。
移動先までの距離が指定した距離よりも短い場合、返却される座標は移動先の座標と等値になります。
docs.unity3d.com

public static Vector3 MoveTowards (Vector3 current, Vector3 target, float maxDistanceDelta);
引数 説明
current 移動元の座標
target 移動先の座標
maxDistanceDelta 移動距離

Vector3.Cross

2つのベクトルと直交するベクトルを算出します。
本記事では回転軸に直交するベクトルを算出するために利用します。
docs.unity3d.com

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

サンプルシーン

以下の通り、サンプルスクリプトを修正しました。
・RotateAroundTarget.cs

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

public class RotateAroundTarget : MonoBehaviour
{
    [SerializeField, Tooltip("ターゲットオブジェクト")]
    private GameObject TargetObject;

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

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

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

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

        // 指定オブジェクトと自身の現在位置を取得する
        Vector3 selfPosition = this.transform.position;
        Vector3 targetPosition = TargetObject.transform.position;

        // 座標が重なっていた場合は回転軸の直交方向に離れた場所を初期位置とする
        if (selfPosition.Equals(targetPosition))
        {
            // 直交ベクトルを求めるため、回転軸に平行でないダミーベクトルを作成する
            Vector3 rotateAxisNormal = RotateAxis.normalized;
            Vector3 dummyDirectVector = Vector3.forward;
            if (Mathf.Abs(rotateAxisNormal.y) < 0.5f) dummyDirectVector = Vector3.up;

            // 回転軸とダミーベクトルから直交ベクトルを算出し、初期位置を設定する
            Vector3 directVector = Vector3.Cross(RotateAxis, dummyDirectVector).normalized;
            selfPosition = directVector * RadiusDistance;
        }

        // 軸方向の移動量は追従する
        Vector3 diffVector = selfPosition - targetPosition;
        float diffMagnitude = diffVector.magnitude;
        float dot = Vector3.Dot(diffVector, RotateAxis);
        // 回転軸との内積から回転軸方向への移動量を求める
        selfPosition -= RotateAxis.normalized * (diffMagnitude * dot);

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

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

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

f:id:bluebirdofoz:20210705040550j:plain

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

今回はプレイヤーに追従させるため、[TargetObject]に[MainCamera]を指定します。
f:id:bluebirdofoz:20210705040611j:plain

動作確認

シーンを再生して動作を確認します。
プレイヤーの周囲を Sphere が指定半径で回転します。
f:id:bluebirdofoz:20210705040621j:plain

プレイヤーが移動しても一定距離を保ちながら追跡します。
f:id:bluebirdofoz:20210705040631j:plain

参考ページ

techblog.kayac.com