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

  1. 2023.12.30 XR Interaction Toolkit 2.4.3 (Grab Interactable)
  2. 2023.12.29 XR Interaction Toolkit 2.4.3 (Custom Hand Pose) OpenXR
  3. 2023.12.28 [OpenXR] Ray Interactor (XR Interaction Toolkit Movement 타입 차이점)
  4. 2023.12.28 [OpenXR] XR Interaction Toolkit 2.4.3 (Two Hand Grab)
  5. 2023.12.27 [XR Interaction Toolkit 2.4.3] Custom Hand Grab Pose
  6. 2023.12.27 XR Interaction Toolkit Animated Hands (Grip, Pinch)

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를 검색해 프리팹을 찾아 자식으로 넣어준다 

 
 
 
실행후 결과를 확인 한다 
 

 

반응형
:

[OpenXR] XR Interaction Toolkit 2.4.3 (Two Hand Grab)

VR/XR Interaction Toolkit 2023. 12. 28. 11:25
반응형

 

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

 

XR Interaction Toolkit 2.4.3

XR Interaction Toolkit 2.4.3 OpenXR Oculus

www.youtube.com


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

리소스 

https://assetstore.unity.com/packages/3d/props/guns/stylized-m4-assault-rifle-with-scope-complete-kit-with-gunshot-v-178197

 
 
컨트롤러를 사용한 한손잡기를 만들어 준다 
 
새 씬을 만들고 
메인 카메라를 제거 후 XR Origin을 생성 한다 

 
XR Origin오브젝트를 선택후 Tracking Origin Mode를 Floor로 변경 

 
Left/Right Controller를 선택하고 

 
XR Controller를 제외한 나머지 컴포넌트는 제거 한다 

 
다음과 같은 구조를 만들어주고 

 
Left Controller를 선택후 Model Prefab과 Model Parent를 각각 넣어준다 

 
왼손 Offset의 위치와 회전을 설정한다 

왼손 설정된 모습

 
오른손도 동일하게 작업 한다 
 
Right Controller를 선택하고 

 
Model Prefab/Parent를 넣어주고 

 
 
Offset의 위치와 회전을 맞춰준다 

 
 

완성된 왼손 컨트롤러 핸드

 
 

 
 


 
리소스를 다운 받고 

 
프리팹을 선택한다 

 
다음과 같이 핑크색으로 보이면 

 
 
 
핑크색 메터리얼들을 선택 하고 

 

 
Edit > Rendering > Materials > Convert Selected Bulit-In Materials to URP를 선택한다 

 

 
 
이제 프리팹을 씬으로 가져온후 위치를 맞추고 실행후 정면에 잘 보이는지 확인 한다 

 
 
실행후 확인 
 

 
 
 
이제 씬에서 총을 선택하고 언팩 하고 다음과 같은 구조로 만들어 준다 

 
기존에 붙어 있던 스크립트는 제거 한다 

 
 
Rifle오브젝트를 선택하고 위치와 크기를 조절 한다 

 
 
손의 크기에 맞게 크기를 조절하고 위치도 잡을수 있게 수정 한다 

 
다시 실행후 크기와 위치가 맞는지 확인한다 
 

 
 


 
Left Controller를 선택하고 
 

 
XR Direct Interactor컴포넌트를 부착하고 

 
후 Interaction Manager 프로퍼티에 XR Interaction Manager오브젝트를 넣어준다 

 
이어서 Sphere Collider를 추가 하고 Radius를 0.1로 설정한다 

 
 
이제 Rifle오브젝트를 선택하고 Rigidbody, XR Grab Interactable 컴포넌트를 부착한뒤 
다음과 같이 설정 한다 

 
Rifle을 선택하고 자식으로 빈오브젝트 (Collider)를 만들어 주고 
 

 
 
Box Collider컴포넌트를 부착 하고 Is Trigger를 체크 한다  

 
크기와 위치 회전을 조절해 핸들 부붙에 가져다 놓는다 

 
 
Rifle 오브젝트를 선택하고 Colliders에 넣어준다 

 
 
Assault_Rifle오브젝트를 선택하고 

Mesh Collider를 제거 한다 

 
 
 
위치와 크기가 맞지 않는다면Assault_Rifle_M4를 선택해 

위치와 회전을 다음과 같이 설정하고 

 
Rifle을 선택해 정면을 바라보게 만들어 준다 

 
실행후 결과를 확인 한다 

 
 
손의 위치가 맞지 않으면 

 
 
Offset의 위치를 설정 한다 

 
 
 

왼손 오프셋

 

오른손 오프셋

 

라이플 위치와 회전 크기

 
실행후 결과를 확인 하자 

 


 
Rifle을 선택한뒤 XR Grab Interactable 컴포넌트의 Select Mode를 Multiple로 변경 한다 

 
다음과 같이 간단히 두손 잡기를 구현 할수 있다 
 

 
 
콜라이더를 하나 더 만들어 핸드가드 (총열덮개) 부분에 위치 시킨다 

 
Rifle 오브젝트를 선택하고 XR Grab Interactable 컴포넌트의 Colliders에 추가 한다 

 

 


라이플을 선택후 

 
 
XR Grab Interactable컴포넌트의 Use Dynamic Attack 프로퍼티를 체크 해본다 

 
다음과 같이 잡은 위치와 회전을 기반으로 잡기가 동작 한다 
 

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/api/UnityEngine.XR.Interaction.Toolkit.XRGrabInteractable.html

 
 
완성된 결과물 


 
도전 
이전시간에 했던 Hand Grab Pose 를 이용해 손동작을 적용해보자

 

 

 

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

public class Hand : MonoBehaviour
{
    public InputDeviceCharacteristics inputDeviceCharacteristics;

    private InputDevice _targetDevice;
    [SerializeField] private Animator _handAnimator;

    private void Start()
    {
        InitializeHand();
    }

    private void InitializeHand()
    {
        List<InputDevice> devices = new List<InputDevice>();
        InputDevices.GetDevicesWithCharacteristics(inputDeviceCharacteristics, devices);

        if (devices.Count > 0)
        {
            _targetDevice = devices[0];
        }
    }

    private void Update()
    {
        if (!_targetDevice.isValid)
        {
            InitializeHand();
        }
        else
        {
            UpdateHand();
        }
    }

    private void UpdateHand()
    {
        SetAnimatorParameterValue(CommonUsages.grip, "Grip");
        
        // Animator 컴포넌트에 해당 트리거 파라미터가 존재하는지 확인
        if (HasParameterOfType(this._handAnimator, "Trigger", AnimatorControllerParameterType.Trigger))
        {
            // 트리거 파라미터가 존재하면 실행
            SetAnimatorParameterValue(CommonUsages.trigger, "Trigger");
        }
        else
        {
            // 트리거 파라미터가 존재하지 않으면 실행하지 않음
            Debug.LogWarning("Trigger parameter does not exist");
        }
        
    }
    
    public bool HasParameterOfType(Animator animator, string paramName, AnimatorControllerParameterType paramType)
    {
        AnimatorControllerParameter[] parameters = animator.parameters;
        foreach (AnimatorControllerParameter parameter in parameters)
        {
            if (parameter.name == paramName && parameter.type == paramType)
            {
                return true;
            }
        }
        return false;
    }

    private void SetAnimatorParameterValue(InputFeatureUsage<float> feature, string parameterName)
    {
        if (_targetDevice.TryGetFeatureValue(feature, out float value))
        {
            _handAnimator.SetFloat(parameterName, value);
        }
        else
        {
            _handAnimator.SetFloat(parameterName, 0);
        }
    }
}

 
 
Use Dynamic Attach 를 사용하지 않았을 경우 
 

 
 
Use Dynamic Attach를 사용했을 경우 
 

 

반응형
:

[XR Interaction Toolkit 2.4.3] Custom Hand Grab Pose

VR/XR Interaction Toolkit 2023. 12. 27. 15:59
반응형

핸드 모델 붙이기 

 
 
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



새 씬을 만들고 메인 카메라를 지우고 XR Origin을 생성 합니다.

 
Left / Right Controller 오브젝트를 선택 하고 XR Controller를 제외한 나머지를 제거 합니다.

 
다음 구조로 만들어 주고 

Left Controller Model을 넣어 줍니다.

오른손도 똑같이 해줍니다.
 
 
 
Offset설정을 해줍니다 
 

왼손 Offset
오른손 Offset

 
 

 


 

오브젝트 잡기 만들기 

 
총 프리팹을 가져와서 언팩 하고 

 
Model을 선택해서 애니메이터를 제거 하고 

 

 
 
다음 구조로 만들어 주고 

 
Gun을 선택해서 Rigidbody와 XR Grab Interactable컴포넌트를 부착 하고 다음과 같이 설정 한다 

 
 
Left / Right Controller 를 선택하고 

 
XR Direct Interactor 컴포넌트를 부착 하고 Manager를 넣어준다 

 
 
Gun을 선택 하고 Collider를 만들어주고 크기를 조절 한다 
 

 
Is Trigger를 체크 한다 

 
 
Left / Right Controller를 선택하고 Sphere Collider를 추가 한다 Radius의 값은 0.1로 설정 한다 

 
Gun을 선택하고 XR Grab Interactable의 Colliders에 Collider를 넣어준다 

 
Gun의 위치를 설정 하고 

 
그랩이 잘 되는지 확인 한다 
손의 Offset위치에 따라 잡는 위치가 달라지니 적당히 조절 한다 
 


 

커스텀 핸드 포즈 만들기 

 
Gun을 선택 하고 
빈오브젝트를 만들고 (Hand Pose)
그 자식으로 핸드 프리팹을 넣어준다 

Animator와 Hand컴포넌트는 제거 한다 

 

 
 
 
쉽지 않겠지만 손의 포즈를 잡아 준다 

 


스크립트 작성 

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

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;

}

 
핸드 프리팹에 부착 한다 

 
 
프로퍼티에 오브젝트를 넣어주고 

 
자물쇠로 잠그고 

다음 오브젝트들을 선택하고 

 
Bones에 넣어준다 

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

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

public class HandPose : MonoBehaviour
{
    public HandData leftHandData;
    void Start()
    {
        XRGrabInteractable grabInteractable = GetComponent<XRGrabInteractable>();
        
        grabInteractable.selectEntered.AddListener(this.SetupPose);
        
        //leftHandData.gameObject.SetActive(false);
    }

    private void SetupPose(SelectEnterEventArgs arg0)
    {
        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);
    }
}

Gun을 선택하고 HandPose 를 부착 한다
 

Hand Data에는 다음 오브젝트를 넣어준다 

 
 
실행후 왼손으로 테스트 해본다 
 


 

핸드의 값 적용하기

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

public class HandPose : MonoBehaviour
{
    public HandData leftHandData;

    private Vector3 startingHandPosition;
    private Vector3 finalHandPosition;
    private Quaternion startingHandRotation;
    private Quaternion finalHandRotation;
    private Quaternion[] startingFingerRotaions;
    private Quaternion[] finalFingerRotations;
    
    void Start()
    {
        XRGrabInteractable grabInteractable = GetComponent<XRGrabInteractable>();
        
        grabInteractable.selectEntered.AddListener(this.SetupPose);
        
        //leftHandData.gameObject.SetActive(false);
    }

    private void SetupPose(SelectEnterEventArgs arg0)
    {
        Debug.LogFormat("SetupPose");
        
        if (arg0.interactorObject is XRDirectInteractor)
        {
            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, leftHandData);
            SetHandData(handData, finalHandPosition, finalHandRotation, finalFingerRotations);
        }
        
    }

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

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

 

 
 
다음 코드로 수정 한다 

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

public class HandPose : MonoBehaviour
{
    public HandData leftHandData;

    private Vector3 initialHandPosition = Vector3.zero;
    private Vector3 targetHandPosition = Vector3.zero;
    private Quaternion initialHandRotation = Quaternion.identity;
    private Quaternion targetHandRotation = Quaternion.identity;
    private Quaternion[] initialFingerRotations;
    private Quaternion[] targetFingerRotations;

    private void Start()
    {
        XRGrabInteractable grabInteractable = GetComponent<XRGrabInteractable>();

        grabInteractable.selectEntered.AddListener(SetupPose);
        grabInteractable.selectExited.AddListener(UnSetPose);

        // leftHandData.gameObject.SetActive(false);
    }
    
    private void UnSetPose(SelectExitEventArgs arg0)
    {
        if (arg0.interactorObject is XRDirectInteractor)
        {
            Debug.LogFormat("<color=lime>[UnSetPose]</color> : {0}", arg0.interactorObject.transform.name);
            HandData handData = arg0.interactorObject.transform.GetComponentInChildren<HandData>();
            EnableHandDataAnimation(handData);
            SetHandData(handData, initialHandPosition, initialHandRotation, initialFingerRotations);
        }
    }

    private void SetupPose(SelectEnterEventArgs arg0)
    {
        if (arg0.interactorObject is XRDirectInteractor)
        {
            Debug.LogFormat("<color=yellow>[SetupPose]</color> : {0}", arg0.interactorObject.transform.name);
            HandData handData = arg0.interactorObject.transform.GetComponentInChildren<HandData>();
            DisableHandDataAnimation(handData);
            SetHandDataValues(handData, leftHandData);
            SetHandData(handData, targetHandPosition, targetHandRotation, targetFingerRotations);
        }
    }

    private Vector3 CorrectScale(Vector3 position, Vector3 scale)
    {
        return new Vector3(position.x / scale.x, position.y / scale.y, position.z / scale.z);
    }

    private void EnableHandDataAnimation(HandData handData)
    {
        handData.anim.enabled = true;
    }

    private void DisableHandDataAnimation(HandData handData)
    {
        handData.anim.enabled = false;
    }

    private void SetHandDataValues(HandData h1, HandData h2)
    {
        initialHandPosition = CorrectScale(h1.root.localPosition, h1.root.localScale);
        targetHandPosition = CorrectScale(h2.root.localPosition, h2.root.localScale);

        initialHandRotation = h1.root.localRotation;
        targetHandRotation = h2.root.rotation;

        initialFingerRotations = new Quaternion[h1.bones.Length];
        targetFingerRotations = new Quaternion[h2.bones.Length];

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

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

 
 
손목 위치가 안맞는다 

 
 
Offset을 변경해서 마춰준다 
 

 

 
 
오른손도 해줍니다 

 
 

 

 
 

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

public class HandPose : MonoBehaviour
{
    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)
        {
            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, leftHandData);
            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)
        {
            
            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];
        }
    }
}

 

Offset의 위치를 잘 맞춰주면 댄다 

 
 
 
 


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

public class Hand : MonoBehaviour
{
    public InputDeviceCharacteristics inputDeviceCharacteristics;

    private InputDevice _targetDevice;
    [SerializeField] private Animator _handAnimator;

    private void Start()
    {
        InitializeHand();
    }

    private void InitializeHand()
    {
        List<InputDevice> devices = new List<InputDevice>();
        InputDevices.GetDevicesWithCharacteristics(inputDeviceCharacteristics, devices);

        if (devices.Count > 0)
        {
            _targetDevice = devices[0];
        }
    }

    private void Update()
    {
        if (!_targetDevice.isValid)
        {
            InitializeHand();
        }
        else
        {
            UpdateHand();
        }
    }

    private void UpdateHand()
    {
        SetAnimatorParameterValue(CommonUsages.grip, "Grip");
        SetAnimatorParameterValue(CommonUsages.trigger, "Trigger");
    }

    private void SetAnimatorParameterValue(InputFeatureUsage<float> feature, string parameterName)
    {
        if (_targetDevice.TryGetFeatureValue(feature, out float value))
        {
            _handAnimator.SetFloat(parameterName, value);
        }
        else
        {
            _handAnimator.SetFloat(parameterName, 0);
        }
    }
}
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;

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

public class HandPose : MonoBehaviour
{
    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)
        {
            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, leftHandData);
            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)
        {
            
            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];
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//This will allow us to get InputDevice
using UnityEngine.XR;

public class InputReader : MonoBehaviour
{
//Creating a List of Input Devices to store our Input Devices in
    List<InputDevice> inputDevices = new List<InputDevice>();

// Start is called before the first frame update
    void Start()
    {
//We will try to Initialize the InputReader here, but all components may not be loaded
        InitializeInputReader();
    }

//This will try to initialize the InputReader by getting all the devices and printing them to the debugger.
    void InitializeInputReader()
    {
        InputDevices.GetDevices(inputDevices);

        foreach (var inputDevice in inputDevices)
        {
            Debug.Log(inputDevice.name + " " + inputDevice.characteristics);
        }
    }

// Update is called once per frame
    void Update()
    {
//We should have a total of 3 Input Devices. If it’s less, then we try to initialize them again.
        if (inputDevices.Count < 2)
        {
            InitializeInputReader();
        }
    }
}
반응형
:

XR Interaction Toolkit Animated Hands (Grip, Pinch)

VR/XR Interaction Toolkit 2023. 12. 27. 12:12
반응형

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


새 씬을 만들어 주고 

메인 카메라를 제거 하고 XR Origin을 생성 한다 

 
 
Left / Right Controller 오브젝트를 선택하고 XR Controller 컴포넌트를 제외하고 모두 제거 한다 

 
 
Left Controller를 선택하고 다음과 같은 구조로 빈오브젝트를 생성한다 

 
Left Controller를 선택하고 Model Prefab과 Model Parent를 넣어준다 

 
오른쪽도 동일하게 진행한다 
 
Offset을 설정해 손의 위치와 회전을 설정 한다 
 

왼손 Offset
오른손 Offset
실행 결과

 


 

애니메이션 잡기 

 
왼손 프리팹모드로 들어와 

 
 
Animation을 선택한다 

Create버튼을 누르고 

 
애니메이션을 저장 한다 
Hand_L_Grip

레코드 버튼을 누르고 손을 접어 준다

 
 
이제 레코드를 해제 한다 
 

Pinch 애니메이션 만들기 

새로운 클립을 생성하고 
 

저장한다 

 
다시 레코드 버튼을 누르고 손을 접어준다 

 
이제 레코드를 해제 한다 
 
 

완전히 편 손 애니메이션 만들기 

새로운 클립을 만들고 

저장한다 

 
기본이 펴진 상태이기 때문에 키는 안잡아도 된다 

 
 
애니메이터 컨트롤러를 더블클릭하고 

 
안에 있는 State를 제거 한다 

 
블랜드 트리를 생성하고 

 
Float 형식으로 Grip, Trigger 파라미터를 생성하고 기존에 있던 것은 제거 한다 
팝업이 뜨면 그냥 지우겠다고 하면된다 

 
 
 
블랜드 트리를 선택 하고 

 
블랜드 트리를 선택하고 인스펙터에서 Blend Type을 2D Freeform Cartesian을 선택 한다 

 
Add Motion Field를 4개 만든다 

 
Pose X, Y 를 다음과 같이 설정 하고 

 
다음과 같이 모션을 넣어 준다 

 
손을 넣어주고 

 
 
빨간 점을 이용해 모션이 동작 하는지 확인 한다 

 
 
 
스크립트를 만들어 다음과 같이 작성하고 프리팹에 부착 한다 

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

public class Hand : MonoBehaviour
{
    public InputDeviceCharacteristics inputDeviceCharacteristics;

    private InputDevice _targetDevice;
    [SerializeField] private Animator _handAnimator;

    private void Start()
    {
        InitializeHand();
    }

    private void InitializeHand()
    {
        List<InputDevice> devices = new List<InputDevice>();
        InputDevices.GetDevicesWithCharacteristics(inputDeviceCharacteristics, devices);

        if (devices.Count > 0)
        {
            _targetDevice = devices[0];
        }
    }

    private void Update()
    {
        if (!_targetDevice.isValid)
        {
            InitializeHand();
        }
        else
        {
            UpdateHand();
        }
    }

    private void UpdateHand()
    {
        SetAnimatorParameterValue(CommonUsages.grip, "Grip");
        SetAnimatorParameterValue(CommonUsages.trigger, "Trigger");
    }

    private void SetAnimatorParameterValue(InputFeatureUsage<float> feature, string parameterName)
    {
        if (_targetDevice.TryGetFeatureValue(feature, out float value))
        {
            _handAnimator.SetFloat(parameterName, value);
        }
        else
        {
            _handAnimator.SetFloat(parameterName, 0);
        }
    }
}

 
이때 Input Device Characteristics 는 Controller, Left를 선택 한다 

 
실행후 결과를 확인해보자 
 
인덱스 트리거를 눌렀을때 Pich가 
핸드 트리거를 눌렀을때 Grip 애니메이션이 실행되는지 확인한다 
 

 
 
오른손도 동일하게 작업 한다 
프리팹을 선택하고 

 

 
애니메이션 창을 열고 

 
저장하고 

 
 
레코드 버튼을 누르고 
손 모양을 잡아준다 

 
 
새 클립을 만들고 

다시 저장 하고 

 
레코드 버튼을 누르고 손 모양( Pinch)를 잡아 준다 

 
다시 새 클립을 만들고 

Hand_R_Open으로 저장한다 

 
기본형이 펴진 상태기 때문에 키는 안잡아도 된다 

 
 
핸드 프리팹을 선택하고 

 
 
컨트롤러를 더블클릭하고 

 
안에 있는 State들을 제거 한다 

 
Float 형식으로 2개의 파라미터 (Grip, Trigger)를 만들어 주고

 
 
블랜드 트리를 생성하고 더블 클릭해서 들어간다 

 
 
인스펙터에서 2D Freefrom Cartesian을 선택하고 

다음과 같이 파라미터를 설정 한다 

 
+ 버튼을 눌러 Add motion field 를 4개 생성하고 
다음과 같이 설정 한다 

 

 
이제 블랜드 트리의 슬라이더를 이용해 애니메이션 동작을 확인 한다 
 

 
 
핸드 프리팹을 선택하고 Hand 스크립트를 부착 한다
 
이때 Input Device Characteristics는 Controller, Right를 선택하고 
Hand Animator는 위에 있는 Animator 컴포넌트를 넣어주면 된다 

 
플레이 해보고 두 손 모두 애니메이션이 잘 나오는지 확인 하자 
 

 

반응형
: