MRが楽しい

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

Unityで指定のオブジェクトを中心に他オブジェクトを連動して回転と移動をさせる その2(移動後に反映する)

本日はUnityの技術調査枠です。
Unityで指定のオブジェクトを中心に他オブジェクトを連動して回転と移動をさせる方法についてです。

前回記事

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

移動量を後から反映する

前回記事では指定オブジェクトを中心に他オブジェクトを連動して回転と移動をさせるスクリプトを作成しました。
このロジックは移動開始時点の指定オブジェクトの状態と現在の状態を計算式に用いているため、Updateによる毎フレームの処理でなくても実行可能です。

指定オブジェクトの移動を記録しておき、後から指定された他オブジェクトの回転と移動を行う以下のサンプルスクリプトを作成しました。
・MoveAsynchronousManipulator.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Microsoft.MixedReality.Toolkit.UI;
using Microsoft.MixedReality.Toolkit.Input;

public class MoveAsynchronousManipulator : MonoBehaviour
{
    [SerializeField]
    private ObjectManipulator pivotPointManipulator;

    // 前回の操作記録を保存する構造体
    [System.Serializable]
    private struct ManipulationRecord
    {
        public Vector3 previousPosition;
        public Quaternion previousRotation;
        public Vector3 currentPosition;
        public Quaternion currentRotation;
        public bool hasValidRecord;
    }

    private ManipulationRecord manipulationRecord;

    public void ApplyManipulationTo(Transform targetTransform)
    {
        if (targetTransform == null || !manipulationRecord.hasValidRecord)
            return;

        // ピボットの変化量を計算
        Vector3 positionDelta = manipulationRecord.currentPosition - manipulationRecord.previousPosition;
        Quaternion rotationDelta = manipulationRecord.currentRotation * Quaternion.Inverse(manipulationRecord.previousRotation);

        // ターゲットオブジェクトの現在の位置を基準に変化を適用
        Vector3 currentTargetPosition = targetTransform.position;
        Quaternion currentTargetRotation = targetTransform.rotation;

        // ピボットからターゲットへの相対位置を計算
        Vector3 relativePosition = currentTargetPosition - manipulationRecord.previousPosition;

        // 回転を適用した相対位置を計算
        Vector3 rotatedRelativePosition = rotationDelta * relativePosition;

        // 新しい位置を計算(ピボットの移動 + 回転による位置変化)
        Vector3 newPosition = manipulationRecord.currentPosition + rotatedRelativePosition;

        // 新しい回転を計算(ターゲットの回転にピボットの回転変化を適用)
        Quaternion newRotation = rotationDelta * currentTargetRotation;

        // ターゲットオブジェクトに変化を適用
        targetTransform.position = newPosition;
        targetTransform.rotation = newRotation;
    }

    /// <summary>
    /// 操作記録をリセットする
    /// </summary>
    public void ClearManipulationRecord()
    {
        manipulationRecord.hasValidRecord = false;
        if (pivotPointManipulator != null)
        {
            manipulationRecord.previousPosition = pivotPointManipulator.transform.position;
            manipulationRecord.previousRotation = pivotPointManipulator.transform.rotation;
            manipulationRecord.currentPosition = pivotPointManipulator.transform.position;
            manipulationRecord.currentRotation = pivotPointManipulator.transform.rotation;
        }
    }

    /// <summary>
    /// 有効な操作記録があるかどうかを確認する
    /// </summary>
    public bool HasValidManipulationRecord()
    {
        return manipulationRecord.hasValidRecord;
    }

    /// <summary>
    /// 現在の操作記録の移動量を取得する
    /// </summary>
    public Vector3 GetPositionDelta()
    {
        if (!manipulationRecord.hasValidRecord)
            return Vector3.zero;

        return manipulationRecord.currentPosition - manipulationRecord.previousPosition;
    }

    /// <summary>
    /// 現在の操作記録の回転量を取得する
    /// </summary>
    public Quaternion GetRotationDelta()
    {
        if (!manipulationRecord.hasValidRecord)
            return Quaternion.identity;

        return manipulationRecord.currentRotation * Quaternion.Inverse(manipulationRecord.previousRotation);
    }

    void Start()
    {
        if (pivotPointManipulator != null)
        {
            // イベントハンドラーを登録
            pivotPointManipulator.OnManipulationStarted.AddListener(OnManipulationStarted);
            pivotPointManipulator.OnManipulationEnded.AddListener(OnManipulationEnded);

            // 初期状態を記録
            InitializeRecord();
        }
    }

    private void InitializeRecord()
    {
        if (pivotPointManipulator != null)
        {
            manipulationRecord.previousPosition = pivotPointManipulator.transform.position;
            manipulationRecord.previousRotation = pivotPointManipulator.transform.rotation;
            manipulationRecord.currentPosition = pivotPointManipulator.transform.position;
            manipulationRecord.currentRotation = pivotPointManipulator.transform.rotation;
            manipulationRecord.hasValidRecord = false;
        }
    }

    private void OnManipulationStarted(ManipulationEventData eventData)
    {
        if (pivotPointManipulator != null)
        {
            // 操作開始時の位置・回転を記録
            manipulationRecord.previousPosition = pivotPointManipulator.transform.position;
            manipulationRecord.previousRotation = pivotPointManipulator.transform.rotation;
        }
    }

    private void OnManipulationEnded(ManipulationEventData eventData)
    {
        if (pivotPointManipulator != null)
        {
            // 操作終了時の位置・回転を記録
            manipulationRecord.currentPosition = pivotPointManipulator.transform.position;
            manipulationRecord.currentRotation = pivotPointManipulator.transform.rotation;
            manipulationRecord.hasValidRecord = true; // 有効な操作記録として設定
        }
    }
}

合わせてEditor上から実行できるように以下のコンテクストメニューを持つスクリプトを追加しました。

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

public class MoveAsynchronousEditor : MonoBehaviour
{
    [SerializeField]
    private MoveAsynchronousManipulator moveAsynchronousManipulator;

    [SerializeField]
    private List<Transform> targetObjects;

    [ContextMenu("Apply Manipulation To All Targets")]
    public void ApplyManipulationToAllTargets()
    {
        if (targetObjects == null || moveAsynchronousManipulator == null)
            return;

        foreach (Transform target in targetObjects)
        {
            moveAsynchronousManipulator.ApplyManipulationTo(target);
        }
    }
}

サンプルスクリプトを設定したシーンを実際に再生して試してみます。

先に基準点となるオブジェクトを移動し回転します。

その後、コンテクストメニューから移動量の反映を行うと、指定オブジェクトを中心に連動して回転と移動をさせた場合の移動結果が反映されます。