跳到主要内容

VerseGrip 位置控制教程

本教程演示如何利用 VerseGrip 的旋转直接控制 Inverse3 设备光标的位置,同时绕过 Unity 主线程以实现高频更新。

简介​

与 入门指南 中所示的方法不同(该方法以标准更新频率 60Hz 运行),本教程旨在根据 VerseGrip 设备的旋转,以更高的频率(1~4kHz)动态调整 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 设备的引用,通过 Inspector 设置。
  • verseGrip:用于光标控制的 VerseGrip 设备的引用。
  • speed:光标的移动速度。
  • movementLimitRadius:光标可从其初始位置移动的最大距离。
  • _targetPosition:光标移动所朝向的目标位置。

Awake 方法​

在 Awake 方法中,注册 Ready 事件,以便根据 Inverse3 设备的工作空间中心来初始化光标的目标位置:

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

在设备就绪之前,由于尚未确定左右手配置,工作空间中心无法获取。

实现 OnDeviceStateChanged 方法,根据 VerseGrip 的旋转和按钮输入计算光标的目标位置:

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 设备,并确保其有足够的移动空间。
  • 进入播放模式(Play Mode),并握住 Inverse3 光标。
  • 旋转 VerseGrip,观察 Unity 场景中光标的移动,其与 VerseGrip 的朝向直接对应。
  • 按下 VerseGrip 的按钮,可使 Inverse3 光标沿 VerseGrip 旋转的方向移动,展示实时控制效果。

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