MRが楽しい

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

Unityで指定のオブジェクトを中心に他オブジェクトを連動して回転と移動をさせる

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

指定のオブジェクトを中心に他オブジェクトを回転させる

指定のオブジェクトを中心に他オブジェクトを回転させる場合、基準となるオブジェクトからの相対回転による移動を計算する必要があります。
相対回転による影響はQuaternion.Inverse()を利用して以下の通り計算できます。

GameObject pivotObject; // 中心となる指定オブジェクト
GameObject targetObject; // 連動する他オブジェクト
Vector3 pivotInitialPosition; // 保存しておいた指定オブジェクトの過去の位置
Quaternion pivotInitialRotation; // 保存しておいた指定オブジェクトの過去の回転

// 現在の回転
Quaternion pivotCurrentRotation = pivotObject.transform.rotation;

// 指定オブジェクトからの回転量を計算
Quaternion rotationDelta = pivotCurrentRotation * Quaternion.Inverse(pivotInitialRotation);

// 指定オブジェクトから連動する他オブジェクトの相対位置と相対回転
relativePosition = targetObject.position - pivotInitialPosition;
relativeRotation = Quaternion.Inverse(pivotInitialRotation) * targetObject.rotation

// 相対回転による位置の移動量を計算する
Vector3 rotatedRelativePosition = rotationDelta * relativePosition;

// 更に指定オブジェクトの移動量を加算する
Vector3 newPosition = pivotCurrentPosition + rotatedRelativePosition;

// 他オブジェクト自身の回転量を計算する
Quaternion newRotation = pivotCurrentRotation * relativeRotation;

docs.unity3d.com

サンプルスクリプト

指定のオブジェクトを中心に他オブジェクトを連動して回転と移動をさせる以下のサンプルスクリプトを作成しました。
・MoveSynchronousManipulator.cs

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

/// <summary>
/// 複数のオブジェクトを同期して移動・回転させる
/// 基準オブジェクトとなるオブジェクトの操作に合わせて、
/// 基準オブジェクトを中心に他のオブジェクトを連動して移動・回転する
/// </summary>
public class MoveSynchronousManipulator : MonoBehaviour
{
    [SerializeField]
    // 基準オブジェクトとなるオブジェクトのマニピュレーター
    private ObjectManipulator pivotPointManipulator;

    [SerializeField]
    // 同期して移動させるオブジェクトのリスト
    private List<Transform> synchronousObjects;

    // 初期位置と回転を記録する構造体
    [System.Serializable]
    private struct ObjectState
    {
        // オブジェクトの初期位置
        public Vector3 initialPosition;
        // オブジェクトの初期回転
        public Quaternion initialRotation;
        // ピボットからの相対位置
        public Vector3 relativePosition;
        // ピボットからの相対回転
        public Quaternion relativeRotation;
    }

    // 同期オブジェクトの初期状態リスト
    private List<ObjectState> initialStates = new List<ObjectState>();

    // 基準オブジェクトの初期位置
    private Vector3 pivotInitialPosition;

    // 基準オブジェクトの初期回転
    private Quaternion pivotInitialRotation;

    /// <summary>
    /// 初期化処理
    /// </summary>
    void Start()
    {
        // 基準オブジェクトのイベントハンドラーを登録し、初期状態を記録する
        if (pivotPointManipulator != null)
        {
            // イベントハンドラーを登録
            pivotPointManipulator.OnManipulationStarted.AddListener(OnManipulationStarted);

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

    /// <summary>
    /// 毎フレーム処理
    /// </summary>
    void Update()
    {
        // ピボットオブジェクトが操作中の場合、同期オブジェクトを更新
        if (pivotPointManipulator != null && synchronousObjects != null)
        {
            // ピボットオブジェクトの変化に合わせて同期オブジェクトの位置・回転を更新する
            UpdateSynchronousObjects();
        }
    }

    /// <summary>
    /// 初期状態を記録する
    /// 基準オブジェクトと同期オブジェクトの初期位置・回転、および相対関係を保存する
    /// </summary>
    private void RecordInitialStates()
    {
        if (pivotPointManipulator == null || synchronousObjects == null)
            return;

        // 基準オブジェクトの初期状態を記録
        pivotInitialPosition = pivotPointManipulator.transform.position;
        pivotInitialRotation = pivotPointManipulator.transform.rotation;

        initialStates.Clear();
        // 各同期オブジェクトの初期状態と相対関係を記録
        foreach (Transform obj in synchronousObjects)
        {
            if (obj != null)
            {
                ObjectState state = new ObjectState
                {
                    initialPosition = obj.position,
                    initialRotation = obj.rotation,
                    relativePosition = obj.position - pivotInitialPosition, // ピボットからの相対位置
                    relativeRotation = Quaternion.Inverse(pivotInitialRotation) * obj.rotation // ピボットからの相対回転
                };
                initialStates.Add(state);
            }
        }
    }

    /// <summary>
    /// マニピュレーション開始時のイベントハンドラー
    /// </summary>
    private void OnManipulationStarted(ManipulationEventData eventData)
    {
        // 操作開始時に初期状態を再記録
        RecordInitialStates();
    }

    /// <summary>
    /// 同期オブジェクトの位置・回転を更新する
    /// 基準オブジェクトの変化量を計算し、各同期オブジェクトに同じ変化を適用する
    /// </summary>
    private void UpdateSynchronousObjects()
    {
        if (initialStates.Count != synchronousObjects.Count)
            return;

        // 基準オブジェクトの現在の位置・回転を取得
        Vector3 pivotCurrentPosition = pivotPointManipulator.transform.position;
        Quaternion pivotCurrentRotation = pivotPointManipulator.transform.rotation;

        // 基準オブジェクトからの移動量と回転量を計算
        Vector3 positionDelta = pivotCurrentPosition - pivotInitialPosition;
        Quaternion rotationDelta = pivotCurrentRotation * Quaternion.Inverse(pivotInitialRotation);

        // 各同期オブジェクトに変化を適用
        for (int i = 0; i < synchronousObjects.Count; i++)
        {
            if (synchronousObjects[i] != null && i < initialStates.Count)
            {
                ObjectState state = initialStates[i];

                // 回転を適用してから位置を計算
                Vector3 rotatedRelativePosition = rotationDelta * state.relativePosition;
                Vector3 newPosition = pivotCurrentPosition + rotatedRelativePosition;

                // 新しい回転を計算
                Quaternion newRotation = pivotCurrentRotation * state.relativeRotation;

                // 同期オブジェクトの位置と回転を更新
                synchronousObjects[i].position = newPosition;
                synchronousObjects[i].rotation = newRotation;
            }
        }
    }
}

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

以下の通り、基準点となるオブジェクトを移動し回転すると他オブジェクトがそれを中心に相対移動と回転を行いました。