[OpenXR] Custom Hand Pose (Mirror Pose) with XR Interaction Toolkit 2.4.3

VR/XR Interaction Toolkit 2024. 1. 1. 01:08
반응형

https://youtube.com/playlist?list=PLTFRwWXfOIYBIPKhWi-ZO_ITXqtNuqj6j&si=XhyHS26tNDP6u7CA

XR Interaction Toolkit 2.4.3

XR Interaction Toolkit 2.4.3 OpenXR Oculus

www.youtube.com


이것은 오큘러스 Integration SDK와 비슷하게 만드는것이다 

 
 
 
 
 
새 씬을 만들고 메인 카메라를 제거후 XR Origin을 생성 한다 
 

 
 
Left/Right Controller를 선택후 XR Controller를 제외한 나머지 컴포넌트를 제거 한다 

 
다음과 같은 구조로 만들고 핸드 프리팹을 넣어 준다 

 
 
 
Left Controller / Hand Visual / Offset을 선택후 Rotation 90, 0, -90으로 설정 한다 

 
Right Controller / Hand Visual / Offset을 선택후 Rotation -90, 0, -90으로 설정 한다 

 
 
실행후 결과를 확인 한다 

 
 
Left/Right Controller를 선택후 XR Controller의 Model Prefab을 None으로 설정 한다 

 
 
실행후 결과를 확인 한다 
 

 
 
프로젝트 창에서 Direct Interactor를 검색해 프리팹을 Left, Right Controller자식으로 넣어준다 

 
 
Direct Controller를 선택해 Sphere Collider의 Radius값을 0.1로 설정 한다 
 

 
 
 
 
 
빈 오브젝트 (Grabbable Cube)를 만들고

 
자식으로 빈오브젝트 Visuals를 만들고 그 자식으로 Cube를 생성한다 

 
 
큐브의 크기를 0.1로 만들어 주고 

 
 
Grabbable Cube를 선택해 위치를 조절 한다 

 
Grabbable Cube를 선택하고 
Rigidbody, XR Grab Interactable을 부착 한다 
Use Gravity는 체크 해제 하고 
Throw on Detach도 체크 해제 하자 

 
 
실행후 결과를 확인하고 
 

 
 
프로젝트 창에서 Interaction Affordance를 검색해 Grabbable Cube자식으로 넣어 주고 

 
 
Interaction Affordance 자식으로 있는 Color Affordance 를 선택해 Renderer프로퍼티에 Cube를 넣어 준다 

 
 
Color Affordance Theme를 살펴 보면 각 상태에 따라 컬러를 변경해준다 

 

 
 
실행해 결과를 확인 하자 
 

 


 
Grabbable Cube를 선택 하고 빈 오브젝트 (Hand Pose)를 만들어 주고 
Left/Right Controller에서 사용했던 프리팹과 동일한 프리팹을 넣어 준다 

 
 
이제 핸드를 선택하고 위치를 적당히 잡아 준다 

 
 
 
HandPose 스크립트와 Editor폴더를 만들고 EditorHandPose스크립트를 만든다 

 
다음과 같이 작성하고 

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

public class HandPose : MonoBehaviour
{
    [SerializeField] private GameObject attachPointPrefab;
    [SerializeField] private Transform wrist;

    public void CreateAttachPoint()
    {
        var point = Instantiate(attachPointPrefab, this.transform);
        point.transform.localPosition =this.transform.InverseTransformPoint(wrist.position); 
    }
}

 

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(HandPose))]
public class EditorHandPose : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        HandPose script = (HandPose)target;

        if (GUILayout.Button("Create Attach Point"))
        {
            script.CreateAttachPoint();
        }
    }
}

 
Hand Pose L 을 선택해 Hand Pose컴포넌트를 부착 한다 

 
 
계층뷰에서 Sphere를 하나 생성하고 크기를 0.01로 설정한후 콜라이더를 제거 하고 이름을 AttachPoint로 변경후 프리팹으로 만들어 준다 
 
 

 
 
Hand Pose L 을 선택후 프로퍼티를 할당한다 
 

 
 
이제 Create Attach Point버튼을 누르면 b_l_wrist위치에 Attach Point오브젝트가 생성된다 

 
 


 
HandData 스크립트를 생성후 
다음과 같이 작성한다 

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

public class HandData : MonoBehaviour
{
    public enum  HandType
    {
        Left, Right
    }

    public HandType handType;
    public Transform[] bones;
    public Transform root;
    public Animator anim;
    
}

 
핸드 포즈 아래 핸드를 선택후 부착 한다 

 
 

 
 
 
Animator를 제거 하고 Root를 할당 한다 
인스펙터를 잠그고 

 
 
17개의 본을 선택후 

 
 
HandData의 Bones프로퍼티에 드레그 드롭 한다 

 
이제 핸드 잠금을 해제 한다 

 
 
 
이번에는 Left Controller아래 있는 핸드를 선택하고 HandData를 부착 하고 프로퍼티를 넣어 주자 

 


 
GrabInteractable 스크립트를 만들고 

 
다음과 같이 작성한후 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class GrabInteractable : XRGrabInteractable
{
    [SerializeField]
    private Transform LeftHandAttachTransform;
    [SerializeField]
    XRDirectInteractor LeftController;
    [SerializeField]
    XRDirectInteractor RightController;
    
    protected override void OnSelectEntering(SelectEnterEventArgs args)
    {
        if (args.interactorObject == LeftController)
        {
            Debug.Log($"Left hand");
            attachTransform.SetPositionAndRotation(LeftHandAttachTransform.position, LeftHandAttachTransform.rotation);
        }
        else if (args.interactorObject == RightController)
        {
            Debug.Log($"Right hand");
        }
        base.OnSelectEntering(args);
    }
}

 
 
Grabbable Cube를 선택하고 XR Grab Interactable을 제거 후 부착 한다 
 

 
 
 
 
Grabbable Cube를 선택하고 빈오브젝트 Attach Point를 생성한다 

 
다음과 같이 프로퍼티를 할당한다 

 
 
DirectInteractor 스크립트를 생성후 
 

다음과 같이 작성하고 Direct Interactor를 선택하 XR Direct Interactor를 제거 하고 부착 한다 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class DirectInteractor : XRDirectInteractor
{
    public HandData.HandType handType;
}

 

 
 
그리고 Hand Type을 변경 한다 

 
 
 
HandPose스크립트를 다음과 같이 수정 한다 

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

public class HandPose : MonoBehaviour
{
    [SerializeField] private GameObject attachPointPrefab;
    [SerializeField] private Transform wrist;


    [SerializeField] private GrabInteractable grabInteractable;
    [SerializeField] private HandData handDataPose;


    private void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {
            var directInteractor = arg.interactorObject as DirectInteractor;

            if (directInteractor.handType == this.handDataPose.handType)
            {
                Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
                var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();

                Debug.LogFormat("handData : {0}", handData);

                handData.anim.enabled = false;

                handData.root.parent.localRotation = handDataPose.root.localRotation;

                for (int i = 0; i < handDataPose.bones.Length; i++)
                {
                    handData.bones[i].localRotation = handDataPose.bones[i].localRotation;
                }
            }
        });


        grabInteractable.selectExited.AddListener((arg) =>
        {
            var directInteractor = arg.interactorObject as DirectInteractor;

            if (directInteractor.handType == this.handDataPose.handType)
            {
                Debug.LogFormat("Select Exited : {0}", arg.interactorObject);

                var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();

                Debug.LogFormat("handData : {0}", handData);

                handData.anim.enabled = true;

                //초기화 
                handData.root.localPosition = Vector3.zero;

                if (this.handDataPose.handType == HandData.HandType.Left)
                    handData.root.parent.localRotation = Quaternion.Euler(new Vector3(90, 0, -90));
            }
        });
    }


    public void CreateAttachPoint()
    {
        var point = Instantiate(attachPointPrefab, this.transform);
        point.transform.localPosition = this.transform.InverseTransformPoint(wrist.position);
    }
}

 
핸드 포즈를 눌러 프로퍼티를 할당한다 

 
 
Grabbable Cube를 선택해 Grab Interactable 프로퍼티를 넣어준다 

 
 
 
핸드 포즈 자식의 핸드를 선택해 Hand컴포넌트를 제거 한다 

 
 
마지막으로 핸드 포즈 자식의 핸드의 메터리얼을 변경하고 이름을 GhostHandL로 변경 한다 

 
실행후 결과를 확인한다 

 
 
이제 손을 조금 구부려 본다 

 
위치가 변경되었다면 반드시 핸드 포즈의 Attach Point를 제거 하고 다시 만들어 주자 

 

 
 
그리고 Grabbable Cube를 선택해 Grab Interactable 의 Left hand Attach Transform에 할당해줘야 한다 

 
 
실행후 결과를 확인 한다 

 
오른손도 테스트 

 
 
 


오른손 미러 포즈 만들기 
MirrorHandPose, EditorMirrorHandPose스크립트를 생성하고 

 
작성한다 

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class MirrorHandPose : MonoBehaviour
{
    [SerializeField] private HandData handDataPoseFrom;
    [SerializeField] private HandData handDataPose;

    public void UpdateMirrorPose()
    {
        Vector3 mirroredPosition = handDataPoseFrom.root.localPosition;
        mirroredPosition.x *= -1;

        Quaternion mirroredQuaternion = handDataPoseFrom.root.localRotation;
        mirroredQuaternion.y *= -1;
        mirroredQuaternion.z *= -1;

        // Z축을 중심으로 180도 회전하는 쿼터니언 생성
        Quaternion rotate180 = Quaternion.Euler(0, 0, 180);

        // 반전된 회전에 추가 회전 적용
        mirroredQuaternion = rotate180 * mirroredQuaternion;

        handDataPose.root.localPosition = mirroredPosition;
        handDataPose.root.localRotation = mirroredQuaternion;

        for (int i = 0; i < this.handDataPoseFrom.bones.Length; i++)
        {
            this.handDataPose.bones[i].localRotation = handDataPoseFrom.bones[i].localRotation;
        }
    }
}
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(MirrorHandPose))]
public class EditorMirrorHandPose : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        MirrorHandPose script = (MirrorHandPose)target;

        if (GUILayout.Button("Update Mirror Pose"))
        {
            script.UpdateMirrorPose();
        }
    }
}

 
 
왼손 포즈를 복사 하고 

HandData를 넣어 주고 

 
 
 
 
핸드 포즈 L 에 Mirror Hand Pose를 부착한다 

 
 
From에 L을 To 에 R을 넣어주고 

 
Update Mirror Pose를 누른다 
그리고 각을 잘 잡아 주자 

 
 
 
 
 
 
 
 
 

 

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(MirrorHandPose))]
public class EditorMirrorHandPose : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        MirrorHandPose script = (MirrorHandPose)target;
        
        if (GUILayout.Button("Save Initial Pose"))
        {
            script.SaveInitialPose();
        }
        

        // "Update Mirror Pose" 버튼
        if (GUILayout.Button("Update Mirror Pose"))
        {
            script.UpdateMirrorPose();
        }

        // "Reset Pose" 버튼 추가
        if (GUILayout.Button("Reset Pose"))
        {
            script.ResetPose();
        }
        
        
    }
}

 

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class MirrorHandPose : MonoBehaviour
{
    [SerializeField] private HandData handDataPoseFrom;
    [SerializeField] private HandData handDataPose;
    // 초기 상태를 저장하기 위한 변수들
    [HideInInspector]
    public Vector3 initialRootPosition;
    [HideInInspector]
    public Quaternion initialRootRotation;
    [HideInInspector]
    public Quaternion[] initialBoneRotations;

    public void ResetPose()
    {
        // root의 초기 위치와 회전으로 재설정
        handDataPose.root.localPosition = initialRootPosition;
        handDataPose.root.localRotation = initialRootRotation;

        // 모든 bones의 초기 회전으로 재설정
        for (int i = 0; i < handDataPose.bones.Length; i++)
        {
            handDataPose.bones[i].localRotation = initialBoneRotations[i];
        }
    }

    

    public void UpdateMirrorPose()
    {
        Vector3 mirroredPosition = handDataPoseFrom.root.localPosition;
        mirroredPosition.x *= -1;

        Quaternion mirroredQuaternion = handDataPoseFrom.root.localRotation;
        mirroredQuaternion.y *= -1;
        mirroredQuaternion.z *= -1;

        // Z축을 중심으로 180도 회전하는 쿼터니언 생성
        Quaternion rotate180 = Quaternion.Euler(0, 0, 180);

        // 반전된 회전에 추가 회전 적용
        mirroredQuaternion = rotate180 * mirroredQuaternion;

        handDataPose.root.localPosition = mirroredPosition;
        handDataPose.root.localRotation = mirroredQuaternion;

        for (int i = 0; i < this.handDataPoseFrom.bones.Length; i++)
        {
            this.handDataPose.bones[i].localRotation = handDataPoseFrom.bones[i].localRotation;
        }
    }
    
    public void SaveInitialPose()
    {
        // handDataPose의 root와 bones에서 초기 상태 저장
        initialRootPosition = handDataPose.root.localPosition;
        initialRootRotation = handDataPose.root.localRotation;

        initialBoneRotations = new Quaternion[handDataPose.bones.Length];
        for (int i = 0; i < handDataPose.bones.Length; i++)
        {
            initialBoneRotations[i] = handDataPose.bones[i].localRotation;
        }
    }
    
    
}

 x 위치에 -1을 곱하는 이유는 3D 공간에서 객체의 거울 이미지를 생성하기 위해서입니다. 유니티에서 3D 객체의 위치는 `(x, y, z)` 형식의 `Vector3`로 표현됩니다. 여기서 x 축은 왼쪽과 오른쪽을, y 축은 위와 아래를, z 축은 앞과 뒤를 나타냅니다.

거울 이미지를 만들기 위해서는 한 축을 기준으로 객체를 '뒤집어야' 합니다. 이 코드에서는 x 축을 기준으로 뒤집습니다. 즉, 객체의 x 위치를 그대로 반대 방향으로 이동시키는 것입니다. 예를 들어, 객체가 원래 x 축에서 +3의 위치에 있었다면, -1을 곱하면 -3의 위치로 이동하게 되어, 거울상에서의 위치가 됩니다.

이러한 변환은 주로 대칭적인 장면이나 객체를 만들 때 사용됩니다. 예를 들어, 캐릭터의 한 손 동작을 다른 손에 정확히 반대로 적용하려 할 때 유용합니다.




회전에서 y축과 z축에 -1을 곱하는 것은 3D 공간에서의 객체의 거울 이미지 회전을 생성하기 위한 것입니다. 유니티에서 객체의 회전은 `Quaternion`으로 표현되며, 이는 복잡한 3D 회전을 나타내는 데 사용되는 수학적 구조입니다.

손 또는 다른 객체를 거울상으로 반전시키기 위해서는 단순히 위치를 반전시키는 것 이상의 조치가 필요합니다. 회전도 반전시켜야 합니다. 3D 공간에서의 회전은 x, y, z 축 주위로 정의됩니다. 거울상 회전을 만들기 위해선, 특정 축(이 경우 y와 z) 주위의 회전 방향을 반대로 해야 합니다.

- **y축 회전 반전:** 객체가 수직(y축)을 중심으로 시계 방향으로 회전했다면, 이를 반대로 반전시키려면 시계 반대 방향으로 같은 각도만큼 회전해야 합니다. 이를 수학적으로 나타내기 위해 y축 회전 성분에 -1을 곱합니다.

- **z축 회전 반전:** 마찬가지로, 객체가 수평(z축)을 중심으로 앞뒤로 회전했다면, 거울상에서는 그 회전이 반대 방향으로 보여야 합니다. z축 회전 성분에 -1을 곱함으로써 이를 달성할 수 있습니다.

이러한 변환을 통해, 객체의 3D 회전이 거울상에서 보는 것처럼 정확하게 반전됩니다. 예를 들어, VR/AR 게임에서 사용자의 한 손 동작을 다른 손에 정확히 반대로 적용하려 할 때 이 방법이 유용합니다.




코드에서 180도 회전을 적용하는 이유는 3D 공간에서의 객체의 거울 이미지를 보다 정확하게 생성하기 위해서입니다. 특히, 이 경우에는 손의 위치와 방향을 거울 이미지처럼 만들기 위해 사용됩니다.

위에서 언급한 것처럼, y축과 z축의 회전 성분에 -1을 곱하는 것만으로는 완벽한 거울 이미지를 생성할 수 없습니다. 이는 객체가 거울을 통해 본 것처럼 완전히 반대 방향을 보도록 하지 않기 때문입니다. 여기서 필요한 것이 바로 Z축을 중심으로 180도 회전하는 추가적인 변환입니다.

Z축 주위로 180도 회전(`Quaternion.Euler(0, 0, 180)`)을 적용하면, 객체가 수평 평면을 기준으로 정확히 반대 방향을 바라보게 됩니다. 이는 객체가 거울에 반사된 것처럼 보이게 하기 위해 필요한 마지막 조정입니다.

결론적으로, y축과 z축 회전 성분에 -1을 곱하고, Z축 주위로 180도 추가 회전을 적용함으로써, 객체는 거울상에서 보는 것처럼 완전히 반대 방향을 바라보게 되며, 이는 거울 이미지 회전을 정확하게 재현하는 데 중요합니다.


 
 public class MirrorPoseUtil
{
    // 3D 객체를 거울 이미지로 변환하는 메서드
    public static void UpdateMirrorPose(Transform objectTransform)
    {
        // 위치를 거울 이미지로 반전
        objectTransform.localPosition = MirrorPosition(objectTransform.localPosition);

        // 회전을 거울 이미지로 반전
        objectTransform.localRotation = MirrorRotation(objectTransform.localRotation);
    }

    // 위치를 거울 이미지로 반전하는 메서드
    private static Vector3 MirrorPosition(Vector3 originalPosition)
    {
        originalPosition.x *= -1;
        return originalPosition;
    }

    // 회전을 거울 이미지로 반전하는 메서드
    private static Quaternion MirrorRotation(Quaternion originalRotation)
    {
        originalRotation.y *= -1;
        originalRotation.z *= -1;

        // Z축을 중심으로 180도 회전 적용
        Quaternion rotate180 = Quaternion.Euler(0, 0, 180);
        return rotate180 * originalRotation;
    }
}


 
 


 
HandGrabInteractor 

 
 
 
HandGrabInteractable

 
 
 
HandGrabPose

 
 
Synthetic Hand 

 
 
 
 
 


 
테스트 코드들 

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(MirrorHandPose))]
public class EditorMirrorHandPose : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        MirrorHandPose script = (MirrorHandPose)target;

        if (GUILayout.Button("Update Mirror Pose"))
        {
            script.UpdateMirrorPose();
        }
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class XRHandedGrabInteractable : XRGrabInteractable
{
    [SerializeField]
    private Transform LeftHandAttachTransform;
    [SerializeField]
    private Transform RightHandAttachTransform;
    [SerializeField]
    XRDirectInteractor LeftController;
    [SerializeField]
    XRDirectInteractor RightController;
    
    protected override void OnSelectEntering(SelectEnterEventArgs args)
    {
        if (args.interactorObject == LeftController)
        {
            Debug.Log($"Left hand");
            attachTransform.SetPositionAndRotation(LeftHandAttachTransform.position, LeftHandAttachTransform.rotation);
        }
        else if (args.interactorObject == RightController)
        {
            Debug.Log($"Right hand");
            attachTransform.SetPositionAndRotation(RightHandAttachTransform.position, RightHandAttachTransform.rotation);
        }
        base.OnSelectEntering(args);
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;


public class MirrorHandPose : MonoBehaviour
{
    [SerializeField] private HandData handDataPoseFrom;
    [SerializeField] private HandData handDataPose;

    public void UpdateMirrorPose()
    {
        Vector3 mirrorredPosition = handDataPoseFrom.root.localPosition;
        Quaternion mirroredQuaternion = handDataPoseFrom.root.localRotation;
        handDataPose.root.localPosition = mirrorredPosition;
        handDataPose.root.localRotation = mirroredQuaternion * Quaternion.Euler(new Vector3(0, 0, 180));

        for (int i = 0; i < this.handDataPoseFrom.bones.Length; i++)
        {
            this.handDataPose.bones[i].localRotation = handDataPoseFrom.bones[i].localRotation;
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;

public class Hand : MonoBehaviour
{
    [SerializeField] private InputDeviceCharacteristics inputDeviceCharacteristics;
    [SerializeField] private Animator anim;
    private InputDevice targetDevice;
    void Start()
    {
        this.StartCoroutine(this.WaitForGetDevices());
    }

    private IEnumerator WaitForGetDevices()
    {
        WaitForEndOfFrame wait = new WaitForEndOfFrame();
        List<InputDevice> devices = new List<InputDevice>();
        
        while (devices.Count == 0)
        {
            yield return wait;
            InputDevices.GetDevicesWithCharacteristics(inputDeviceCharacteristics, devices);
        }
        
        this.targetDevice = devices[0];
    }

    void Update()
    {
        if (this.targetDevice.isValid)
        {
            UpdateHand();
        }
    }

    private void UpdateHand()
    {
        if (this.targetDevice.TryGetFeatureValue(CommonUsages.grip, out float gripValue))
        {
            this.anim.SetFloat("Grip", gripValue);
        }
        
        if (this.targetDevice.TryGetFeatureValue(CommonUsages.trigger, out float triggerValue))
        {
            this.anim.SetFloat("Trigger", triggerValue);
        }
        
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandData : MonoBehaviour
{
    public enum  HandType
    {
        Left, Right
    }

    public HandType handType;
    public Transform[] bones;
    public Transform root;
    public Animator anim;
    
}
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class HandPose : MonoBehaviour
{
    [SerializeField]
    private XRGrabInteractable grabInteractable;
    [SerializeField] private HandData handDataPose;
    
    void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {

            var directInteractor = arg.interactorObject as DirectInteractor;

            if (directInteractor.handType == this.handDataPose.handType)
            {
                
                    

                Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
                var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
                Debug.LogFormat("handData : {0}", handData);
            
                handData.anim.enabled = false;
            
                //handData.root.localRotation = handDataPose.root.localRotation * Quaternion.Euler(new Vector3(-90, 0, 90));
            
                handData.root.parent.localRotation = handDataPose.root.localRotation;
            
                for (int i = 0; i < handDataPose.bones.Length; i++)
                {
                    handData.bones[i].localRotation = handDataPose.bones[i].localRotation;
                }
            
                //handDataPose.gameObject.SetActive(false);
            }
        });

        
        grabInteractable.selectExited.AddListener((arg) =>
        {
            var directInteractor = arg.interactorObject as DirectInteractor;
            if (directInteractor.handType == this.handDataPose.handType)
            {
                Debug.LogFormat("Select Exited : {0}", arg.interactorObject);
            
                var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
                Debug.LogFormat("handData : {0}", handData);

                handData.anim.enabled = true;

                //초기화 
                handData.root.localPosition = Vector3.zero;
                
                if(this.handDataPose.handType == HandData.HandType.Left)
                    handData.root.parent.localRotation = Quaternion.Euler(new Vector3(90, 0, -90)); 
                if(this.handDataPose.handType == HandData.HandType.Right)
                    handData.root.parent.localRotation = Quaternion.Euler(new Vector3(-90, 0, -90)); 

                //handDataPose.gameObject.SetActive(true);
            }

            
        });
    }

    
}

 
https://youtu.be/JdspLj4fZlI?list=RDCMUC-BligqNSwG0krJDfaPhytw
https://youtu.be/TW3eAJqWCDU

반응형
: