工作区导航教程
本教程介绍如何使用句柄调整光标缩放比例,让用户可以控制 导航工作区建筑的精度和大小。
本示例以工作区缩放和放置中创建的场景为基础 中创建的场景,对触觉工作区提供基于手柄的控制。 控制触觉工作区。目的是使用手柄按钮来触发工作区偏移,并使用光标位置来更改偏移。 并使用光标位置进行更改,第二个按钮用于触发工作区大小调整,并使用手柄滚动进行更改。 使用手柄滚动进行更改。
为此,当用户按下按钮时,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 = false
至 OnButtonUp()
事件和 (触觉工作区)WorkspaceOffsetController.active = true
至 OnButtonDown()
.
可选:绑定摄像机移动
光标偏移量的改变可以通过移动摄像机和工作区来实现,非常直观。因此 因此,即使偏移量较大,光标也不会移出画面。
为此,请在主摄像机上添加一个 "位置约束"组件,设置 触觉工作区* 为源,然后按下 "激活"按钮。
工作空间缩放控制器组件
创建新脚本 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
将手柄旋转的度数转换为数字刻度,该刻度受
的 minimumScale
和 maximumScale
.
然后添加以下字段:
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 = false
至 OnButtonUp()
事件和 (触觉工作区)WorkspaceScaleController.active = true
至 OnButtonDown()
.
结果
现在您可以按下手柄 按钮,即可在场景中进行缩放和移动。
源文件
本示例使用的最终场景和所有相关文件可从 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);
}
}