跳至主要内容
版本:最新

工作区导航教程

本教程介绍如何使用句柄调整光标缩放比例,让用户可以控制 导航工作区建筑的精度和大小。

本示例以工作区缩放和放置中创建的场景为基础 中创建的场景,对触觉工作区提供基于手柄的控制。 控制触觉工作区。目的是使用手柄按钮来触发工作区偏移,并使用光标位置来更改偏移。 并使用光标位置进行更改,第二个按钮用于触发工作区大小调整,并使用手柄滚动进行更改。 使用手柄滚动进行更改。

为此,当用户按下按钮时,WorkspaceOffsetController脚本会保存上次已知的光标位置,并停止更新头像位置。 已知的光标位置,并停止更新头像位置。另外,当用户按下按钮时,WorkspaceScaleController会保存工作区比例和当前手柄方向。 按下时,工作区缩放控制器会保存工作区缩放和当前手柄方向。同时使用这两个脚本的结果是,移动光标将创建一个新的光标偏移量,而旋转手柄将改变光标偏移量。 光标偏移量,而旋转手柄则会改变工作区的缩放比例。在此实现中 绕 Z 轴的 CCW 旋转对应于缩小工作区缩放比例,反之亦然。当 用户松开按钮时,两个偏移量将停止变化,光标头像将再次移动。 再次移动。

场景设置

首先将HandleTread组件添加到触觉工作区游戏对象中。在 检查器视图中,将光标设置为 "HandleThreadAvatar",然后添加一个立方体作为该 然后添加一个立方体作为光标的子对象,使其旋转可视化(详见《快速入门指南》

手柄螺纹

工作空间偏移控制器组件

创建新脚本 WorkspaceOffsetController.cs 并将其添加到 触觉工作区 游戏对象。然后为由 触觉线程

    private Transform m_cursor;

private void Awake()
{
m_cursor = GetComponent<HapticThread>().avatar;
}

那么有以下特性 :

private Vector3 m_basePosition;
private Vector3 m_cursorBasePosition;

m_basePosition 在进行任何修改之前保存工作场景位置、 而 m_cursorBasePosition 保存光标位置。

接下来,添加 active 与按钮状态相对应的封装字段,该字段初始化前面的字段,并在每次更新时启用工作区偏移。 的字段,并在每次更新时启用工作区偏移:

public bool active
{
get => m_active;
set
{
if (value)
{
m_basePosition = transform.localPosition;
m_cursorBasePosition = m_cursor.localPosition;
}
m_active = value;
}
}
private bool m_active;

最后,添加 Update 激活后,会通过计算光标位置相对于基准位置的变化来更改工作区偏移。 光标位置相对于基准位置的变化。请注意,位置变化需要通过工作区变换缩放 工作区变换进行缩放,以保持准确。

private void Update()
{
if (active)
{
// Move cursor offset relative to cursor position
transform.position = m_basePosition - Vector3.Scale(m_cursor.localPosition - m_cursorBasePosition, transform.lossyScale);
}
}

要将句柄按钮绑定到活动字段,请选择 触觉工作区 并从 检查面板中,添加 (触觉工作区)WorkspaceOffsetController.active = falseOnButtonUp() 事件和 (触觉工作区)WorkspaceOffsetController.active = trueOnButtonDown().

工作区偏移事件处理

可选:绑定摄像机移动

光标偏移量的改变可以通过移动摄像机和工作区来实现,非常直观。因此 因此,即使偏移量较大,光标也不会移出画面。

无摄像机限制 带摄像机约束

为此,请在主摄像机上添加一个 "位置约束"组件,设置 触觉工作区* 为源,然后按下 "激活"按钮。

摄像机限制

工作空间缩放控制器组件

创建新脚本 WorkspaceScaleController.cs 并将其添加到 触觉工作区 游戏对象。 然后为由 处理线程

private Transform m_cursor;

private void Awake()
{
m_cursor = GetComponent<HandleThread>().avatar;
}

然后添加以下设置:

public float scalingFactor = 0.25f;
public float minimumScale = 1f;
public float maximumScale = 20f;

摄像机限制

scalingFactor 将手柄旋转的度数转换为数字刻度,该刻度受 的 minimumScalemaximumScale.

然后添加以下字段:

private float m_baseScale;
private float m_cursorBaseAngle;

m_baseScale 在进行任何修改之前保存工作景观比例,而 m_cursorBaseAngle 保存 保存光标在 Y 轴上的方向。

接下来,添加 GetTotalDegrees 方法:

private float m_cursorPreviousAngle;
private int m_rotationCount;

private float GetTotalDegrees(float currentAngle, float baseAngle)
{
if (currentAngle - m_cursorPreviousAngle > 330)
m_rotationCount--;
else if (m_cursorPreviousAngle - currentAngle > 330)
m_rotationCount++;

m_cursorPreviousAngle = currentAngle;

return 360f * m_rotationCount + (currentAngle - baseAngle);
}

手柄可绕自身轴线旋转不止一次,这可能会导致位移在越过 0° 时突然跳变。 位移。此函数将当前角度与上一个角度进行对比检查,并 检测何时越过 0°,以及越过的方向,确保返回的角度偏移准确无误。 即使旋转不止一圈,也能确保返回的角度偏移准确无误。

接下来是 active 封装字段,与按钮状态相对应,初始化之前的字段并在每次更新时启用工作区缩放。 字段,并在每次更新时启用工作区缩放:

public bool active
{
get => m_active;
set
{
if (value)
{
m_rotationCount = 0;
m_baseScale = transform.localScale.z;
m_cursorPreviousAngle = m_cursorBaseAngle = m_cursor.localEulerAngles.z;

}
m_active = value;
}
}
private bool m_active;

最后,添加 Update 的变换。 触觉工作区 根据 手柄角度。

private void Update()
{
if (active)
{
// Calculate scale relative to cursor roll on Z-axis rotation
var totalDegrees = GetTotalDegrees(m_cursor.localEulerAngles.z, m_cursorBaseAngle);
var scale = m_baseScale - totalDegrees * scalingFactor / 100f;

// Limit between minimumScale and maximumScale
scale = Mathf.Clamp(scale, minimumScale, maximumScale);

// Set cursor offset scale (same on each axis)
transform.localScale = Vector3.one * scale;
// Invert cursor scale to keep its original size
m_cursor.localScale = Vector3.one / scale;
}
}

装订手柄按钮

与之前一样,将句柄按钮绑定到 active 字段,选择 触觉工作区 并从 检查面板中,添加 (触觉工作区)WorkspaceScaleController.active = falseOnButtonUp() 事件和 (触觉工作区)WorkspaceScaleController.active = trueOnButtonDown().

工作区刻度处理事件

结果

现在您可以按下手柄 按钮,即可在场景中进行缩放和移动。

工作区刻度处理事件

源文件

本示例使用的最终场景和所有相关文件可从 Unity 软件包管理器中的 "基本力反馈和工作区控制"示例导入。 Unity 示例包含本教程范围之外的其他生活质量改进:

  • 可视化工作区大小的透明气泡
  • 键盘快捷键
    • 按下 M 键只能移动工作区
    • 按下 S 键只能缩放工作区
  • 显示当前偏移值和比例值的用户界面

WorkspaceOffsetController.cs

using Haply.HardwareAPI.Unity;
using UnityEngine;

public class WorkspaceOffsetController : MonoBehaviour
{
// Movable cursor with position controlled by Haptic Thread
private Transform m_cursor;

// Saved workspace and cursor values at transformation beginning
private Vector3 m_basePosition;
private Vector3 m_cursorBasePosition;

// If true, the workspace offset is set relatively to the cursor position on each Update() loop
public bool active
{
get => m_active;
set
{
if (value)
{
m_basePosition = transform.localPosition;
m_cursorBasePosition = m_cursor.localPosition;
}
m_active = value;
}
}

private bool m_active;

private void Awake()
{
// Get the moving cursor from the HapticThread
m_cursor = GetComponent<HapticThread>().avatar;
}

private void Update()
{
if (active)
{
// Update the workspace offset relative to cursor position
transform.position = m_basePosition - Vector3.Scale(m_cursor.localPosition - m_cursorBasePosition, transform.lossyScale);
}
}
}

WorkspaceScaleController.cs

using System;
using Haply.HardwareAPI.Unity;
using UnityEngine;

public class WorkspaceScaleController : MonoBehaviour
{
// Movable cursor with rotation controlled by Handle Thread
private Transform m_cursor;

[Tooltip("Sensitivity of scaling on handle rotation")]
public float scalingFactor = 3f;
public float minimumScale = 1f;
public float maximumScale = 5f;

// Saved workspace and cursor values at transformation beginning
private float m_baseScale;
private float m_cursorBaseAngle;
private float m_cursorPreviousAngle;
private int m_rotationCount;

// If enabled the workspace will be uniformly scaled relatively to cursor roll (Z-axis rotation) on each Update() loop
public bool active
{
get => m_active;
set
{
if (value)
{
m_rotationCount = 0;
m_baseScale = transform.localScale.z;
m_cursorPreviousAngle = m_cursorBaseAngle = m_cursor.localEulerAngles.z;
}
m_active = value;
}
}
private bool m_active;

private void Awake()
{
// Get the rotating cursor from the HandleThread
m_cursor = GetComponent<HandleThread>().avatar;
}

private void Update()
{
if (active)
{
// Calculate scale relative to cursor roll on Z-axis rotation
var totalDegrees = GetTotalDegrees(m_cursor.localEulerAngles.z, m_cursorBaseAngle);
var scale = m_baseScale - totalDegrees * scalingFactor / 100f;

// Limit between minimumScale and maximumScale
scale = Mathf.Clamp(scale, minimumScale, maximumScale);

// Set cursor offset scale (same on each axis)
transform.localScale = Vector3.one * scale;
// Invert cursor scale to keep its original size
m_cursor.localScale = Vector3.one / scale;
}
}

// Return the total degrees between baseAngle and currentAngle over the 360 degrees limitation
private float GetTotalDegrees(float currentAngle, float baseAngle)
{
if (currentAngle - m_cursorPreviousAngle > 330)
m_rotationCount--;
else if (m_cursorPreviousAngle - currentAngle > 330)
m_rotationCount++;

m_cursorPreviousAngle = currentAngle;

return 360f * m_rotationCount + (currentAngle - baseAngle);
}
}