가상현실(Virtual Reality)/가상현실 기초 다루기

Part02. 손 컨트롤러(Hand Presence)

danmujicat 2021. 12. 17. 18:32

1. XR 입력 방식 확인하기

- 플랫폼별 XR 입력기는 표준 컨트롤러 InputFeatureUsage 이름과 인기 XR 시스템기기의 컨트롤러에 매핑되는 방식입니다.

https://docs.unity3d.com/kr/2021.2/Manual/xr_input.html를 참고하세요

 

 

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. 컨트롤러 입력값 확인하기

 

컨트롤러의 4가지 유형

①  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폴더로 임포트한다

oculus controller.unitypackage
5.17MB

 

② 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폴더로 임포트한다

 

Oculus Hand.unitypackage
0.42MB

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