본문으로 건너뛰기

VerseGrip 위치 제어 튜토리얼

이 튜토리얼에서는 VerseGrip의 회전 값을 사용하여 Inverse3 장치의 커서 위치를 직접 제어하는 방법을 설명합니다. 이 방식은 고주파 업데이트를 위해 Unity 메인 스레드를 우회합니다.

소개​

시작 가이드에서 소개된 방식은 표준 업데이트 주기(60Hz)로 동작하는 반면, 이 튜토리얼은 더 높은 주파수(1~4kHz)에서 VerseGrip 장치의 회전에 따라 Inverse3 커서의 위치를 동적으로 조정하는 것을 목표로 합니다. 이는 햅틱 스레드에 의해 트리거되는 DeviceStateChanged 이벤트를 활용하여 구현됩니다.

장면 설정​

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

VerseGripPositionControl 컴포넌트​

VerseGripPositionControl.cs라는 이름의 새로운 C# 스크립트를 생성하고, 이를 Haptic Origin 게임 오브젝트에 연결합니다. 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 장치에 대한 참조입니다.
  • speed: 커서가 이동하는 속도입니다.
  • 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의 버튼을 누르면 Inverse3 커서가 VerseGrip의 회전 방향으로 움직여, 실시간 제어를 보여 줍니다.

versegrip이 공을 움직이는 모습

이 이미지는 명확하게 보이도록 앞쪽 축을 강조 표시한 Cursor Model을 보여 줍니다. Cursor Model 사용자 지정에 대한 자세한 내용은 Cursor documentation을 참조하십시오.

소스 파일​

이 예제에서 사용하는 최종 씬과 관련 파일은 모두 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);
}
}
}