MRが楽しい

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

Unityで指定のオブジェクトを中心に他オブジェクトを連動して回転と移動をさせる その3(掴んだオブジェクトで相互作用させる)

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

前回記事

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

掴んだオブジェクトで相互作用させる

前回記事では指定オブジェクトを中心に他オブジェクトを連動して回転と移動をさせるスクリプトを作成しました。
掴み操作が発生したイベントで指定オブジェクトを切り変えることで、どのオブジェクトを操作してもその他のオブジェクトを対象にすることも可能です。

掴んだオブジェクトを中心に、それ以外の他オブジェクトの回転と移動を行う以下のサンプルスクリプトを作成しました。
・MultiManipulator.cs

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

public class MultiManipulator : MonoBehaviour
{
    [SerializeField]
    private List<ObjectManipulator> objectsManipulators;

    // 同期機能の有効無効切り替え
    [SerializeField]
    private bool enableSynchronization = true;

    // 現在操作中のオブジェクト(主オブジェクト)
    private ObjectManipulator currentMainManipulator;
    
    // 主オブジェクトの前回の位置と回転
    private Vector3 lastMainPosition;
    private Quaternion lastMainRotation;
    
    // 各オブジェクトと主オブジェクトの相対関係を保存するクラス
    private class RelativeTransform
    {
        public Vector3 relativePosition;
        public Quaternion relativeRotation;
        public ObjectManipulator manipulator;
        
        public RelativeTransform(ObjectManipulator manipulator, Vector3 relativePos, Quaternion relativeRot)
        {
            this.manipulator = manipulator;
            this.relativePosition = relativePos;
            this.relativeRotation = relativeRot;
        }
    }
    
    // 従オブジェクトの相対関係リスト
    private List<RelativeTransform> relativeTransforms = new List<RelativeTransform>();

    void Start()
    {
        // オブジェクトの掴み状態を検出する
        foreach (var manipulator in objectsManipulators)
        {
            manipulator.OnManipulationStarted.AddListener((eventData) =>
            {
                // 現在の主オブジェクトを設定
                currentMainManipulator = manipulator;
                
                if (enableSynchronization)
                {
                    // 主オブジェクトの現在の位置・回転を記録
                    lastMainPosition = manipulator.transform.position;
                    lastMainRotation = manipulator.transform.rotation;
                    
                    // 他のオブジェクトとの相対関係を計算・保存
                    CalculateRelativeTransforms(manipulator);
                }
                else
                {
                    // 同期機能が無効の場合、掴んだオブジェクト以外の操作を無効化する
                    foreach (var otherManipulator in objectsManipulators)
                    {
                        if (otherManipulator != manipulator)
                        {
                            otherManipulator.enabled = false;
                        }
                    }
                }
            });

            manipulator.OnManipulationEnded.AddListener((eventData) =>
            {
                // 主オブジェクトをクリア
                currentMainManipulator = null;
                relativeTransforms.Clear();
                
                if (!enableSynchronization)
                {
                    // 全てのオブジェクトの操作を有効化する
                    foreach (var otherManipulator in objectsManipulators)
                    {
                        otherManipulator.enabled = true;
                    }
                }
            });
        }   
        
    }

    void Update()
    {
        // 同期機能が有効で、現在操作中のオブジェクトがある場合
        if (enableSynchronization && currentMainManipulator != null)
        {
            UpdateSynchronizedObjects();
        }
    }

    /// <summary>
    /// 主オブジェクトと他のオブジェクトとの相対関係を計算して保存
    /// </summary>
    private void CalculateRelativeTransforms(ObjectManipulator mainManipulator)
    {
        relativeTransforms.Clear();
        
        foreach (var manipulator in objectsManipulators)
        {
            if (manipulator != mainManipulator)
            {
                // 主オブジェクトからの相対位置を計算
                Vector3 relativePos = Quaternion.Inverse(mainManipulator.transform.rotation) * 
                                     (manipulator.transform.position - mainManipulator.transform.position);
                
                // 主オブジェクトからの相対回転を計算
                Quaternion relativeRot = Quaternion.Inverse(mainManipulator.transform.rotation) * 
                                        manipulator.transform.rotation;
                
                relativeTransforms.Add(new RelativeTransform(manipulator, relativePos, relativeRot));
            }
        }
    }

    /// <summary>
    /// 主オブジェクトの移動・回転に合わせて他のオブジェクトを同期移動・回転
    /// </summary>
    private void UpdateSynchronizedObjects()
    {
        if (currentMainManipulator == null || relativeTransforms.Count == 0)
            return;

        // 主オブジェクトの現在の位置・回転
        Vector3 currentMainPosition = currentMainManipulator.transform.position;
        Quaternion currentMainRotation = currentMainManipulator.transform.rotation;

        // 位置や回転に変化があるかチェック
        bool hasPositionChanged = Vector3.Distance(currentMainPosition, lastMainPosition) > 0.001f;
        bool hasRotationChanged = Quaternion.Angle(currentMainRotation, lastMainRotation) > 0.1f;

        if (hasPositionChanged || hasRotationChanged)
        {
            // 各従オブジェクトを同期移動・回転
            foreach (var relativeTransform in relativeTransforms)
            {
                // 新しい位置を計算(主オブジェクトの回転を考慮した相対位置)
                Vector3 newPosition = currentMainPosition + 
                                    currentMainRotation * relativeTransform.relativePosition;

                // 新しい回転を計算(主オブジェクトの回転 × 相対回転)
                Quaternion newRotation = currentMainRotation * relativeTransform.relativeRotation;

                // オブジェクトの位置・回転を更新
                relativeTransform.manipulator.transform.position = newPosition;
                relativeTransform.manipulator.transform.rotation = newRotation;
            }

            // 主オブジェクトの前回の位置・回転を更新
            lastMainPosition = currentMainPosition;
            lastMainRotation = currentMainRotation;
        }
    }

    /// <summary>
    /// 同期機能のオン/オフを切り替える公開メソッド
    /// </summary>
    public void SetSynchronizationEnabled(bool enabled)
    {
        enableSynchronization = enabled;
        
        // 同期機能を無効にした場合、現在の操作をリセット
        if (!enabled && currentMainManipulator != null)
        {
            currentMainManipulator = null;
            relativeTransforms.Clear();
            
            // 全てのオブジェクトの操作を有効化
            foreach (var manipulator in objectsManipulators)
            {
                manipulator.enabled = true;
            }
        }
    }
}

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

以下のようにどのオブジェクトを掴んでもそのオブジェクトを中心にその他のオブジェクトの回転と移動が行われます。