기본 잡기 기능을 허용하는 상호 작용 가능한 구성 요소입니다. 이 동작이 인터랙터에 의해 선택(잡아)지면 이 동작은 이를 따라가며 놓을 때 속도를 상속합니다.
Interaction Manager
The 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 Mask
Allows interaction with Interactors whose Interaction Layer Mask overlaps with any Layer in this Interaction Layer Mask.
상호 작용 레이어 마스크가 이 상호 작용 레이어 마스크의 레이어와 겹치는 인터랙터와의 상호 작용을 허용합니다.
큐브를 하나 복사 하고
복사된 큐브를 선택하고
인터렉션 레이어를 추가 하고
인터렉션 레이어 마스크를 변경한다
이제 왼손 컨트롤러의 Direct Interactor를 선택후
인터렉션 레이어 마스크를 설정 한다
이 Interactable과의 상호 작용에 사용할 충돌체(비어 있는 경우 하위 충돌체를 사용함)
Colliders
Colliders to use for interaction with this Interactable (if empty, will use any child Colliders).
다음과 같이 콜라이더를 하나 더 만들고 실행해 본다
자동으로 하위 콜라이더들이 할당 되었다
가장 빠른 것부터 가장 정확한 것까지 인터랙터까지의 거리가 계산되는 방식을 지정합니다. Mesh Colliders를 사용하는 경우 Collider Volume은 메쉬가 볼록한 경우에만 작동합니다.
Distance Calculation Mode
Specifies 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 애플리케이션의 성능과 사용자 경험을 최적화하는 데 중요합니다. 각 모드에 대한 자세한 설명은 다음과 같습니다:
Transform Position (변환 위치):
설명: 이 모드는 상호작용 가능한 객체의 변환 위치를 사용하여 거리를 계산합니다.
성능 비용: 낮음.
거리 계산 정확도: 일부 객체에 대해 정확도가 낮을 수 있습니다. 이는 객체의 중심점을 기준으로 거리를 계산하기 때문에, 크기가 크거나 복잡한 형태의 객체에서는 정확도가 떨어질 수 있습니다.
Collider Position (콜라이더 위치):
설명: 이 모드는 상호작용 가능한 객체의 콜라이더 목록을 사용하여 각 콜라이더까지의 가장 짧은 거리를 계산합니다.
성능 비용: 중간.
거리 계산 정확도: 대부분의 객체에 대해 중간 정도의 정확도를 제공합니다. 이 방법은 콜라이더의 위치를 기반으로 거리를 계산하기 때문에, 객체의 형태가 복잡하거나 여러 콜라이더를 가진 경우에도 적절한 정확도를 제공할 수 있습니다.
Collider Volume (콜라이더 볼륨):
설명: 이 모드는 상호작용 가능한 객체의 콜라이더 목록을 사용하여 각 콜라이더의 가장 가까운 지점까지의 거리를 계산합니다(콜라이더의 표면이나 내부에 있는 지점 포함).
성능 비용: 높음.
거리 계산 정확도: 높음. 이 방법은 콜라이더의 볼륨 내부나 표면에 있는 가장 가까운 지점까지의 거리를 계산하기 때문에, 가장 정확한 거리 측정을 제공하지만 성능 비용이 높습니다.
각 모드는 성능과 정확도의 균형을 고려하여 선택해야 합니다. 애플리케이션의 요구 사항과 사용자 경험에 따라 적절한 모드를 선택하는 것이 중요합니다.
Transform Position
Interactable의 변환 위치를 사용하여 거리를 계산합니다. 이 옵션은 성능 비용이 낮지만 일부 객체의 경우 거리 계산 정확도가 낮을 수 있습니다.
Collider Position
Interactable의 Colliders 목록을 사용하여 각각에 대한 최단 거리를 사용하여 거리를 계산합니다. 이 옵션은 성능 비용이 적당하며 대부분의 객체에 대해 적당한 거리 계산 정확도를 가져야 합니다.
Collider Volume
Interactable의 Colliders 목록을 사용하여 각각의 가장 가까운 지점(표면 또는 Collider 내부)까지의 최단 거리를 사용하여 거리를 계산합니다. 이 옵션은 성능 비용이 높지만 거리 계산 정확도가 높습니다.
Custom Reticle
The reticle that appears at the end of the line when valid.
이 속성은 XR Grab Interactable 컴포넌트의 중요한 속성 중 하나입니다. 이것은 해당 상호작용 대상이 그랩될 때 동적으로 그랩 포인트(Grab Point)를 생성하고 이동시키는 데 사용됩니다.
Use Dynamic Attach를 활성화하면 XR Grab Interactable가 그랩될 때 그랩 포인트가 자동으로 생성되고 설정된 조건에 따라 업데이트됩니다. 이로써 사용자의 손 또는 컨트롤러와 상호작용하는 물체에 더 자연스러운 물리적 연결을 만들 수 있습니다.
예를 들어, Use Dynamic Attach를 사용하면 물체가 그랩될 때 손 또는 컨트롤러와 물체 사이의 상대 위치와 회전이 고정되지 않고 동적으로 조정됩니다.
Match Position (일치하는 위치):
"Match Position" 프로퍼티는 XR Grab Interactable 컴포넌트의 일부로 설정할 수 있는 옵션 중 하나입니다.
이 프로퍼티를 활성화하면 물체가 그랩될 때 그랩 포인트(Grab Point)가 사용자의 손 또는 컨트롤러 위치와 일치하도록 설정됩니다. 즉, 그랩 포인트가 그랩될 때 손의 위치에 따라 동적으로 조정됩니다.
일반적으로 "Match Position"을 사용하면 물체가 그랩될 때 물체의 위치가 사용자의 손 또는 컨트롤러의 위치와 일치하여 더 자연스러운 상호작용을 가능하게 합니다.
Match Rotation (일치하는 회전):
"Match Rotation" 프로퍼티는 XR Grab Interactable 컴포넌트의 또 다른 중요한 설정입니다.
이 프로퍼티를 활성화하면 물체가 그랩될 때 그랩 포인트의 회전이 사용자의 손 또는 컨트롤러의 회전과 일치하도록 설정됩니다. 즉, 그랩 포인트의 회전이 동적으로 조정됩니다.
"Match Rotation"을 사용하면 물체가 그랩될 때 회전이 맞추어져 물체가 사용자의 손 또는 컨트롤러와 일치하여 물체를 더 쉽게 조작할 수 있습니다.
Snap To Collider Volume:
이 옵션은 XR Grab Interactable가 그랩될 때 물체를 자동으로 해당 콜라이더 볼륨에 맞게 조정하는 데 사용됩니다.
Snap To Collider Volume을 활성화하면 XR Grab Interactable가 그랩될 때 물체가 그랩 포인트에 따라 자동으로 해당 콜라이더의 경계에 맞추어집니다. 이것은 물체가 손이나 컨트롤러와 자연스럽게 들어맞는 것을 도와줍니다.
이 옵션을 사용하면 사용자의 입체감과 상호작용 경험이 향상됩니다.
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);
}
}
XR Interaction Toolkit (XRIT)은 Unity에서 확장형 현실(VR), 증강현실(AR) 및 기타 현실 혼합 기술(XR) 애플리케이션을 개발하는 데 사용되는 훌륭한 도구입니다. XRIT에서 제공하는 다양한 이동 방식 중 Kinematic Movement, Instantaneous Movement, 그리고 Velocity Tracked Movement의 주요 차이점을 설명해 드리겠습니다.
Kinematic Movement (운동학적 움직임):
Kinematic Movement는 XRIT에서 가장 기본적인 이동 방식 중 하나입니다.
이 방식은 개체나 사용자의 위치와 상호 작용하는 데 사용됩니다.
Kinematic 이동은 사용자 입력을 기반으로 플레이어 또는 개체를 부드럽게 제어하며, 물리적인 특성을 고려하지 않습니다.
이동이 부드럽고 예측 가능하며, 사용자 경험을 개선하기 위해 고려되는 역학 요소가 없습니다.
Instantaneous Movement (순간이동):
Instantaneous Movement는 사용자가 즉각적으로 원하는 위치로 이동하는 방식입니다.
이 방식은 사용자가 특정 위치로 순간적으로 이동할 수 있도록 합니다.
주로 포털, 텔레포트, 뷰포인트 변경 등의 상황에서 사용됩니다.
이동이 순간적이므로 물리 역학을 고려하지 않으며, 사용자 경험의 편리성을 높입니다.
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 프로퍼티는 컨트롤러와 상호 작용하는 데 사용되는 중요한 설정입니다. 이 두 프로퍼티에 대한 설명은 다음과 같습니다:
Attach Transform:
Attach Transform 프로퍼티는 XR 컨트롤러에 연결된 개체의 위치와 회전을 지정합니다.
일반적으로 이 프로퍼티는 컨트롤러의 물리적인 모양과 일치하는 개체를 나타냅니다. 예를 들어, VR 헤드셋의 컨트롤러에는 특정한 모양을 가진 컨트롤러 모델이 있습니다.
이 프로퍼티를 올바르게 설정하면, XR 컨트롤러가 이 개체에 연결되어 사용자의 손 또는 컨트롤러와 같은 개체로 보입니다.
Attach Transform은 컨트롤러의 위치 및 회전을 정확하게 추적하기 위해 사용됩니다.
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를 검색해 프리팹을 찾아 자식으로 넣어준다
그랩이 잘 되는지 확인 한다 손의 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.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();
}
}
}