본문으로 건너뛰기

VerseGrip 위치 제어 튜토리얼

이 튜토리얼에서는 빈번한 업데이트를 위해 Unity의 메인 스레드를 거치지 않고, VerseGrip의 회전 기능을 활용하여 ‘ Inverse3 ’ 장치의 커서 위치를 직접 제어하는 방법을 설명합니다.

서문​

입문 가이드 앞서 설명한 방법은 표준 재생 빈도(60Hz)에서 작동하지만, 이 튜토리얼에서는 VerseGrip 장치의 회전을 기반으로 더 높은 빈도(1~4kHz)로 Inverse3 커서 위치를 동적으로 조정하는 것을 목표로 합니다. 이는 햅틱 스레드에 의해 트리거되는 DeviceStateChanged 이벤트를 활용함으로써 실현됩니다.

장면 설정​

먼저, Haptic Rig를 생성합니다: GameObject > Haply > Haptic Rig (one hand). 자세한 내용은 입문 가이드를 참조하십시오.

VerseGripPositionControl 컴포넌트​

새로운 C# 스크립트 VerseGripPositionControl.cs 을(를) 작성하고,햅틱 오리진 게임 오브젝트에 부착합니다. VerseGripPositionControl 클래스 내에서 다음 속성을 정의합니다:

public Inverse3 inverse3;
public VerseGrip verseGrip;

[Range(0, 1)]
public float speed = 0.5f;

[Range(0, 0.2f)]
public float movementLimitRadius = 0.2f;

private Vector3 _targetPosition;
  • inverse3: 인스펙터에서 설정하는,Inverse3 장치에 대한 참조입니다.
  • verseGrip: 커서 제어에 사용되는 VerseGrip 장치에 대한 참조입니다.
  • 속도: 커서가 이동하는 속도입니다.
  • movementLimitRadius: 커서가 초기 위치에서 이동할 수 있는 최대 거리입니다.
  • _targetPosition: 커서가 이동할 목표 위치입니다.

Awake 메서드​

Awake 메서드에서는,Ready 이벤트를 등록하고,Inverse3 장치의 작업 공간 중심을 기준으로 커서의 목표 위치를 초기화합니다:

private void Awake()
{
inverse3.Ready.AddListener((inverse3Controller, args) =>
{
_targetPosition = inverse3Controller.WorkspaceCenterLocalPosition;
});
}

장치 준비가 완료되기 전에는 장치의 좌우 구성(핸드니스)이 아직 결정되지 않았기 때문에 작업 공간의 중심을 사용할 수 없습니다.

VerseGrip의 회전 및 버튼 입력을 바탕으로 커서의 목표 위치를 계산하기 위해,OnDeviceStateChanged 메서드를 구현합니다:

private void OnDeviceStateChanged(object sender, VerseGripEventArgs args)
{
var verseGrip = args.DeviceController;
// Calculate the direction based on the VerseGrip's rotation
var direction = verseGrip.Orientation * Vector3.forward;

// Check if the VerseGrip button is pressed down
if (verseGrip.GetButtonDown())
{
// Initialize target position
_targetPosition = inverse3.CursorLocalPosition;
}

// Check if the VerseGrip button is being held down
if (verseGrip.GetButton())
{
// Move the target position toward the grip direction
_targetPosition += direction * (0.0001f * speed);

// Clamp the target position within the movement limit radius
var workspaceCenter = inverse3.WorkspaceCenterLocalPosition;
_targetPosition = Vector3.ClampMagnitude(_targetPosition - workspaceCenter, movementLimitRadius)
+ workspaceCenter;
}
// Move cursor to new position
inverse3.SetCursorLocalPosition(_targetPosition);
}

OnEnable 그리고 OnDisable 에서 DeviceStateChanged 이벤트를 등록하거나 등록을 취소합니다.

/// Subscribes to the DeviceStateChanged event.
private void OnEnable()
{
verseGrip.DeviceStateChanged += OnDeviceStateChanged;
}

/// Unsubscribes from the DeviceStateChanged event.
private void OnDisable()
{
verseGrip.DeviceStateChanged -= OnDeviceStateChanged;
}

게임 플레이​

  • Inverse3 장치를 고정하고, 움직일 수 있는 충분한 공간이 있는지 확인합니다.
  • '재생 모드'로 들어가서, 'Inverse3 '에 커서를 위치시킨 상태로 유지합니다.
  • VerseGrip을 회전시켜 Unity 씬에서 커서의 움직임을 확인합니다. 이 움직임은 VerseGrip의 방향과 직접적으로 연동됩니다.
  • VerseGrip의 버튼을 누르면, VerseGrip의 회전 방향으로 ‘ Inverse3 ’ 커서를 움직일 수 있어 실시간 제어를 체험할 수 있습니다.

versegrip으로 공을 움직이는 모습

이 이미지는 이해를 돕기 위해 전방 축을 강조 표시한 커서 모델을 보여줍니다. 커서 모델 사용자 정의에 대한 자세한 내용은커서 문서를 참조하십시오.

소스 파일​

이 예제에서 사용된 마지막 장면과 모든 관련 파일은 Unity 패키지 관리자의 ‘Tutorials’ 샘플에서 가져올 수 있습니다.

VerseGripPositionControl.cs​

/*
* Copyright 2024 Haply Robotics Inc. All rights reserved.
*/

using Haply.Inverse.DeviceControllers;
using Haply.Inverse.DeviceData;
using UnityEngine;

namespace Haply.Samples.Tutorials._6_VerseGripPositionControl
{
/// <summary>
/// Demonstrates how to control the device cursor position using the VerseGrip.
/// </summary>
public class VerseGripPositionControl : MonoBehaviour
{
public Inverse3Controller inverse3;
public VerseGripController verseGrip;

[Tooltip("Cursor moving speed")]
[Range(0, 1)]
public float speed = 0.5f;

[Tooltip("Maximum radius for cursor movement")]
[Range(0, 0.1f)]
public float movementLimitRadius = 0.075f;

private Vector3 _targetPosition; // Target position for the cursor

private void Awake()
{
inverse3.Ready.AddListener((inverse3Controller, args) =>
{
_targetPosition = inverse3Controller.WorkspaceCenterLocalPosition;
});
}

/// <summary>
/// Subscribes to the DeviceStateChanged event.
/// </summary>
private void OnEnable()
{
verseGrip.DeviceStateChanged += OnDeviceStateChanged;
}

/// <summary>
/// Unsubscribes from the DeviceStateChanged event.
/// </summary>
private void OnDisable()
{
verseGrip.DeviceStateChanged -= OnDeviceStateChanged;
inverse3.Release();
}

private void OnDeviceStateChanged(object sender, VerseGripEventArgs args)
{
var verseGrip = args.DeviceController;
// Calculate the direction based on the VerseGrip's rotation
var direction = verseGrip.Orientation * Vector3.forward;

// Check if the VerseGrip button is pressed down
if (verseGrip.GetButtonDown())
{
// Initialize target position
_targetPosition = inverse3.CursorLocalPosition;
}

// Check if the VerseGrip button is being held down
if (verseGrip.GetButton())
{
// Move the target position toward the grip direction
_targetPosition += direction * (0.0001f * speed);

// Clamp the target position within the movement limit radius
var workspaceCenter = inverse3.WorkspaceCenterLocalPosition;
_targetPosition = Vector3.ClampMagnitude(_targetPosition - workspaceCenter, movementLimitRadius)
+ workspaceCenter;
}
// Move cursor to new position
inverse3.SetCursorLocalPosition(_targetPosition);
}
}
}