VR/XR Interaction Toolkit

XR Interaction Toolkit 2.4.3 (Custom Hand Pose) OpenXR

일등하이 2023. 12. 29. 17:31
반응형

 
https://assetstore.unity.com/packages/3d/props/weapons/long-sword-212082

Long Sword | 3D 무기 | Unity Asset Store

Elevate your workflow with the Long Sword asset from VarcanRex. Find this & other 무기 options on the Unity Asset Store.

assetstore.unity.com

 
위 리소스를 다운받아 설치 하고 
 
새 씬을 만들어 줍니다.

 
 
메인 카메라를 제거 하고 XR Origin을 생성 합니다.

 
Tracking Origin Mode를 Floor로 설정 하고 XR Origin (XR Rig) 와 Camera Offset의 위치를 0으로 초기화 합니다. 

 
 
Left / Right Controller를 선택하고 

 
XR Controller 컴포넌트를 제외한 나머지 컴포넌트들을 제거 합니다.

 
 
다음 구조로 만들어 주고 

 
 
Left Controller를 선택하고 

 
Model Prefab과 Model Parent를 넣어 줍니다.

 
 
오른쪽도 동일하게 진행 합니다.
Left / Right Controller의 Hand Visual / Offset을 선택해 알맞게 회전 합니다.
 
 

왼손 Offset

 

오른손 Offset

 

테스트

 
 
 
인터렉터 설정 
프로젝트 창에서 Direct Interactor 프리팹을 찾아 Left Controller에 넣어줍니다.

 
오른쪽도 동일하게 진행 합니다.

 
Direct Interactor를 선택해 배터리 절약을 위해 햅틱 이벤트를 꺼줍니다.


 
롱소드를 씬으로 가져오고 

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

 
Interactable LongSword 오브젝트를 선택하고 Rigidbody와 XR Grab Interactable 컴포넌트를 부착 합니다.

 
LongSwordMesh에 이미 콜라이더가 있습니다.
 

 
그랩이 되는지 확인 합니다.


 
Interactable LongSword 오브젝트를 선택하고 

 
Use Dynamic Attach를 체크 합니다.

 
잡은 위치와 회전이 잘 맞는지 확인 합니다.

더보기

 

  1. Use Dynamic Attach:
    • 이 속성은 XR Grab Interactable 컴포넌트의 중요한 속성 중 하나입니다. 이것은 해당 상호작용 대상이 그랩될 때 동적으로 그랩 포인트(Grab Point)를 생성하고 이동시키는 데 사용됩니다.
    • Use Dynamic Attach를 활성화하면 XR Grab Interactable가 그랩될 때 그랩 포인트가 자동으로 생성되고 설정된 조건에 따라 업데이트됩니다. 이로써 사용자의 손 또는 컨트롤러와 상호작용하는 물체에 더 자연스러운 물리적 연결을 만들 수 있습니다.
    • 예를 들어, Use Dynamic Attach를 사용하면 물체가 그랩될 때 손 또는 컨트롤러와 물체 사이의 상대 위치와 회전이 고정되지 않고 동적으로 조정됩니다.
  1. Match Position (일치하는 위치):
    • "Match Position" 프로퍼티는 XR Grab Interactable 컴포넌트의 일부로 설정할 수 있는 옵션 중 하나입니다.
    • 이 프로퍼티를 활성화하면 물체가 그랩될 때 그랩 포인트(Grab Point)가 사용자의 손 또는 컨트롤러 위치와 일치하도록 설정됩니다. 즉, 그랩 포인트가 그랩될 때 손의 위치에 따라 동적으로 조정됩니다.
    • 일반적으로 "Match Position"을 사용하면 물체가 그랩될 때 물체의 위치가 사용자의 손 또는 컨트롤러의 위치와 일치하여 더 자연스러운 상호작용을 가능하게 합니다.
  2. Match Rotation (일치하는 회전):
    • "Match Rotation" 프로퍼티는 XR Grab Interactable 컴포넌트의 또 다른 중요한 설정입니다.
    • 이 프로퍼티를 활성화하면 물체가 그랩될 때 그랩 포인트의 회전이 사용자의 손 또는 컨트롤러의 회전과 일치하도록 설정됩니다. 즉, 그랩 포인트의 회전이 동적으로 조정됩니다.
    • "Match Rotation"을 사용하면 물체가 그랩될 때 회전이 맞추어져 물체가 사용자의 손 또는 컨트롤러와 일치하여 물체를 더 쉽게 조작할 수 있습니다.
  3. Snap To Collider Volume:
    • 이 옵션은 XR Grab Interactable가 그랩될 때 물체를 자동으로 해당 콜라이더 볼륨에 맞게 조정하는 데 사용됩니다.
    • Snap To Collider Volume을 활성화하면 XR Grab Interactable가 그랩될 때 물체가 그랩 포인트에 따라 자동으로 해당 콜라이더의 경계에 맞추어집니다. 이것은 물체가 손이나 컨트롤러와 자연스럽게 들어맞는 것을 도와줍니다.
    • 이 옵션을 사용하면 사용자의 입체감과 상호작용 경험이 향상됩니다.
  4. Reinitialize Every Single Grab:
    • 이 옵션은 XR Grab Interactable가 여러 번 그랩될 때마다 초기화를 다시 수행할지 여부를 결정합니다.
    • Reinitialize Every Single Grab를 활성화하면 물체가 그랩될 때마다 XR Grab Interactable가 재설정되며 모든 상태와 속성이 초기 상태로 돌아갑니다. 이것은 그랩 해제 및 다시 그랩할 때마다 일종의 리셋을 수행하는 데 사용됩니다.
    • 이 옵션을 비활성화하면 물체가 그랩될 때마다 초기화가 수행되지 않으며, 그랩 상태가 유지됩니다.

 
Interactable LongSword를 선택하고 빈오브젝트 (Hand Pose)를 생성 합니다.
 

 
Hand Pose 자식으로 Left Controller의 Model Prefab과 동일한 프리팹을 넣어줍니다.
이것은 핸드 포즈를 잡을 손 입니다.

 
Hand Data 스크립트를 만들고 다음과 같이 작성 합니다.

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

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

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

}

 
Hand Data를 Hand Pose / OculusHand_L 에 부착 하고 각 프로퍼티에 아래와 같이 넣어 줍니다. 

 
 
OculusHand_L 오브젝트를 선택하고 

 
Animator와 Hand 스크립트를 제거 합니다.

 
HandPose 스크립트를 생성하고 다음과 같이 작성 합니다.

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

public class HandPose : MonoBehaviour
{
    public HandData.HandType handType;
    public HandData handDataRef;
    public XRGrabInteractable grabInteractable;
    
    private Vector3 startingHandPosition;
    private Vector3 finalHandPosition;
    private Quaternion startingHandRotation;
    private Quaternion finalHandRotation;
    private Quaternion[] startingFingerRotaions;
    private Quaternion[] finalFingerRotations;

    void Start()
    {
        grabInteractable.selectEntered.AddListener(this.SetupPose);
        grabInteractable.selectExited.AddListener(this.UnSetPose);
    }

    private void UnSetPose(SelectExitEventArgs arg0)
    {
        if (arg0.interactorObject is XRDirectInteractor)
        {
            if (this.handDataRef.handType == this.handType && arg0.interactorObject.transform.name.Contains(this.handType.ToString()))
            {
                Debug.LogFormat("UnSetPose: <color=yellow>{0}</color>", arg0.interactorObject.transform.name);
                HandData handData = arg0.interactorObject.transform.GetComponentInChildren<HandData>();
                handData.anim.enabled = true;
                //handData.gameObject.SetActive(false);

                SetHandDataValues(handData, handDataRef);
                SetHandData(handData, startingHandPosition, startingHandRotation, startingFingerRotaions);
            
                handData.gameObject.transform.localPosition = Vector3.zero;
                handData.gameObject.transform.localRotation = Quaternion.identity;
            }
        }
    }

    private void SetupPose(SelectEnterEventArgs arg0)
    {
        if (arg0.interactorObject is XRDirectInteractor)
        {
            if (this.handDataRef.handType == this.handType && arg0.interactorObject.transform.name.Contains(this.handType.ToString()))
            {
                Debug.LogFormat("SetupPose: <color=yellow>{0}</color>", arg0.interactorObject.transform.name);
                HandData handData = arg0.interactorObject.transform.GetComponentInChildren<HandData>();
                handData.anim.enabled = false;
            

                //handData.gameObject.SetActive(false);

                SetHandDataValues(handData, handDataRef);
                SetHandData(handData, finalHandPosition, finalHandRotation, finalFingerRotations);
            
                handData.gameObject.transform.localPosition = Vector3.zero;
                handData.gameObject.transform.localRotation = Quaternion.identity;
            }
        }
    }

    public void SetHandDataValues(HandData h1, HandData h2)
    {
        // startingHandPosition = h1.root.localPosition;
        // finalHandPosition = h2.root.localPosition;

        startingHandPosition = new Vector3(h1.root.localPosition.x / h1.root.localScale.x,
            h1.root.localPosition.y / h1.root.localScale.y, h1.root.localPosition.z / h1.root.localScale.z);
        finalHandPosition = new Vector3(h2.root.localPosition.x / h2.root.localScale.x,
            h2.root.localPosition.y / h2.root.localScale.y, h2.root.localPosition.z / h2.root.localScale.z);

        startingHandRotation = h1.root.localRotation;
        finalHandRotation = h2.root.rotation;

        startingFingerRotaions = new Quaternion[h1.bones.Length];
        finalFingerRotations = new Quaternion[h2.bones.Length];

        for (int i = 0; i < h1.bones.Length; i++)
        {
            startingFingerRotaions[i] = h1.bones[i].localRotation;
            finalFingerRotations[i] = h2.bones[i].localRotation;
        }
    }

    public void SetHandData(HandData handData, Vector3 newPosition, Quaternion newRotation,
        Quaternion[] newBonesRotation)
    {
        handData.root.localPosition = newPosition;
        handData.root.localRotation = newRotation;
        for (int i = 0; i < newBonesRotation.Length; i++)
        {
            handData.bones[i].localRotation = newBonesRotation[i];
        }
    }
}

 
Hand Pose를 선택하고 

손의 모양을 잡아주고 

 

HandPose 스크립트를 부착 합니다.

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.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.XR.Interaction.Toolkit;

public class HandPose : MonoBehaviour
{
    public HandData.HandType handType;
    public HandData handDataRef;
    public XRGrabInteractable grabInteractable;

    public Transform wrist;
    public Transform attachPoint;
    
    [SerializeField] private HandData leftHandData;
    [SerializeField] private HandData rightHandData;
    
    [SerializeField]
    XRDirectInteractor LeftController;
    [SerializeField]
    XRDirectInteractor RightController;
    
    private Vector3 startingHandPosition;
    private Vector3 finalHandPosition;
    private Quaternion startingHandRotation;
    private Quaternion finalHandRotation;
    private Quaternion[] startingFingerRotaions;
    private Quaternion[] finalFingerRotations;

    void Start()
    {

        // this.attachPoint.localPosition = this.transform.InverseTransformPoint(this.wrist.position);
        // this.attachPoint.localRotation = this.wrist.localRotation;
        
        
        grabInteractable.selectEntered.AddListener(this.SetupPose);
        grabInteractable.selectExited.AddListener(this.UnSetPose);
    }

    private void UnSetPose(SelectExitEventArgs arg0)
    {
        
        if (arg0.interactorObject == LeftController && this.handType == HandData.HandType.Left)
        {
            Debug.Log($"<color=yellow>Left hand</color>");
            leftHandData.anim.enabled = true;
            SetHandDataValues(leftHandData, handDataRef);
            SetHandData(leftHandData, startingHandPosition, startingHandRotation, startingFingerRotaions);
            
            leftHandData.gameObject.transform.localPosition = Vector3.zero;
            leftHandData.gameObject.transform.localRotation = Quaternion.identity;
        }
        else if (arg0.interactorObject == RightController && this.handType == HandData.HandType.Right)
        {
            Debug.Log($"<color=yellow>Right hand</color>");
            rightHandData.anim.enabled = true;
            SetHandDataValues(rightHandData, handDataRef);
            SetHandData(rightHandData, startingHandPosition, startingHandRotation, startingFingerRotaions);
            
            rightHandData.gameObject.transform.localPosition = Vector3.zero;
            rightHandData.gameObject.transform.localRotation = Quaternion.identity;
        }
    }

    private void SetupPose(SelectEnterEventArgs arg0)
    {
        System.Action<HandData> setupPose = (handData) =>
        {
            handData.anim.enabled = false;
            //handData.gameObject.SetActive(false);
            SetHandDataValues(handData, handDataRef);
            SetHandData(handData, finalHandPosition, finalHandRotation, finalFingerRotations);
            handData.gameObject.transform.localPosition = Vector3.zero;
            handData.gameObject.transform.localRotation = Quaternion.identity;   
        };
        if (arg0.interactorObject == LeftController && this.handType == HandData.HandType.Left)
        {
            Debug.Log($"<color=yellow>Left hand</color>");
            setupPose(leftHandData);
        }
        else if (arg0.interactorObject == RightController && this.handType == HandData.HandType.Right)
        {
            Debug.Log($"<color=yellow>Right hand</color>");
            setupPose(rightHandData);
        }
    }

    public void SetHandDataValues(HandData h1, HandData h2)
    {
        // startingHandPosition = h1.root.localPosition;
        // finalHandPosition = h2.root.localPosition;

        startingHandPosition = new Vector3(h1.root.localPosition.x / h1.root.localScale.x,
            h1.root.localPosition.y / h1.root.localScale.y, h1.root.localPosition.z / h1.root.localScale.z);
        finalHandPosition = new Vector3(h2.root.localPosition.x / h2.root.localScale.x,
            h2.root.localPosition.y / h2.root.localScale.y, h2.root.localPosition.z / h2.root.localScale.z);

        startingHandRotation = h1.root.localRotation;
        finalHandRotation = h2.root.rotation;

        startingFingerRotaions = new Quaternion[h1.bones.Length];
        finalFingerRotations = new Quaternion[h2.bones.Length];

        for (int i = 0; i < h1.bones.Length; i++)
        {
            startingFingerRotaions[i] = h1.bones[i].localRotation;
            finalFingerRotations[i] = h2.bones[i].localRotation;
        }
    }

    public void SetHandData(HandData handData, Vector3 newPosition, Quaternion newRotation,
        Quaternion[] newBonesRotation)
    {
        handData.root.localPosition = newPosition;
        handData.root.localRotation = newRotation;
        for (int i = 0; i < newBonesRotation.Length; i++)
        {
            handData.bones[i].localRotation = newBonesRotation[i];
        }
    }
}

 
 

반응형