Part02. 손 컨트롤러(Hand Presence)
1. XR 입력 방식 확인하기
- 플랫폼별 XR 입력기는 표준 컨트롤러 InputFeatureUsage 이름과 인기 XR 시스템기기의 컨트롤러에 매핑되는 방식입니다.
2. XR 입력기 작동 확인하기
※ Part01에서 만든 프로젝트를 이용해도 되고, 프로젝트를 새로 생성하려면 XR 환경설정 및 하이러키창에 XR Origin을 추가 해야 합니다.
2.1 XR 입력기 입력받기
① GameObject | Create Empty 메뉴를 선택하여 하이러키 창에 GameObject 추가하고, 이름을 HandPresence로 변경한다
② 하이러키 창에서 HandPresence오브젝트를 선택하고 인스펙트 창에서 Transfrom 컴포넌트 재설정한다
③ 하이러키 창에 HandPresence 오브젝트를 선택하고, 인스펙터 창에서 [Add Component]를 클릭하여 새로운 HandPresence 스크립트를 생성한다
④ HandPresence 스크립트를 열어 코드 작성한다
HandPresence.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
public class HandPresnce : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
List<InputDevice> devices = new List<InputDevice>();
InputDeviceCharacteristics rightControllerCharacteristics = InputDeviceCharacteristics.Right | InputDeviceCharacteristics.Controller;
InputDevices.GetDevicesWithCharacteristics(rightControllerCharacteristics, devices);
foreach (var item in devices)
{
Debug.Log(item.name + item.characteristics);
}
}
// Update is called once per frame
void Update()
{
}
}
2.2 XR 입력기 출력하기 : 캔버스 활용으로
[캔버스만들기]
① GameObject | UI | Canvas 메뉴 선택하여 프로젝트 창에 Canvas를 추가하고 이름을 InputCanvas로 변경하고 선택한다.
② 인스펙터 창에서 Canvas 컴포넌트의 Render mode 속성값을 World Space 설정하고 Event Camera에 XR Origin하위의 Main Camera 설정한다.
③ 인스펙터 창에서 다음의 속성 값을 설정한다.
- Rect Transform의 Width = 900 및 Height = 300로 설정
- Rect Transform에서 Pos X, Pos Y, Pos Z = (0, 1.325, 0.8)으로 설정
- Scale (0.00135, 0.00135, 0.00135)로 설정.
[배경만들기]
①InputCanvas를 선택하고 GameObject | UI | Image 메뉴 선택하여 InputCanvas의 하위로 Image 오브젝트를 추가하고 선택한다
② 인스펙터 창에서 Rect Transform을 초기화하고 왼쪽 상단에 anchor presets 버튼 선택하고, anchor presets 창에서 Alt 키를 누른 채 stretch - stretch 를 선택한다
③ 인스펙터 창에서 Image 컴포넌트의 Color 속성값을 원하는 색상으로 변경한다.
[글자만들기]
① InputCanvas를 선택하고 GameObject | UI | Text 메뉴 선택하여 InputCanvas의 하위로 Text를 추가하고 선택한다.
② 인스펙터 창에서 다음 속성 값을 설정한다.
- Alignment를 Center Align 및 Middle Align으로 설정
- Vertical Overflow를 Overflow로 설정
- Scale을 (4, 4, 4)로 설정.
③ 인스펙터 창에서 Rect Transform을 초기화하고 왼쪽 상단에 anchor presets 버튼 선택하고, anchor presets 창에서 Alt 키를 누른 채 stretch - stretch 를 - 선택한다.
④ Text를 선택하고 인스펙터 창에서 Font style = bold, font Size=15, Color=빨강색 으로 변경한다.
① HandPresence 스크립트를 수정한다.
HandPresence.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.UI;
public class HandPresence : MonoBehaviour
{
public Text scoreText;
// Start is called before the first frame update
void Start()
{
List<InputDevice> devices = new List<InputDevice>();
InputDeviceCharacteristics rightControllerCharacteristics = InputDeviceCharacteristics.Right | InputDeviceCharacteristics.Controller;
InputDevices.GetDevicesWithCharacteristics(rightControllerCharacteristics, devices);
foreach (var item in devices)
{
Debug.Log(item.name + item.characteristics);
scoreText.text = item.name + item.characteristics;
}
}
// Update is called once per frame
void Update()
{
}
}
② HandPresence 스크립트의 인스펙터 창에서 Score Text에 InputCanvas->Text를 설정한다.
실행하기
3. 컨트롤러 입력값 확인하기
① HandPresence 스크립트를 다음과 같이 변경한다
HandPresence.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.UI;
public class HandPresence : MonoBehaviour
{
public Text scoreText;
private InputDevice targetDevice;
// Start is called before the first frame update
void Start()
{
List<InputDevice> devices = new List<InputDevice>();
InputDeviceCharacteristics rightControllerCharacteristics = InputDeviceCharacteristics.Right | InputDeviceCharacteristics.Controller;
InputDevices.GetDevicesWithCharacteristics(rightControllerCharacteristics, devices);
foreach (var item in devices)
{
Debug.Log(item.name + item.characteristics);
// scoreText.text = item.name + item.characteristics;
}
if (devices.Count > 0)
{
targetDevice = devices[0];
}
}
// Update is called once per frame
void Update()
{
if (targetDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool primaryButtonValue) && primaryButtonValue)
{
Debug.Log("Pressing Primary Button");
scoreText.text = "Pressing Primary Button";
}
if (targetDevice.TryGetFeatureValue(CommonUsages.trigger, out float triggerValue) && triggerValue > 0.1f)
{
Debug.Log("Trigger pressed " + triggerValue);
scoreText.text = "Trigger pressed " + triggerValue;
}
if (targetDevice.TryGetFeatureValue(CommonUsages.primary2DAxis, out Vector2 primary2DAxisValue) && primary2DAxisValue != Vector2.zero)
{
Debug.Log("Primary Touchpad " + primary2DAxisValue);
scoreText.text = "Primary Touchpad " + primary2DAxisValue;
}
}
}
② 실행하기
3. 오클러스퀘스트2 컨트롤러 만들기
① Oculus Controller Art (https://developer.oculus.com/downloads/package/oculus-controller-art/)사이트에서 모델을 다운로드한 후 그중에 Oculus Touch for Quest 2 폴더를 프로젝트 창에 Assets폴더로 임포트한다
② HandPresence 스크립트를 다음과 같이 수정한다.
HandPresence.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
//using UnityEngine.UI;
public class HandPresence : MonoBehaviour
{
//public Text scoreText;
private InputDevice targetDevice;
public InputDeviceCharacteristics controllerCharacteristics;
public GameObject controllerPrefabs;
private GameObject spawnedController;
void Start()
{
List<InputDevice> devices = new List<InputDevice>();
// InputDeviceCharacteristics rightControllerCharacteristics = InputDeviceCharacteristics.Right | InputDeviceCharacteristics.Controller;
// InputDevices.GetDevicesWithCharacteristics(rightControllerCharacteristics, devices);
InputDevices.GetDevicesWithCharacteristics(controllerCharacteristics, devices);
// if (devices.Count > 0)
{
targetDevice = devices[0];
spawnedController = Instantiate(controllerPrefabs, transform);
}
}
// Update is called once per frame
void Update()
{
if (targetDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool primaryButtonValue) && primaryButtonValue)
{
Debug.Log("Pressing Primary Button");
//scoreText.text = "Pressing Primary Button";
}
if (targetDevice.TryGetFeatureValue(CommonUsages.trigger, out float triggerValue) && triggerValue > 0.1f)
{
Debug.Log("Trigger pressed " + triggerValue);
//scoreText.text = "Trigger pressed " + triggerValue;
}
if (targetDevice.TryGetFeatureValue(CommonUsages.primary2DAxis, out Vector2 primary2DAxisValue) && primary2DAxisValue != Vector2.zero)
{
Debug.Log("Primary Touchpad " + primary2DAxisValue);
// scoreText.text = "Primary Touchpad " + primary2DAxisValue;
}
}
}
③ 하이러키 창에서 Hand Presence 오브젝트를 프로젝트 창에 드래그앤드롭하여 프리팹으로 만들고
하이어라키 창에서 삭제한다
④ 프로젝트 창에서 Hand Presence 프리팹을 선택하고
인스펙트 창에서 Hand Presence 컴포넌트의 Controller Chracteristics 속성값을 Right와 Controller로 선택, Controller Prefabs 속성값을 Oculus Touch for Quest 2폴더의 right_quest2_mesh.fbx(프리팹으로 변경해도 됨)로 설정하고, 이름을 Right Hand Presence으로 변경한다.
⑤ 프로젝트 창에서 Right Hand Presence 프리팹을 복사하고
인스펙트 창에서 Controller Chracteristics 속성값을 Left와 Controller로 선택, Controller Prefabs 속성값을 Oculus Touch for Quest 2폴더의 left_quest2_mesh.fbx(프리팹으로 변경해도 됨)로 설정하고, 이름을 Left Hand Presence 으로 변경한다
⑥ 하이러키 창에 XR Origin -> Left Hand Controller 를 선택하고 인스펙터 창에서 XR Cotroller 컴포넌트의 Model의 Mode Prefab 속성값에 프로젝트 창에 Left Hand Presence 프리팹을 설정한다
⑦ 하이러키 창에 XR Origin -> Right Hand Controller를 선택하고 인스펙터 창에서 XR Cotroller 컴포넌트의 Model의 Mode Prefab 속성값에 프로젝트 창에 Right Hand Presence 프리팹을설정한다
실행하기
4. 손 모델로 컨트롤러 만들기
① Oculus Hand.package를 다운로드 받아서 프로젝트 창에 Assets폴더로 임포트한다
② HandPresence 스크립트를 수정한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
public class HandPresence : MonoBehaviour
{
private InputDevice targetDevice;
public InputDeviceCharacteristics controllerCharacteristics;
public GameObject handModelPrefab;
private GameObject spawnedHandModel;
void Start()
{
List<InputDevice> devices = new List<InputDevice>();
InputDevices.GetDevicesWithCharacteristics(controllerCharacteristics, devices);
targetDevice = devices[0];
spawnedHandModel = Instantiate(handModelPrefab, transform);
}
// Update is called once per frame
void Update()
{
}
}
③ 프로젝트 창에서 Assets 폴더의 Left Hand Presence 프리팹을 선택하고 인스펙터 창에 Hand Presence 컴포넌트의 Hand Model Prefab 속성값에 프로젝트 창에서 Assets/Oculus Hands/Prefabs 폴더의 왼쪽손 모양의 프리팹 Custom Left Hand Model을 설정한다
④ 프로젝트 창에서 Assets 폴더의 Right Hand Presence 프리팹을 선택하고 인스펙터 창에 Hand Presence 컴포넌트의 Hand Model Prefab 속성값에 프로젝트 창에서 Assets/Oculus Hands/Prefabs 폴더의 오른손 모양의 프리팹 Custom right Hand Model을 설정한다
실행하기
5. 손 동작 애니메이션 만들기
5.1 손 모델 확인하기
① 프로젝트 창에서 Assets/Oculus Hands/Animation 폴더에서 l_hand_default_anim/Take 001선택하고 Oculus Hands/Models폴더에서 l_hand_skeletal_lowres를 프리뷰 창으로 드래그앤 드룹한다
② 프로젝트 창에서 Oculus Hands/Animation 폴더에서 r_hand_default_anim/Take 001선택하고 Oculus Hands/Models폴더에서 r_hand_skeletal_lowres를 선택하여 프리뷰 창으로 드래그앤 드룹 한다
5.2 애니메이션 만들기
5.2.1 오른손 애니메이션 만들기
① 프로젝트 창에서 Create | Animator Controller 메뉴를 선택하여 Assets 폴더에 New Animator Controller를 추가하고 이름을 Right Hand Animator으로 변경한다
② 프로젝트 창에 Assets/Oculus Hands/Prefabs 폴더의 Custom Right Hand Model 프리팹을 선택하고 인스펙터 창에서 Animator 컴포넌트의 Controller 속성에 프로젝트 창의 Right Hand Animator 드래그앤 드룹한다
③ 프로젝트 창에서 Assets 폴더에 Right Hand Animator를 선택하고 Window | Animation | Animator 메뉴 선택
④ Animator 창에서 Pramaters 탭에서 + 클릭하여 float 메뉴 선택하고 이름을 Grip, 값을 0.0으로 설정,
+ 클릭하여 float 메뉴선택하고 이름을 Trigger, 값을 0.0으로 설정한다
⑤ Animator 창에서 마우스 오른쪽 버튼을 클릭하여 Create State|from New Blend Tree 메뉴 선택한다
⑥ 생성된 Blend Tree 더블 클릭한 후 인스펙터 창에서 Blend Type속성을 2D Freeform Cartesian 설정, Prameters 속성에서 Grip과 Trigger 설정한다
⑦ 인스펙터 창에서 Motion 속성의 +버튼을 클릭하여 Add Motion Field 메뉴 선택하여 4개 목록 생성하고 PosX, PosY값을 (0,0), (0,1), (1,0), (1,1)로 설정한다
⑧ 프로젝트 창에서 Assets/Oculus Hands/Animations 폴더의 r_hand_default_anim의 Take 001 애니메이션클립을, r_hand_pinch_anim의 r_hand_pinch_anim과 r_hand_first_anim의 r_hand_firt 애니메이션클립을 드래그앤 드룹하여 인스펙터 창의 Motion에 각각 설정한다
⑨ Blend Tree의 빨간색 점을 움직여서 손 모양의 형태 확인한다
⑩ 프로젝트 창에서 Assets/Oculus Hands/Prefabs 폴더의 Custom Right Hand Model 프리팹을 선택한 후 인스펙트 창에서 Animator 컴포넌트에서 Controller 속성에 드래그 앤 드롭하여 설정한다
5.2.2 왼손 애니메이션
- 오른손 애니메이션 만들기와 동일하게 작성한다
5.2.3 HandPresence 스크립트 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
public class HandPresence : MonoBehaviour
{
private InputDevice targetDevice;
private GameObject spawnedHandModel;
public GameObject handModelPrefab;
public InputDeviceCharacteristics controllerCharacteristics;
private Animator handAnimator;
void Start()
{
List<InputDevice> devices = new List<InputDevice>();
InputDevices.GetDevicesWithCharacteristics(controllerCharacteristics, devices);
targetDevice = devices[0];
spawnedHandModel = Instantiate(handModelPrefab, transform);
handAnimator = spawnedHandModel.GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
UpdateHandAnimation();
}
void UpdateHandAnimation()
{
if (targetDevice.TryGetFeatureValue(CommonUsages.trigger, out float triggerValue))
{
handAnimator.SetFloat("Trigger", triggerValue);
}
else
{
handAnimator.SetFloat("Trigger", 0);
}
if (targetDevice.TryGetFeatureValue(CommonUsages.grip, out float gripValue))
{
handAnimator.SetFloat("Grip", gripValue);
}
else
{
handAnimator.SetFloat("Grip", 0);
}
}
}
실행하기
6. 컨트롤러(오클러스퀘스트2)와 손 모델을 모두 표현하기
HandPresence.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
public class HandPresence : MonoBehaviour
{
public bool showController = false;
public InputDeviceCharacteristics controllerCharacteristics;
public List<GameObject> controllerPrefabs;
public GameObject handModelPrefab;
private InputDevice targetDevice;
private GameObject spawnedController;
private GameObject spawnedHandModel;
private Animator handAnimator;
// Start is called before the first frame update
void Start()
{
TryInitialize();
}
void TryInitialize()
{
List<InputDevice> devices = new List<InputDevice>();
InputDevices.GetDevicesWithCharacteristics(controllerCharacteristics, devices);
foreach (var item in devices)
{
Debug.Log(item.name + item.characteristics);
}
if (devices.Count > 0)
{
targetDevice = devices[0];
GameObject prefab = controllerPrefabs.Find(controller => controller.name == targetDevice.name);
if (prefab)
{
spawnedController = Instantiate(prefab, transform);
}
else
{
Debug.LogError("Did not find corresponding controller model");
spawnedController = Instantiate(controllerPrefabs[0], transform);
}
spawnedHandModel = Instantiate(handModelPrefab, transform);
handAnimator = spawnedHandModel.GetComponent<Animator>();
}
}
void UpdateHandAnimation()
{
if (targetDevice.TryGetFeatureValue(CommonUsages.trigger, out float triggerValue))
{
handAnimator.SetFloat("Trigger", triggerValue);
}
else
{
handAnimator.SetFloat("Trigger", 0);
}
if (targetDevice.TryGetFeatureValue(CommonUsages.grip, out float gripValue))
{
handAnimator.SetFloat("Grip", gripValue);
}
else
{
handAnimator.SetFloat("Grip", 0);
}
}
// Update is called once per frame
void Update()
{
if (!targetDevice.isValid)
{
TryInitialize();
}
else
{
if (showController)
{
spawnedHandModel.SetActive(false);
spawnedController.SetActive(true);
}
else
{
spawnedHandModel.SetActive(true);
spawnedController.SetActive(false);
UpdateHandAnimation();
}
}
}
}