MR 입력 213: 모션 컨트롤러

참고

Mixed Reality 아카데미 자습서는 HoloLens(1세대) 및 Mixed Reality 몰입형 헤드셋을 염두에 두고 설계되었습니다. 따라서 이러한 디바이스 개발에 대한 지침을 계속 찾고 있는 개발자를 위해 이러한 자습서를 그대로 두는 것이 중요합니다. 이러한 자습서는 HoloLens 2에 사용되는 최신 도구 집합 또는 상호 작용으로 업데이트되지 않습니다. 대신 지원되는 디바이스에서 계속 작동하도록 유지 관리됩니다. HoloLens 2에 대한 새로운 자습서 시리즈가 게시되었습니다.

혼합 현실 세계의 모션 컨트롤러는 다른 수준의 대화형 작업을 추가합니다. 모션 컨트롤러를 사용하면 실제 실제 상호 작용과 유사하게 보다 자연스러운 방식으로 개체와 직접 상호 작용할 수 있으므로 앱 환경에 몰입도와 즐거움을 높일 수 있습니다.

MR 입력 213에서는 간단한 공간 그리기 환경을 만들어 모션 컨트롤러의 입력 이벤트를 살펴봅니다. 이 앱을 사용하면 사용자는 다양한 유형의 브러시와 색으로 3차원 공간에 페인트를 칠할 수 있습니다.

이 자습서에서 다루는 항목

MixedReality213 항목1 MixedReality213 항목2 MixedReality213 항목3
컨트롤러 시각화 컨트롤러 입력 이벤트 사용자 지정 컨트롤러 및 UI
Unity의 게임 모드 및 런타임에서 모션 컨트롤러 모델을 렌더링하는 방법을 알아봅니다. 다양한 유형의 단추 이벤트 및 해당 애플리케이션을 이해합니다. 컨트롤러 위에 UI 요소를 오버레이하거나 완전히 사용자 지정하는 방법을 알아봅니다.

디바이스 지원

과정 HoloLens 몰입형 헤드셋
MR 입력 213: 모션 컨트롤러 ✔️

시작하기 전에

필수 구성 요소

이 페이지에서 몰입형 헤드셋에 대한 설치 검사 목록을 참조하세요.

프로젝트 파일

참고

다운로드하기 전에 소스 코드를 살펴보려면 GitHub에서 사용할 수 있습니다.

Unity 설정

목표

  • Windows Mixed Reality 개발을 위해 Unity 최적화
  • Mixed Reality 카메라 설정
  • 설정 환경

지침

  • Unity를 시작합니다.

  • 열기를 선택합니다.

  • 데스크톱으로 이동하여 이전에 보관하지 않은 MixedReality213-master 폴더를 찾습니다.

  • 폴더 선택을 클릭합니다.

  • Unity에서 프로젝트 파일 로드를 완료하면 Unity 편집기를 볼 수 있습니다.

  • Unity에서 파일 > 빌드 설정을 선택합니다.

    MR213_BuildSettings

  • 플랫폼 목록에서 유니버설 Windows 플랫폼 선택하고 플랫폼 전환 단추를 클릭합니다.

  • 대상 디바이스를 모든 디바이스로 설정

  • Build Type(빌드 형식) 을 D3D로 설정합니다.

  • SDK를 최신 설치됨으로 설정

  • Unity C# 프로젝트 확인

    • 이렇게 하면 Unity 프로젝트를 다시 빌드하지 않고도 Visual Studio 프로젝트에서 스크립트 파일을 수정할 수 있습니다.
  • 플레이어 설정을 클릭합니다.

  • 검사기 패널에서 아래로 스크롤하여 아래쪽으로 스크롤합니다.

  • XR 설정에서 지원되는 가상 현실 검사

  • Virtual Reality SDK에서 Windows Mixed Reality 선택합니다.

    MR213_XRSettings

  • 빌드 설정 창을 닫습니다.

프로젝트 구조

이 자습서에서는 Mixed Reality 도구 키트 - Unity를 사용합니다. 이 페이지에서 릴리스를 찾을 수 있습니다.

ProjectStructure

참조에 대한 완료된 장면

  • Scenes 폴더 아래에 완료된 두 개의 Unity 장면이 있습니다.
    • MixedReality213: 단일 브러시로 완성된 장면
    • MixedReality213고급: 여러 브러시가 있는 고급 디자인의 완성된 장면

자습서에 대한 새 장면 설정

  • Unity에서 파일 > 새 장면을 클릭합니다.

  • 주 카메라방향 표시등 삭제

  • 프로젝트 패널에서 다음 프리팹을 검색하여 계층 구조 패널로 끕니다.

    • Assets/HoloToolkit/Input/Prefabs/MixedRealityCamera
    • Assets/AppPrefabs/Environment

    카메라 및 환경

  • Mixed Reality 도구 키트에는 두 개의 카메라 프리팹이 있습니다.

    • MixedRealityCamera.prefab: 카메라만
    • MixedRealityCameraParent.prefab: 카메라 + 텔레포트 + 경계
    • 이 자습서에서는 텔레포트 기능 없이 MixedRealityCamera 를 사용합니다. 이 때문에 기본 바닥이 포함된 간단한 환경 프리팹을 추가하여 사용자가 접지된 느낌을 줍니다.
    • MixedRealityCameraParent를 사용하는 텔레포트에 대한 자세한 내용은 고급 디자인 - 텔레포트 및 기관차를 참조하세요.

Skybox 설정

  • 창 > 조명 > 설정을 클릭합니다.

  • Skybox 재질 필드의 오른쪽에 있는 원을 클릭합니다.

  • '회색'을 입력하고 SkyboxGray (Assets/AppPrefabs/Support/Materials/SkyboxGray.mat)를 선택합니다.

    skybox 설정

  • 할당된 회색 그라데이션 스카이박스를 볼 수 있도록 Skybox 옵션을 선택합니다.

    skybox 옵션 설정/해제

  • MixedRealityCamera, 환경 및 회색 스카이박스가 있는 장면은 다음과 같습니다.

    MixedReality213 환경

  • 파일 > 저장 장면을 다음으로 클릭합니다.

  • 모든 이름으로 Scenes 폴더 아래에 장면 저장

1장 - 컨트롤러 시각화

목표

  • Unity의 게임 모드 및 런타임에 모션 컨트롤러 모델을 렌더링하는 방법을 알아봅니다.

Windows Mixed Reality 컨트롤러 시각화를 위한 애니메이션 컨트롤러 모델을 제공합니다. 앱에서 컨트롤러 시각화를 위해 수행할 수 있는 몇 가지 방법이 있습니다.

  • 기본값 - 수정 없이 기본 컨트롤러 사용
  • 하이브리드 - 기본 컨트롤러를 사용하지만 일부 요소를 사용자 지정하거나 UI 구성 요소를 오버레이합니다.
  • 교체 - 컨트롤러에 사용자 지정 3D 모델 사용

이 장에서는 이러한 컨트롤러 사용자 지정의 예에 대해 알아봅니다.

지침

  • 프로젝트 패널의 검색 상자에 MotionControllers를 입력합니다. Assets/HoloToolkit/Input/Prefabs/에서 찾을 수도 있습니다.
  • MotionControllers 프리팹을 계층 패널로 끕니다.
  • 계층 패널에서 MotionControllers 프리팹을 클릭합니다.

MotionControllers 프리팹

MotionControllers 프리팹에는 대체 컨트롤러 모델에 대한 슬롯을 제공하는 MotionControllerVisualizer 스크립트가 있습니다. 손이나 칼과 같은 사용자 지정 3D 모델을 할당하고 '항상 대체 왼쪽/오른쪽 모델 사용'을 검사 경우 기본 모델 대신 해당 모델이 표시됩니다. 4장에서 이 슬롯을 사용하여 컨트롤러 모델을 브러시로 바꿉니다.

MR213_ControllerVisualizer

지침

  • 검사기 패널에서 MotionControllerVisualizer 스크립트를 두 번 클릭하여 Visual Studio에서 코드를 확인합니다.

MotionControllerVisualizer 스크립트

MotionControllerVisualizerMotionControllerInfo 클래스는 기본 컨트롤러 모델을 수정하기 & 액세스하는 수단을 제공합니다. MotionControllerVisualizer 는 Unity의 InteractionSourceDetected 이벤트를 구독하고 컨트롤러 모델이 발견되면 자동으로 인스턴스화합니다.

protected override void Awake()
{
    ...
    InteractionManager.InteractionSourceDetected += InteractionManager_InteractionSourceDetected;
    InteractionManager.InteractionSourceLost += InteractionManager_InteractionSourceLost;
    ...
}

컨트롤러 모델은 glTF 사양에 따라 제공됩니다. 이 형식은 일반적인 형식을 제공하는 동시에 3D 자산을 전송하고 압축을 푸는 프로세스를 개선하기 위해 만들어졌습니다. 이 경우 사용자의 환경을 최대한 원활하게 만들기 위해 런타임에 컨트롤러 모델을 검색하고 로드해야 하며, 사용자가 사용할 수 있는 동작 컨트롤러 버전이 보장되지는 않습니다. 이 과정에서는 Mixed Reality 도구 키트를 통해 Khronos 그룹의 UnityGLTF 프로젝트 버전을 사용합니다.

컨트롤러가 전달되면 스크립트는 MotionControllerInfo 를 사용하여 특정 컨트롤러 요소에 대한 변환을 찾아서 올바르게 배치할 수 있습니다.

이후 챕터에서는 이러한 스크립트를 사용하여 컨트롤러에 UI 요소를 연결하는 방법을 알아봅니다.

일부 스크립트에서는 #if ! UNITY_EDITOR 또는 UNITY_WSA. 이러한 코드 블록은 Windows에 배포할 때 UWP 런타임에서만 실행됩니다. Unity 편집기와 UWP 앱 런타임에서 사용하는 API 집합이 다르기 때문입니다.

  • 장면을 저장하고 재생 단추를 클릭합니다.

헤드셋에서 모션 컨트롤러를 사용하여 장면을 볼 수 있습니다. 단추 클릭, 엄지스틱 이동 및 터치 패드 터치 강조 표시에 대한 자세한 애니메이션을 볼 수 있습니다.

MR213_Controller 시각화 기본값

2장 - 컨트롤러에 UI 요소 연결

목표

  • 모션 컨트롤러의 요소에 대해 알아보기
  • 컨트롤러의 특정 부분에 개체를 연결하는 방법 알아보기

이 장에서는 사용자가 언제든지 쉽게 액세스하고 조작할 수 있는 컨트롤러에 사용자 인터페이스 요소를 추가하는 방법을 알아봅니다. 터치 패드 입력을 사용하여 간단한 색 선택기 UI를 추가하는 방법도 알아봅니다.

지침

  • 프로젝트 패널에서 MotionControllerInfo 스크립트를 검색합니다.
  • 검색 결과에서 MotionControllerInfo 스크립트를 두 번 클릭하여 Visual Studio에서 코드를 확인합니다.

MotionControllerInfo 스크립트

첫 번째 단계는 UI를 연결할 컨트롤러의 요소를 선택하는 것입니다. 이러한 요소는 MotionControllerInfo.csControllerElementEnum에 정의됩니다.

MR213 MotionControllerElements

  • Home
  • 메뉴
  • 파악
  • 엄지스틱
  • Select
  • 터치패드
  • 포인팅 포즈 – 이 요소는 전방 방향을 가리키는 컨트롤러의 끝을 나타냅니다.

지침

  • 프로젝트 패널에서 AttachToController 스크립트를 검색합니다.
  • 검색 결과에서 AttachToController 스크립트를 두 번 클릭하여 Visual Studio에서 코드를 확인합니다.

AttachToController 스크립트

AttachToController 스크립트는 지정된 컨트롤러 전달 및 요소에 개체를 연결하는 간단한 방법을 제공합니다.

AttachElementToController()에서

  • MotionControllerInfo.Handedness를 사용하여 손 확인
  • MotionControllerInfo.TryGetElement()를 사용하여 컨트롤러의 특정 요소 가져오기
  • 컨트롤러 모델에서 요소의 변환을 검색한 후 그 아래에 있는 개체를 부모로 설정하고 개체의 로컬 위치 & 회전을 0으로 설정합니다.
public MotionControllerInfo.ControllerElementEnum Element { get { return element; } }

private void AttachElementToController(MotionControllerInfo newController)
{
     if (!IsAttached && newController.Handedness == handedness)
     {
          if (!newController.TryGetElement(element, out elementTransform))
          {
               Debug.LogError("Unable to find element of type " + element + " under controller " + newController.ControllerParent.name + "; not attaching.");
               return;
          }

          controller = newController;

          SetChildrenActive(true);

          // Parent ourselves under the element and set our offsets
          transform.parent = elementTransform;
          transform.localPosition = positionOffset;
          transform.localEulerAngles = rotationOffset;
          if (setScaleOnAttach)
          {
               transform.localScale = scale;
          }

          // Announce that we're attached
          OnAttachToController();
          IsAttached = true;
     }
}

AttachToController 스크립트를 사용하는 가장 간단한 방법은 ColorPickerWheel의 경우와 같이 상속하는 것입니다. 컨트롤러가 검색/연결이 끊어지면 OnAttachToControllerOnDetachFromController 함수를 재정의하여 설정/분석 작업을 수행합니다.

지침

  • 프로젝트 패널에서 ColorPickerWheel 검색 상자를 입력합니다. 자산/AppPrefabs/에서 찾을 수도 있습니다.
  • ColorPickerWheel 프리팹을 계층 패널로 끌어옵니다.
  • 계층 패널에서 ColorPickerWheel 프리팹 클릭합니다.
  • 검사기 패널에서 ColorPickerWheel 스크립트를 두 번 클릭하여 Visual Studio에서 코드를 확인합니다.

ColorPickerWheel 프리팹

ColorPickerWheel 스크립트

ColorPickerWheelAttachToController를 상속하므로 검사기 패널에 HandednessElement가 표시됩니다. 왼쪽 컨트롤러의 터치 패드 요소에 UI를 연결합니다.

ColorPickerWheel 스크립트

ColorPickerWheelOnAttachToControllerOnDetachFromController 를 재정의하여 터치 패드 입력을 사용하여 색 선택을 위해 다음 챕터에서 사용할 입력 이벤트를 구독합니다.

public class ColorPickerWheel : AttachToController, IPointerTarget
{
    protected override void OnAttachToController()
    {
        // Subscribe to input now that we're parented under the controller
        InteractionManager.InteractionSourceUpdated += InteractionSourceUpdated;
    }

    protected override void OnDetachFromController()
    {
        Visible = false;

        // Unsubscribe from input now that we've detached from the controller
        InteractionManager.InteractionSourceUpdated -= InteractionSourceUpdated;
    }
    ...
}
  • 장면을 저장하고 재생 단추를 클릭합니다.

컨트롤러에 개체를 연결하기 위한 대체 방법

스크립트가 AttachToController 에서 상속되고 OnAttachToController를 재정의하는 것이 좋습니다. 그러나 이것이 항상 가능한 것은 아닙니다. 대안은 독립 실행형 구성 요소로 사용하는 것입니다. 이는 스크립트를 리팩터링하지 않고 컨트롤러에 기존 프리팹을 연결하려는 경우에 유용할 수 있습니다. 설정을 수행하기 전에 클래스에서 IsAttached가 true로 설정될 때까지 기다리면 됩니다. 이 작업을 수행하는 가장 간단한 방법은 'Start'에 코루틴을 사용하는 것입니다.

private IEnumerator Start() {
    AttachToController attach = gameObject.GetComponent<AttachToController>();

    while (!attach.IsAttached) {
        yield return null;
    }

    // Perform setup here
}

3장 - 터치 패드 입력 작업

목표

  • 터치 패드 입력 데이터 이벤트를 가져오는 방법 알아보기
  • 앱 환경에 터치 패드 축 위치 정보를 사용하는 방법 알아보기

지침

  • 계층 구조 패널에서 ColorPickerWheel을 클릭합니다.
  • 검사기 패널의 애니메이터에서 ColorPickerWheelController를 두 번 클릭합니다.
  • Animator 탭이 열린 것을 볼 수 있습니다.

Unity의 애니메이션 컨트롤러를 사용하여 UI 표시/숨기기

애니메이션으로 ColorPickerWheel UI를 표시하고 숨기기 위해 Unity의 애니메이션 시스템을 사용합니다. ColorPickerWheelVisible 속성을 true 또는 false로 설정하면 애니메이션 트리거 표시숨기기가 트리거됩니다. 매개 변수 표시숨기기ColorPickerWheelController 애니메이션 컨트롤러에 정의되어 있습니다.

Unity 애니메이션 컨트롤러

지침

  • 계층 구조 패널에서 ColorPickerWheel 프리팹을 선택합니다.
  • 검사기 패널에서 ColorPickerWheel 스크립트를 두 번 클릭하여 Visual Studio에서 코드를 확인합니다.

ColorPickerWheel 스크립트

ColorPickerWheel 은 Unity의 InteractionSourceUpdated 이벤트를 구독하여 터치 패드 이벤트를 수신 대기합니다.

InteractionSourceUpdated()에서 스크립트는 먼저 다음을 확인합니다.

  • 는 실제로 터치 패드 이벤트(obj.state)입니다.touchpadTouched)
  • 는 왼쪽 컨트롤러(obj.state.source)에서 시작됩니다.손)

둘 다 true이면 터치 패드 위치(obj.state.touchpadPosition)은 selectorPosition에 할당됩니다.

private void InteractionSourceUpdated(InteractionSourceUpdatedEventArgs obj)
{
    if (obj.state.source.handedness == handedness && obj.state.touchpadTouched)
    {
        Visible = true;
        selectorPosition = obj.state.touchpadPosition;
    }
}

Update()에서는 표시 속성에 따라 색 선택기의 애니메이터 구성 요소에서 애니메이션 표시 및 숨기기 트리거를 트리거합니다.

if (visible != visibleLastFrame)
{
    if (visible)
    {
        animator.SetTrigger("Show");
    }
    else
    {
        animator.SetTrigger("Hide");
    }
}

Update()에서 selectorPosition은 UV 위치를 반환하는 색상 휠의 메시 충돌기에서 광선을 캐스팅하는 데 사용됩니다. 그런 다음 이 위치를 사용하여 색상 휠 텍스처의 픽셀 좌표 및 색 값을 찾을 수 있습니다. 이 값은 SelectedColor 속성을 통해 다른 스크립트에서 액세스할 수 있습니다.

컬러 피커 휠 레이캐스팅

...
    // Clamp selector position to a radius of 1
    Vector3 localPosition = new Vector3(selectorPosition.x * inputScale, 0.15f, selectorPosition.y * inputScale);
    if (localPosition.magnitude > 1)
    {
        localPosition = localPosition.normalized;
    }
    selectorTransform.localPosition = localPosition;

    // Raycast the wheel mesh and get its UV coordinates
    Vector3 raycastStart = selectorTransform.position + selectorTransform.up * 0.15f;
    RaycastHit hit;
    Debug.DrawLine(raycastStart, raycastStart - (selectorTransform.up * 0.25f));

    if (Physics.Raycast(raycastStart, -selectorTransform.up, out hit, 0.25f, 1 << colorWheelObject.layer, QueryTriggerInteraction.Ignore))
    {
        // Get pixel from the color wheel texture using UV coordinates
        Vector2 uv = hit.textureCoord;
        int pixelX = Mathf.FloorToInt(colorWheelTexture.width * uv.x);
        int pixelY = Mathf.FloorToInt(colorWheelTexture.height * uv.y);
        selectedColor = colorWheelTexture.GetPixel(pixelX, pixelY);
        selectedColor.a = 1f;
    }
    // Set the selector's color and blend it with white to make it visible on top of the wheel
    selectorRenderer.material.color = Color.Lerp (selectedColor, Color.white, 0.5f);
}

4장 - 컨트롤러 모델 재정의

목표

  • 사용자 지정 3D 모델로 컨트롤러 모델을 재정의하는 방법을 알아봅니다.

MR213_BrushToolOverride

지침

  • 계층 구조 패널에서 MotionControllers를 클릭합니다.
  • 대체 오른쪽 컨트롤러 필드의 오른쪽에 있는 원을 클릭합니다.
  • 'BrushController'를 입력하고 결과에서 프리팹을 선택합니다. 자산/AppPrefabs/BrushController에서 찾을 수 있습니다.
  • 항상 대체 올바른 모델 사용 확인

MR213_BrushToolOverrideSlot

BrushController 프리팹은 계층 구조 패널에 포함될 필요가 없습니다. 그러나 자식 구성 요소를 검사:

  • 프로젝트 패널에서 BrushController를 입력하고 BrushController 프리팹을 계층 구조 패널로 끌어옵니다.

MR213_BrushTool_Prefab2

BrushController에서 구성 요소를 찾을 수 있습니다. 변환을 사용하여 그리기 선을 시작/중지합니다.

  • 계층 구조 패널에서 BrushController를 삭제합니다.
  • 장면을 저장하고 재생 단추를 클릭합니다. 브러시 모델이 오른쪽 모션 컨트롤러를 대체한 것을 볼 수 있습니다.

5장 - 입력 선택을 사용하여 그리기

목표

  • Select 단추 이벤트를 사용하여 선 그리기를 시작하고 중지하는 방법을 알아봅니다.

지침

  • 프로젝트 패널에서 BrushController 프리팹을 검색합니다.
  • 검사기 패널에서 BrushController 스크립트를 두 번 클릭하여 Visual Studio에서 코드를 확인합니다.

BrushController 스크립트

BrushController 는 InteractionManager의 InteractionSourcePressedInteractionSourceReleased 이벤트를 구독합니다. InteractionSourcePressed 이벤트가 트리거되면 브러시의 Draw 속성이 true로 설정됩니다. InteractionSourceReleased 이벤트가 트리거되면 브러시의 Draw 속성이 false로 설정됩니다.

private void InteractionSourcePressed(InteractionSourcePressedEventArgs obj)
{
    if (obj.state.source.handedness == InteractionSourceHandedness.Right && obj.pressType == InteractionSourcePressType.Select)
    {
        Draw = true;
    }
}

private void InteractionSourceReleased(InteractionSourceReleasedEventArgs obj)
{
    if (obj.state.source.handedness == InteractionSourceHandedness.Right && obj.pressType == InteractionSourcePressType.Select)
    {
        Draw = false;
    }
}

그리기는 true로 설정되어 있지만 브러시는 인스턴스화된 Unity LineRenderer에서 포인트를 생성합니다. 이 프리팹에 대한 참조는 브러시의 스트로크 프리팹 필드에 유지됩니다.

private IEnumerator DrawOverTime()
{
    // Get the position of the tip
    Vector3 lastPointPosition = tip.position;

    ...

    // Create a new brush stroke
    GameObject newStroke = Instantiate(strokePrefab);
    LineRenderer line = newStroke.GetComponent<LineRenderer>();
    newStroke.transform.position = startPosition;
    line.SetPosition(0, tip.position);
    float initialWidth = line.widthMultiplier;

    // Generate points in an instantiated Unity LineRenderer
    while (draw)
    {
        // Move the last point to the draw point position
        line.SetPosition(line.positionCount - 1, tip.position);
        line.material.color = colorPicker.SelectedColor;
        brushRenderer.material.color = colorPicker.SelectedColor;
        lastPointAddedTime = Time.unscaledTime;
        // Adjust the width between 1x and 2x width based on strength of trigger pull
        line.widthMultiplier = Mathf.Lerp(initialWidth, initialWidth * 2, width);

        if (Vector3.Distance(lastPointPosition, tip.position) > minPositionDelta || Time.unscaledTime > lastPointAddedTime + maxTimeDelta)
        {
            // Spawn a new point
            lastPointAddedTime = Time.unscaledTime;
            lastPointPosition = tip.position;
            line.positionCount += 1;
            line.SetPosition(line.positionCount - 1, lastPointPosition);
        }
        yield return null;
    }
}

색상 선택기 휠 UI에서 현재 선택한 색을 사용하려면 BrushControllerColorPickerWheel 개체에 대한 참조가 있어야 합니다. BrushController 프리팹은 런타임에 대체 컨트롤러로 인스턴스화되므로 장면의 개체에 대한 모든 참조는 런타임에 설정해야 합니다. 이 경우 GameObject.FindObjectOfType 을 사용하여 ColorPickerWheel을 찾습니다.

private void OnEnable()
{
    // Locate the ColorPickerWheel
    colorPicker = FindObjectOfType<ColorPickerWheel>();

    // Assign currently selected color to the brush’s material color
    brushRenderer.material.color = colorPicker.SelectedColor;
    ...
}
  • 장면을 저장하고 재생 단추를 클릭합니다. 오른쪽 컨트롤러의 선택 단추를 사용하여 선과 페인트를 그릴 수 있습니다.

6장 - 입력 선택을 사용하여 개체 생성

목표

  • 선택 및 파악 단추 입력 이벤트를 사용하는 방법 알아보기
  • 개체를 인스턴스화하는 방법 알아보기

지침

  • 프로젝트 패널의 검색 상자에 ObjectSpawner를 입력합니다. Assets/AppPrefabs/에서 찾을 수도 있습니다.

  • ObjectSpawner 프리팹을 계층 구조 패널로 끕니다.

  • 계층 구조 패널에서 ObjectSpawner를 클릭합니다.

  • ObjectSpawner 에는 Color Source라는 필드가 있습니다.

  • 계층 구조 패널에서 ColorPickerWheel 참조를 이 필드로 끕니다.

    개체 생성자 검사기

  • 계층 구조 패널에서 ObjectSpawner 프리팹을 클릭합니다.

  • 검사기 패널에서 ObjectSpawner 스크립트를 두 번 클릭하여 Visual Studio에서 코드를 확인합니다.

ObjectSpawner 스크립트

ObjectSpawner는 기본 메시(큐브, 구, 원통)의 복사본을 공간에 인스턴스화합니다. InteractionSourcePressed가 검색되면 손수와 InteractionSourcePressType.Grasp 또는 InteractionSourcePressType.Select 이벤트인지 확인합니다.

Grasp 이벤트의 경우 현재 메시 형식(구, 큐브, 실린더)의 인덱스를 증가합니다.

private void InteractionSourcePressed(InteractionSourcePressedEventArgs obj)
{
    // Check handedness, see if it is left controller
    if (obj.state.source.handedness == handedness)
    {
        switch (obj.pressType)
        {
            // If it is Select button event, spawn object
            case InteractionSourcePressType.Select:
                if (state == StateEnum.Idle)
                {
                    // We've pressed the grasp - enter spawning state
                    state = StateEnum.Spawning;
                    SpawnObject();
                }
                break;

            // If it is Grasp button event
            case InteractionSourcePressType.Grasp:

                // Increment the index of current mesh type (sphere, cube, cylinder)
                meshIndex++;
                if (meshIndex >= NumAvailableMeshes)
                {
                    meshIndex = 0;
                }
                break;

            default:
                break;
        }
    }
}

Select 이벤트의 경우 SpawnObject()에서 새 개체가 인스턴스화되고, 부모가 되지 않고, 월드로 릴리스됩니다.

private void SpawnObject()
{
    // Instantiate the spawned object
    GameObject newObject = Instantiate(displayObject.gameObject, spawnParent);
    // Detach the newly spawned object
    newObject.transform.parent = null;
    // Reset the scale transform to 1
    scaleParent.localScale = Vector3.one;
    // Set its material color so its material gets instantiated
    newObject.GetComponent<Renderer>().material.color = colorSource.SelectedColor;
}

ObjectSpawnerColorPickerWheel을 사용하여 표시 개체 재질의 색을 설정합니다. 생성된 개체는 이 재질의 instance 주어 색을 유지합니다.

  • 장면을 저장하고 재생 단추를 클릭합니다.

확인 단추를 사용하여 개체를 변경하고 선택 단추를 사용하여 개체를 생성할 수 있습니다.

Mixed Reality Portal에 앱 빌드 및 배포

  • Unity에서 파일 > 빌드 설정을 선택합니다.
  • 열려 있는 장면 추가를 클릭하여 빌드의 장면에 현재 장면을 추가합니다.
  • 빌드를 클릭한 다음
  • "App"이라는 새 폴더 를 만듭니다.
  • App 폴더를 한 번 클릭합니다.
  • 폴더 선택을 클릭합니다.
  • Unity가 완료되면 파일 탐색기 창이 나타납니다.
  • App 폴더를 엽니다.
  • YourSceneName.sln Visual Studio Solution 파일을 두 번 클릭합니다.
  • Visual Studio의 위쪽 도구 모음을 사용하여 대상을 디버그에서 릴리스 로, ARM에서 X64로 변경합니다.
  • 디바이스 단추 옆에 있는 드롭다운 화살표를 클릭하고 로컬 컴퓨터를 선택합니다.
  • 메뉴에서 디버그 -> 디버깅하지 않고 시작을 클릭하거나 Ctrl + F5를 누릅니다.

이제 앱이 빌드되고 Mixed Reality Portal에 설치됩니다. Mixed Reality Portal의 시작 메뉴를 통해 다시 시작할 수 있습니다.

고급 디자인 - 방사형 레이아웃이 있는 브러시 도구

MixedReality213 Main

이 챕터에서는 기본 모션 컨트롤러 모델을 사용자 지정 브러시 도구 컬렉션으로 바꾸는 방법을 알아봅니다. 참조의 경우 Scenes 폴더에서 완료된 장면 MixedReality213Advanced를 찾을 수 있습니다.

지침

  • 프로젝트 패널의 검색 상자에 BrushSelector를 입력합니다. Assets/AppPrefabs/에서 찾을 수도 있습니다.

  • BrushSelector 프리팹을 계층 구조 패널로 끕니다.

  • organization 경우 Brushes라는 빈 GameObject를 만듭니다.

  • 프로젝트 패널에서 다음 프리팹을 브러시로 끌어옵니다.

    • 자산/AppPrefabs/BrushFat
    • Assets/AppPrefabs/BrushThin
    • Assets/AppPrefabs/Eraser
    • Assets/AppPrefabs/MarkerFat
    • Assets/AppPrefabs/MarkerThin
    • 자산/AppPrefabs/Pencil

    브러시

  • 계층 패널에서 MotionControllers 프리팹을 클릭합니다.

  • 검사기 패널의 동작 컨트롤러 시각화 도우미에서 항상 대체 오른쪽 모델 사용 선택 취소

  • 계층 구조 패널에서 BrushSelector를 클릭합니다.

  • BrushSelector에는 ColorPicker라는 필드가 있습니다.

  • 계층 구조 패널에서 ColorPickerWheel검사기 패널의 ColorPicker 필드로 끕니다.

    ColorPickerWheel을 브러시 선택기로 할당

  • 계층 구조 패널의 BrushSelector 프리팹에서 Menu 개체를 선택합니다.

  • 검사기 패널의 LineObjectCollection 구성 요소 아래에서 개체 배열 드롭다운을 엽니다. 6개의 빈 슬롯이 표시됩니다.

  • 계층 구조 패널에서 Brushes GameObject 아래에 부모로 지정된 각 프리팹을 순서대로 이러한 슬롯으로 끕니다. (프로젝트 폴더의 프리팹이 아니라 장면에서 프리팹을 끌어와야 합니다.)

브러시 선택기

BrushSelector 프리팹

BrushSelectorAttachToController를 상속하므로 검사기 패널에 HandednessElement 옵션을 표시합니다. 오른쪽포인팅 포즈를 선택하여 브러시 도구를 전방 방향으로 오른쪽 컨트롤러에 연결했습니다.

BrushSelector는 다음 두 유틸리티를 사용합니다.

  • 타원: 타원 셰이프를 따라 공간의 점을 생성하는 데 사용됩니다.
  • LineObjectCollection: 모든 Line 클래스(예: Ellipse)에서 생성된 점을 사용하여 개체를 배포합니다. 이것은 타원 모양을 따라 브러시를 배치하는 데 사용할 것입니다.

결합된 경우 이러한 유틸리티를 사용하여 방사형 메뉴를 만들 수 있습니다.

LineObjectCollection 스크립트

LineObjectCollection 에는 해당 선을 따라 분산된 개체의 크기, 위치 및 회전에 대한 컨트롤이 있습니다. 브러시 선택기 같은 방사형 메뉴를 만드는 데 유용합니다. 가운데에서 선택한 위치에 접근할 때 아무 것도 확장하지 않는 브러시의 모양을 만들기 위해 ObjectScale 곡선은 가운데에서 정점에 도달하고 가장자리에서 테이퍼를 제거합니다.

BrushSelector 스크립트

BrushSelector의 경우 절차 애니메이션을 사용하도록 선택했습니다. 먼저 브러시 모델은 LineObjectCollection 스크립트에 의해 타원에 분산됩니다. 그런 다음 각 브러시는 선택 항목에 따라 변경되는 DisplayMode 값에 따라 사용자의 손에서 위치를 유지 관리합니다. 사용자가 브러시를 선택할 때 브러시 위치 전환이 중단될 가능성이 높기 때문에 절차적 접근 방식을 선택했습니다. Mecanim 애니메이션은 중단을 정상적으로 처리할 수 있지만 간단한 Lerp 작업보다 더 복잡한 경향이 있습니다.

BrushSelector 는 둘의 조합을 사용합니다. 터치 패드 입력이 감지되면 브러시 옵션이 표시되고 방사형 메뉴를 따라 확장됩니다. 시간 제한 기간(사용자가 선택했음을 나타낸 것)이 지나면 브러시 옵션이 다시 축소되어 선택한 브러시만 남습니다.

터치 패드 입력 시각화

컨트롤러 모델이 완전히 교체된 경우에도 원래 모델 입력에 입력을 표시하는 것이 유용할 수 있습니다. 이렇게 하면 사용자의 작업을 실제로 접지할 수 있습니다. BrushSelector의 경우 입력이 수신될 때 터치 패드를 간략하게 표시하도록 선택했습니다. 이 작업은 컨트롤러에서 터치 패드 요소를 검색하고, 재질을 사용자 지정 재질로 바꾼 다음, 터치 패드 입력을 마지막으로 받은 시간을 기준으로 해당 재질의 색에 그라데이션을 적용하여 수행되었습니다.

protected override void OnAttachToController()
{
    // Turn off the default controller's renderers
    controller.SetRenderersVisible(false);

    // Get the touchpad and assign our custom material to it
    Transform touchpad;
    if (controller.TryGetElement(MotionControllerInfo.ControllerElementEnum.Touchpad, out touchpad))
    {
        touchpadRenderer = touchpad.GetComponentInChildren<MeshRenderer>();
        originalTouchpadMaterial = touchpadRenderer.material;
        touchpadRenderer.material = touchpadMaterial;
        touchpadRenderer.enabled = true;
    }

    // Subscribe to input now that we're parented under the controller
    InteractionManager.InteractionSourceUpdated += InteractionSourceUpdated;
}

private void Update()
{
    ...
    // Update our touchpad material
    Color glowColor = touchpadColor.Evaluate((Time.unscaledTime - touchpadTouchTime) / touchpadGlowLossTime);
    touchpadMaterial.SetColor("_EmissionColor", glowColor);
    touchpadMaterial.SetColor("_Color", glowColor);
    ...
}

터치 패드 입력을 사용하여 브러시 도구 선택

브러시 선택기가 터치 패드의 누름 입력을 감지하면 입력의 위치를 확인하여 왼쪽 또는 오른쪽에 있는지 확인합니다.

selectPressedAmount를 사용하여 스트로크 두께

InteractionSourcePressed()InteractionSourcePressType.Select 이벤트 대신 selectPressedAmount를 통해 누른 금액의 아날로그 값을 가져올 수 있습니다. 이 값은 InteractionSourceUpdated()에서 검색할 수 있습니다.

private void InteractionSourceUpdated(InteractionSourceUpdatedEventArgs obj)
{
    if (obj.state.source.handedness == handedness)
    {
        if (obj.state.touchpadPressed)
        {
            // Check which side we clicked
            if (obj.state.touchpadPosition.x < 0)
            {
                currentAction = SwipeEnum.Left;
            }
            else
            {
                currentAction = SwipeEnum.Right;
            }

            // Ping the touchpad material so it gets bright
            touchpadTouchTime = Time.unscaledTime;
        }

        if (activeBrush != null)
        {
            // If the pressed amount is greater than our threshold, draw
            if (obj.state.selectPressedAmount >= selectPressedDrawThreshold)
            {
                activeBrush.Draw = true;
                activeBrush.Width = ProcessSelectPressedAmount(obj.state.selectPressedAmount);
            }
            else
            {
                // Otherwise, stop drawing
                activeBrush.Draw = false;
                selectPressedSmooth = 0f;
            }
        }
    }
}

지우개 스크립트

지우개는 기본 Brush의 DrawOverTime() 함수를 재정의하는 특수한 유형의 브러시입니다. 그리기는 true이지만 지우개는 팁이 기존 브러시 스트로크와 교차하는지 확인합니다. 이 경우 줄어드는 큐에 추가되고 삭제됩니다.

고급 디자인 - 텔레포트 및 기관차

사용자가 엄지스틱을 사용하여 텔레포트로 장면 주위를 이동할 수 있도록 하려면 MixedRealityCamera 대신 MixedRealityCameraParent를 사용합니다. 또한 InputManagerDefaultCursor를 추가해야 합니다. MixedRealityCameraParent는 이미 MotionControllers경계를 자식 구성 요소로 포함하므로 기존 MotionControllers환경 프리팹을 제거해야 합니다.

지침

  • 계층 구조 패널에서 MixedRealityCamera, EnvironmentMotionControllers를 삭제합니다.

  • 프로젝트 패널에서 다음 프리팹을 검색하여 계층 구조 패널로 끕니다.

    • Assets/AppPrefabs/Input/Prefabs/MixedRealityCameraParent
    • Assets/AppPrefabs/Input/Prefabs/InputManager
    • Assets/AppPrefabs/Input/Prefabs/Cursor/DefaultCursor

    Mixed Reality 카메라 부모

  • 계층 구조 패널에서 입력 관리자를 클릭합니다.

  • 검사기 패널에서 단순 단일 포인터 선택기 섹션까지 아래로 스크롤합니다.

  • 계층 구조 패널에서 DefaultCursor를 커서 필드로 끕니다.

    DefaultCursor 할당

  • 장면을 저장하고 재생 단추를 클릭합니다. 엄지스틱을 사용하여 왼쪽/오른쪽 또는 텔레포트를 회전할 수 있습니다.

이 자습서의 끝입니다. 다음에 대해 알아보았습니다.

  • Unity의 게임 모드 및 런타임에서 모션 컨트롤러 모델을 작업하는 방법.
  • 다양한 유형의 단추 이벤트 및 해당 애플리케이션을 사용하는 방법입니다.
  • 컨트롤러 위에 UI 요소를 오버레이하거나 완전히 사용자 지정하는 방법입니다.

이제 모션 컨트롤러를 사용하여 사용자 고유의 몰입형 환경을 만들 준비가 되었습니다!

완료된 장면

  • Unity의 프로젝트 패널에서 Scenes 폴더를 클릭합니다.
  • 두 개의 Unity 장면 MixedReality213MixedReality213Advanced를 찾을 수 있습니다.
    • MixedReality213: 단일 브러시로 완성된 장면
    • MixedReality213Advanced: 선택 단추의 누름 양 예제가 있는 여러 브러시가 있는 완성된 장면

추가 정보