'전체 글'에 해당되는 글 1801건

  1. 2024.01.02 [OpenXR] When using Two Hand Grab in XR Interaction Toolkit 2.4.3
  2. 2024.01.01 [OpenXR] Custom Hand Pose (Mirror Pose) with XR Interaction Toolkit 2.4.3
  3. 2023.12.30 XR Interaction Toolkit 2.4.3 (Hand Pose)
  4. 2023.12.30 XR Interaction Toolkit 2.4.3 (Grab Interactable)
  5. 2023.12.29 XR Interaction Toolkit 2.4.3 (Custom Hand Pose) OpenXR
  6. 2023.12.28 [OpenXR] Ray Interactor (XR Interaction Toolkit Movement 타입 차이점)

[OpenXR] When using Two Hand Grab in XR Interaction Toolkit 2.4.3

VR/XR Interaction Toolkit 2024. 1. 2. 13:31
반응형

https://forum.unity.com/threads/when-using-two-hand-grab-in-xr-interaction-toolkit-2-4-3-the-position-of-the-second-grabbed-hand-is.1532110/#post-9557029

When using Two Hand Grab in XR Interaction Toolkit 2.4.3, the position of the second grabbed hand is

[ATTACH] When using Two Hand Grab in XR Interaction Toolkit 2.4.3, the position of the second grabbed hand is not fixed. How do I fix this? [ATTACH]

forum.unity.com

 
 
예제에서도 저럼 
이것이 그냥 기본인가보네 -_-;;
 

 
 
 
핸드 비주얼을 꺼버리고 고스트 핸드를 보여주면된다
 

 

반응형
:

[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

반응형
:

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

 

반응형
:

XR Interaction Toolkit 2.4.3 (Grab Interactable)

VR/XR Interaction Toolkit 2023. 12. 30. 15:34
반응형

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

 
Left/Right Controller를 선택후 XR Controller를 제외한 모든 컴포넌트를 제거 한다 

 
다음과 같이 구조를 만들고 

 
핸드 모델을 Offset자식으로 넣어주고 

 
왼손 Offset 설정 

 
오른손 Offset설정 

 

 
 

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

 
Grabbble 큐브 만들기 

 
Grabbable Cube를 선택하고 rigidbody, xr grab interactable컴포넌트를 부착 한다 

 


인터렉터 설정 
 
프로젝트 창에서 Direct Interactor를 찾아 Left/Right Controller에 자식으로 넣어준다 


실행후 결과를 확인 한다 
 
 

 
물체를 잡았다가 놓으면 고정되지 않고 떠다니는 현상이 발생하는 것은 Throw On Detach가 활성화 상태이기 때문이다 

 
이 현상을 막기 위해서는 Throw On Detach를 비활성화 하거나 Rigidbody의 Is Kinematic을 체크 한다 
 

 
 
 


 
 
실행하면 자동으로 생성 또는 할당되는 컴포넌트 확인하기 
 
Left Controller의 Model 프로퍼티 

실행전

 

실행후

 


 
 
Grabbable Cube의 XR Grab Interactable 컴포넌트 

실행전

 
 

실행후

 
 
 
XR Grab Interactable컴포넌트가 부착된 Grabbable Cube에 

XR General Grab Transformer가 자동으로 부착 된다 

 
 
 


 
기본 Attach Point 확인 하기 
박스의 중앙과 각 손의 손목에 색있는 구를 만들고 확인 해본다 
정확히 손목으로 들어오는것을 확인 할수 있음 
 


 
이제 문서를 확인 해보자 

XR Grab Interactable

 
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/xr-grab-interactable.html

XR Grab Interactable | XR Interaction Toolkit | 2.4.3

XR Grab Interactable Interactable component that allows for basic grab functionality. When this behavior is selected (grabbed) by an Interactor, this behavior will follow it around and inherit velocity when released. Property Description Interaction Manage

docs.unity3d.com

기본 잡기 기능을 허용하는 상호 작용 가능한 구성 요소입니다. 이 동작이 인터랙터에 의해 선택(잡아)지면 이 동작은 이를 따라가며 놓을 때 속도를 상속합니다.
 


 

Interaction ManagerThe XRInteractionManager that this Interactable will communicate with (will find one if None).

 
없으면 찾아서 넣는다 
 
Interaction Manager는 Interactor와 Interactable 사이의 중개자 역할을 합니다. 
각각 고유한 유효한 인터랙터 및 인터랙터블 세트를 포함하는 여러 인터랙션 관리자를 가질 수 있습니다. 
활성화되면 Interactor와 Interactable 모두 유효한 Interaction Manager에 등록됩니다(특정 관리자가 검사기에 아직 할당되지 않은 경우). 
로드된 장면에는 Interactor 및 Interactable이 통신할 수 있도록 Interaction Manager가 하나 이상 있어야 합니다.
 Interactors 및 Interactables의 많은 메서드는 상호 작용 이벤트의 두 대상 간의 일관성을 유지하기 위해 직접 호출되지 않고 이 Interaction Manager에 의해 호출되도록 설계되었습니다.
 


Interaction Layer MaskAllows interaction with Interactors whose Interaction Layer Mask overlaps with any Layer in this Interaction Layer Mask.

 
상호 작용 레이어 마스크가 이 상호 작용 레이어 마스크의 레이어와 겹치는 인터랙터와의 상호 작용을 허용합니다.
 
큐브를 하나 복사 하고

.

 
복사된 큐브를 선택하고 

 
인터렉션 레이어를 추가 하고 

 
인터렉션 레이어 마스크를 변경한다 

 
 
 
이제 왼손 컨트롤러의 Direct Interactor를 선택후 

 
인터렉션 레이어 마스크를 설정 한다 

 
 


 
이 Interactable과의 상호 작용에 사용할 충돌체(비어 있는 경우 하위 충돌체를 사용함)

CollidersColliders to use for interaction with this Interactable (if empty, will use any child Colliders).

 
 
다음과 같이 콜라이더를 하나 더 만들고 실행해 본다 

 
자동으로 하위 콜라이더들이 할당 되었다 

 
 

 


 
가장 빠른 것부터 가장 정확한 것까지 인터랙터까지의 거리가 계산되는 방식을 지정합니다.
Mesh Colliders를 사용하는 경우 Collider Volume은 메쉬가 볼록한 경우에만 작동합니다.

Distance Calculation ModeSpecifies how distance is calculated to Interactors, from fastest to most accurate. If using Mesh Colliders, Collider Volume only works if the mesh is convex.
더보기

Unity의 XR Interaction Toolkit에서 XR Grab Interactable 컴포넌트의 Distance Calculation Mode 속성은 인터랙터와 상호작용 가능한 객체 간의 거리를 계산하는 방법을 제공합니다. 이 옵션들을 이해하는 것은 XR 애플리케이션의 성능과 사용자 경험을 최적화하는 데 중요합니다. 각 모드에 대한 자세한 설명은 다음과 같습니다:

  1. Transform Position (변환 위치):
    • 설명: 이 모드는 상호작용 가능한 객체의 변환 위치를 사용하여 거리를 계산합니다.
    • 성능 비용: 낮음.
    • 거리 계산 정확도: 일부 객체에 대해 정확도가 낮을 수 있습니다. 이는 객체의 중심점을 기준으로 거리를 계산하기 때문에, 크기가 크거나 복잡한 형태의 객체에서는 정확도가 떨어질 수 있습니다.
  2. Collider Position (콜라이더 위치):
    • 설명: 이 모드는 상호작용 가능한 객체의 콜라이더 목록을 사용하여 각 콜라이더까지의 가장 짧은 거리를 계산합니다.
    • 성능 비용: 중간.
    • 거리 계산 정확도: 대부분의 객체에 대해 중간 정도의 정확도를 제공합니다. 이 방법은 콜라이더의 위치를 기반으로 거리를 계산하기 때문에, 객체의 형태가 복잡하거나 여러 콜라이더를 가진 경우에도 적절한 정확도를 제공할 수 있습니다.
  3. Collider Volume (콜라이더 볼륨):
    • 설명: 이 모드는 상호작용 가능한 객체의 콜라이더 목록을 사용하여 각 콜라이더의 가장 가까운 지점까지의 거리를 계산합니다(콜라이더의 표면이나 내부에 있는 지점 포함).
    • 성능 비용: 높음.
    • 거리 계산 정확도: 높음. 이 방법은 콜라이더의 볼륨 내부나 표면에 있는 가장 가까운 지점까지의 거리를 계산하기 때문에, 가장 정확한 거리 측정을 제공하지만 성능 비용이 높습니다.

각 모드는 성능과 정확도의 균형을 고려하여 선택해야 합니다. 애플리케이션의 요구 사항과 사용자 경험에 따라 적절한 모드를 선택하는 것이 중요합니다.

Transform PositionInteractable의 변환 위치를 사용하여 거리를 계산합니다. 이 옵션은 성능 비용이 낮지만 일부 객체의 경우 거리 계산 정확도가 낮을 수 있습니다.
 Collider PositionInteractable의 Colliders 목록을 사용하여 각각에 대한 최단 거리를 사용하여 거리를 계산합니다. 이 옵션은 성능 비용이 적당하며 대부분의 객체에 대해 적당한 거리 계산 정확도를 가져야 합니다.
 Collider VolumeInteractable의 Colliders 목록을 사용하여 각각의 가장 가까운 지점(표면 또는 Collider 내부)까지의 최단 거리를 사용하여 거리를 계산합니다. 이 옵션은 성능 비용이 높지만 거리 계산 정확도가 높습니다.

 
 


 

Custom ReticleThe reticle that appears at the end of the line when valid.
end of the line

Left Controller자식으로 Ray Interactor 프리팹을 넣는다 

 
레티클용 프리팹을 만들어주고 

 
 
Custom Reticle 프로퍼티에 넣어준다 

 


Focus ModeSpecifies the focus policy of this interactable.
 NoneSet Focus Mode to None to disable the foucs state for an interactable.
 SingleSet Focus Mode to Single to allow interactors of a single interaction group to focus this interactable.
 MultipleSet Focus Mode to Multiple to allow interactors of multiple interaction groups to focus this interactable

 
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/architecture.html#states

Architecture | XR Interaction Toolkit | 2.4.3

Architecture This section describes the relationship between the core components of the interaction system and the states that make up the lifecycle of an interaction. States The Interaction system has three common states: Hover, Select, and Activate. Thes

docs.unity3d.com

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/architecture.html#interaction-groups

Architecture | XR Interaction Toolkit | 2.4.3

Architecture This section describes the relationship between the core components of the interaction system and the states that make up the lifecycle of an interaction. States The Interaction system has three common states: Hover, Select, and Activate. Thes

docs.unity3d.com

 

반응형
:

XR Interaction Toolkit 2.4.3 (Custom Hand Pose) OpenXR

VR/XR Interaction Toolkit 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];
        }
    }
}

 
 

반응형
:

[OpenXR] Ray Interactor (XR Interaction Toolkit Movement 타입 차이점)

VR/XR Interaction Toolkit 2023. 12. 28. 17:02
반응형

XR Interaction Toolkit (XRIT)은 Unity에서 확장형 현실(VR), 증강현실(AR) 및 기타 현실 혼합 기술(XR) 애플리케이션을 개발하는 데 사용되는 훌륭한 도구입니다. XRIT에서 제공하는 다양한 이동 방식 중 Kinematic Movement, Instantaneous Movement, 그리고 Velocity Tracked Movement의 주요 차이점을 설명해 드리겠습니다.

  1. Kinematic Movement (운동학적 움직임):
    • Kinematic Movement는 XRIT에서 가장 기본적인 이동 방식 중 하나입니다.
    • 이 방식은 개체나 사용자의 위치와 상호 작용하는 데 사용됩니다.
    • Kinematic 이동은 사용자 입력을 기반으로 플레이어 또는 개체를 부드럽게 제어하며, 물리적인 특성을 고려하지 않습니다.
    • 이동이 부드럽고 예측 가능하며, 사용자 경험을 개선하기 위해 고려되는 역학 요소가 없습니다.
  2. Instantaneous Movement (순간이동):
    • Instantaneous Movement는 사용자가 즉각적으로 원하는 위치로 이동하는 방식입니다.
    • 이 방식은 사용자가 특정 위치로 순간적으로 이동할 수 있도록 합니다.
    • 주로 포털, 텔레포트, 뷰포인트 변경 등의 상황에서 사용됩니다.
    • 이동이 순간적이므로 물리 역학을 고려하지 않으며, 사용자 경험의 편리성을 높입니다.
  3. Velocity Tracked Movement (속도 추정 이동):
    • Velocity Tracked Movement는 사용자의 속도와 방향에 따라 이동하는 방식입니다.
    • 사용자의 움직임을 실시간으로 추적하고, 이동 방향과 속도를 고려하여 개체 또는 플레이어를 제어합니다.
    • 이 방식은 물리 역학을 시뮬레이션하고 실제 물체의 움직임을 모방할 때 사용됩니다.
    • 예를 들어, 사용자의 손이나 머리 움직임을 추적하여 VR 환경에서 자연스러운 상호 작용을 제공합니다.

이 세 가지 이동 방식은 XRIT에서 제공하는 다양한 상황에 맞게 선택할 수 있으며, 프로젝트의 목적 및 사용자 경험을 고려하여 적절한 방식을 선택해야 합니다.
 


 
Instantaneous Movement (순간이동)
 
빈오브젝트를 생성 한다 (Interactable Instant Pyramid)

 
Mesh Filter 와 Mesh Renderer 를 부착 한다 

 
Mesh Collider를 부착 한다 

 
Rigidbody를 부착 한다 

 
XR Grab Interactable 컴포넌트를 부착 하고 Use Dynamic Attach를 체크 한다 

 
 
Movement Type을 Instantaneous로 변경 한다 

 
프로젝트 창에서 Interaction Affordance프리팹을 찾아 자식으로 넣어 준다 

 
Interactable Source 에 Interactable Instantaneous Pyramid 오브젝트를 넣어 준다 


Left Controller 셋팅 
 
Right Controller를 선택 하고 XR Controller를 제외한 나머지 컴포넌트를 제거 한다 

 
XR Controller 의 Model Prefab, Model Parent 프로퍼티에 알맞게 넣어준다 

 
프로젝트 창에서 Ray Interactor 프리팹을 찾아서 Right Controller 자식으로 넣어 준다 

 
 
다음과 같이 설정 한다 
이때 Attach Transform에 Right Controller Stabilized Attach를 
Ray Origin Transform 에는 Right Controller Stabilized 를 넣어 준다 
 

 
XR Interaction Toolkit (XRIT)에서 XR Controller 컴포넌트의 Attach Transform 및 Ray Origin Transform 프로퍼티는 컨트롤러와 상호 작용하는 데 사용되는 중요한 설정입니다. 이 두 프로퍼티에 대한 설명은 다음과 같습니다:

  1. Attach Transform:
    • Attach Transform 프로퍼티는 XR 컨트롤러에 연결된 개체의 위치와 회전을 지정합니다.
    • 일반적으로 이 프로퍼티는 컨트롤러의 물리적인 모양과 일치하는 개체를 나타냅니다. 예를 들어, VR 헤드셋의 컨트롤러에는 특정한 모양을 가진 컨트롤러 모델이 있습니다.
    • 이 프로퍼티를 올바르게 설정하면, XR 컨트롤러가 이 개체에 연결되어 사용자의 손 또는 컨트롤러와 같은 개체로 보입니다.
    • Attach Transform은 컨트롤러의 위치 및 회전을 정확하게 추적하기 위해 사용됩니다.
  2. Ray Origin Transform:
    • Ray Origin Transform 프로퍼티는 XR 컨트롤러에서 발사되는 레이(선)의 시작점을 나타냅니다.
    • 이 레이는 주로 컨트롤러 버튼을 누르고 레이를 사용하여 상호 작용할 때 유용합니다. 예를 들어, 사용자가 컨트롤러 버튼을 눌러 가상의 레이저 포인터를 표시하고 화면의 개체와 상호 작용하는 경우가 있습니다.
    • Ray Origin Transform을 올바르게 설정하면, 레이의 시작점이 컨트롤러의 원하는 위치에 정확하게 위치하게 됩니다.
    • 이것은 사용자가 레이를 통해 상호 작용하는 가상의 개체를 정확하게 조준하는 데 도움이 됩니다.

이 두 프로퍼티는 XRIT를 사용하여 상호 작용 기능을 개발할 때 중요한 역할을 합니다. 올바르게 설정되지 않으면 사용자 경험이 부정확하거나 불편할 수 있으므로 주의 깊게 설정해야 합니다.
 
실행해보고 결과를 확인 한다 
 


 

Velocity Tracked Movement 

 
빈 오브젝트를 만들어주고 (Interactable Velocity Tracked Wedge)

 
Mesh Filter와 Mesh Renderer컴포넌트를 부착 하고 
Mesh와 Material을 설정 한다 

Mesh Collider를 부착 하고 

Rigidbody를 부착 한다 

 
XR Grab Interactable 컴포넌트를 부착 한다 
 
Movement Type을 Velocity Tracking으로 변경 하고 
Use Dynamic Attach 를 체크 한다 

 
이하 기본 설정 

 
 
프로젝트 창에서 Interaction Affordance를 검색해 프리팹을 찾아 자식으로 넣어준다 

 
 
 
실행후 결과를 확인 한다 
 

 

반응형
: