XR Interaction Toolkit 2.4.3 (Hand Pose)

VR/XR Interaction Toolkit 2023. 12. 30. 23:05
반응형

https://youtu.be/kq9AucaBeOw?list=PLTFRwWXfOIYBIPKhWi-ZO_ITXqtNuqj6j

 

 

 

 

 
 


 
새 씬을 만들어 줍니다.
 
메인 카메라를 제거 하고 XR Origin을 생성한뒤 
Left/Right Controller를 선택해 XR Controller를 제외한 나머지 컴포넌트들을 제거 합니다.

 
실행해서 컨트롤러가 잘 나오는지 확인 합니다.
 

 


 
 
 
Left / Right Controller를 선택하고 다음 구조로 만들어 준뒤 핸드 프리팹을 Offset 자식으로 넣어 줍니다.

 
왼손 Offset 회전을 90, 0 -90으로 설정 합니다.

 
 
오른손 Offset회전을 -90, 0, -90으로 설정 합니다.

 
 
 
 
프로젝트 창에서 Direct Interactor를 검색해서 Left Controller, Right Controller에 각각 넣어 줍니다.

 
 
Sphere콜라이더의 Radius의 값을 0.1로 설정 합니다.

 
위치를 손바닥의 중앙으로 설정 합니다.

 


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

 
다음과 같은 구조로 만들어 줍니다.

 
Grabbable Cube를 선택하고 적당한 위치로 설정 합니다.

 
Rigidbody와 XR Grab Interactable컴포넌트를 부착 합니다.
그리고 Throw on Detach를 비활성화 합니다 

 
 
Left/Right Controller를 선택해 XR controller의 model prefab을 none으로 설정합니다.

 
실행후 결과를 확인 합니다.


 
Grabbable Cube를 선택하고 Hand Pose 빈오브젝트를 생성 합니다.

 
Left/Right Controller에 붙어 있는 핸드와 동일한 핸드 프리팹을 넣어 줍니다.

 
 
 
적당히 포즈를 잡아 줍니다.,

 
 
Hand Pose 아래 핸드 프리팹을 선택한뒤 Animator와 Hand컴포넌트를 제거 합니다.
 

 
 
 
 
 
 
 
핸드 포즈 오브젝트 자식으로 Sphere를 만들고 빨강 메터리얼을 적용하고 콜라이더는 제거 합니다.

 
그리고 이름을 Attach Point로 변경 합니다.

 
이제 Attach Point를 드레그 해서 Hand Pose자식으로 넣습니다.

회전값을 초기화 합니다.

 
 
Grabbable Cube를 선택하고

 
XR Grab Interactable 컴포넌트의 Attach Transform에 Attach Point를 넣어 줍니다.

 
 


 
 
Left Right Controller의 Direct Interactor 를 선택해 위치를 초기화 합니다.
 
Direct Interactor를 선택하고 Sphere를 생성하고 메터리얼을 빨강색, 콜라이더는 제거 합니다.

 
 
실행해보기 
 


 
 
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;
    
}

 
핸드 포즈 자식으로 있는 핸드를 선택해 HandData를 부착 합니다.

 
인스펙터를 잠그고 

 
다음과 같이 선택해서 Bones에 넣어 줍니다.
 

 
 
이제 인스펙터 잠금을 해제 합니다

 
 


 
 
HandPose 스크립트를 생성하고 

다음과 같이 작성 합니다.

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

public class HandPose : MonoBehaviour
{
    [SerializeField]
    private XRGrabInteractable grabInteractable;
    
    void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {
            Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
        });
        
        grabInteractable.selectExited.AddListener((arg) =>
        {
            Debug.LogFormat("Select Exited : {0}", arg.interactorObject);
        });
    }

    
}

 


 
 
Left Controller의 핸드를 선택 하고 HandData를 부착 합니다.
그리고 프로퍼티를 채워줍니다.

 
다음과 같이 코드를 수정 합니다.

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

public class HandPose : MonoBehaviour
{
    [SerializeField]
    private XRGrabInteractable grabInteractable;
    
    void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {
            Debug.LogFormat("Select Entered : {0}", arg.interactorObject);

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

    
}

 
 
 
코드를 다음과 같이 수정하고 실행 합니다.
 

using System.Collections;
using System.Collections.Generic;
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) =>
        {
            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;
            
            for (int i = 0; i < handDataPose.bones.Length; i++)
            {
                handData.bones[i].localRotation = handDataPose.bones[i].localRotation;
            }
            
        });

        
        grabInteractable.selectExited.AddListener((arg) =>
        {
            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;
            handData.root.localRotation = Quaternion.Euler(new Vector3(90, 0, -90));
        });
    }

    
}

 

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(OnSelectEntered);
        grabInteractable.selectExited.AddListener(OnSelectExited);
    }

    private void OnSelectEntered(SelectEnterEventArgs arg)
    {
        Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
        var handData = GetHandData(arg);
        
        ApplyHandPose(handData);
    }

    private void OnSelectExited(SelectExitEventArgs arg)
    {
        Debug.LogFormat("Select Exited : {0}", arg.interactorObject);
        var handData = GetHandData(arg);
        
        ResetHandPose(handData);
    }

    private HandData GetHandData(BaseInteractionEventArgs arg)
    {
        var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
        Debug.LogFormat("handData : {0}", handData);
        return handData;
    }

    private void ApplyHandPose(HandData handData)
    {
        handData.anim.enabled = false;
        
        Vector3 invertedEulerAngles = InvertEulerAngles(handData.root.parent.localRotation.eulerAngles);
        Debug.LogFormat("{0} {1} {2}", invertedEulerAngles.x, invertedEulerAngles.y, invertedEulerAngles.z);
        handData.root.localRotation = handDataPose.root.localRotation * Quaternion.Euler(invertedEulerAngles);
        
        for (int i = 0; i < handDataPose.bones.Length; i++)
        {
            handData.bones[i].localRotation = handDataPose.bones[i].localRotation;
        }
    }

    private void ResetHandPose(HandData handData)
    {
        handData.anim.enabled = true;
        handData.root.localPosition = Vector3.zero;
        handData.root.localRotation = quaternion.identity;
        handDataPose.gameObject.SetActive(true);
    }

    private Vector3 InvertEulerAngles(Vector3 eulerAngles)
    {
        return new Vector3(-eulerAngles.x, eulerAngles.y, -eulerAngles.z);
    }
}

 
 
 
 
 
 
 
 
 


 
 

 

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) =>
        {
            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.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) =>
        {
            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;
            handData.root.localRotation = Quaternion.Euler(new Vector3(90, 0, -90)); //quaternion.identity;

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

    
}

 
 
 


Mirror 포즈 만들기 
 
MirrorHandPose스크립트를 만들고 

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 UpdateHandPose()
    {
        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;
        }
    }
}

 
왼손 Hand Pose를 복사 한후 모델을 변경 해준다 (OculusHand_R)로 

 
 
오른손 모델을 선택 하고 Hand Data를 부착 후 프로퍼티를 설정한다 

 
 
 
 
Editor Mirror Hand 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("Update Mirror Pose"))
        {
            script.UpdateMirrorPose();
        }
    }
}

 
Mirror Hand Pose 스크립트를 부착 한다 

 
 
인스펙터에서 Update mirror pose 버튼을 누른다 

 
 
왼손의 포즈가 오른손으로 적용된 모습이다 

 
 


 
 
왼손으로 잡았는지 오른손으로 잡았는지 알아내기 
 
문제는 이제 왼손 오른손에 HandPose가 달려 있기 때문에 2번씩 호출 된다 

 
 
Direct Interactor 오브젝트를 선택하고 XR Direct Interactor를 제거 한다 

 
DirectInteractor 스크립트를 만들고 다음과 같이 작성후 다시 붙여 주자 

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

public class DirectInteractor : XRDirectInteractor
{
    public HandData.HandType handType;
    
    protected override void OnSelectEntered(SelectEnterEventArgs args)
    {
        base.OnSelectEntered(args);
        
        Debug.LogFormat("OnSelectEntered : <color=yellow>{0}</color>", this.handType);
    }
}

 
 
 
이제 HandPose스크립트를 수정 한다 

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);
            }

            
        });
    }

    
}

 
 
 
 
 
 
 
 
 
 
양손모두 동작 하는지 확인 하자 


 

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);
    }
}

 

반응형
: