指针 - MRTK2
本文介绍在实践中如何配置和响应指针输入。 若要更好地大致了解如何控制多个指针,请参阅指针体系结构。
检测到新控制器时,指针会在运行时自动实例化。 可将多个指针附加到一个控制器。 例如,使用默认的指针配置文件时,Windows Mixed Reality 控制器将获得一条直线和一个抛物线指针,分别用于法线选择和传送。
指针配置
指针通过 MixedRealityPointerProfile
配置为 MRTK 中输入系统的一部分。 这种类型的配置文件将分配到 MRTK 配置检查器中的 MixedRealityInputSystemProfile
。 指针配置文件确定光标、运行时可用的指针类型,以及这些指针如何相互通信以确定哪个指针处于活动状态。
指向范围 - 定义指针可与 GameObject 交互的最大距离。
指向光线投射层掩码 - 这是一个高优先级的 LayerMask 数组,用于确定任一给定指针可与哪些可能的 GameObject 交互,以及尝试交互的顺序。 这有助于确保指针在其他场景对象之前首先与 UI 元素交互。
指针选项配置
默认 MRTK 指针配置文件配置包含以下现成的指针类和关联的预制件。 在运行时可供系统使用的指针列表在指针配置文件中的“指针选项”下定义。 开发人员可以利用此列表来重新配置现有指针、添加新指针或删除指针。
每个指针条目由以下数据集定义:
控制器类型 - 指针对其有效的控制器集。
- 例如,PokePointer 负责用手指“戳击”对象,默认情况下标记为仅支持关节手控制器类型。 仅当控制器可用时才会实例化指针。具体而言,“控制器类型”定义了可以使用此指针预制件创建哪些控制器。
惯用手 - 允许仅为特定的左/右手实例化指针
注意
将指标条目的“惯用手”属性设置为“无”会从系统中实际禁用该指针,相当于从列表中删除了该指针。
- 指针预制件 - 开始跟踪与指定的控制器类型和惯用手匹配的控制器时,将实例化此预制件资产。
一个控制器可以关联多个指针。 例如,在 DefaultHoloLens2InputSystemProfile
(Assets/MRTK/SDK/Profiles/HoloLens2/) 中,关节手控制器与 PokePointer、GrabPointer 和 DefaultControllerPointer(即手部射线)相关联。
注意
MRTK 在 Assets/MRTK/SDK/Features/UX/Prefabs/Pointers 中提供了一组指针预制件。 可以构建新的自定义预制件,前提是它包含 Assets/MRTK/SDK/Features/UX/Scripts/Pointers 中的指针脚本之一,或任何其他实现 IMixedRealityPointer
的脚本。
游标配置
通过编辑器中 GazeCursorPrefab
的 MixedRealityInputSystemProfile
属性可直接配置凝视光标。 若要配置用于其他指针的光标,需要更改对应 BaseControllerPointer
的 CursorPrefab
字段中使用的 prefab。 若要以编程方式更改光标,请修改相应 IMixedRealityPointer
行为的 BaseCursor
属性。
请参阅我们在 Assets/MRTK/SDK/Features/UX/Prefabs/Cursors 中的游标预制件,例如实现游标行为的示例。 特别是,DefaultGazeCursor 提供了基于上下文状态更改光标图形的可靠实现。
默认指针类
以下类是现成可用的 MRTK 指针,在前面所述的默认 MRTK 指针配置文件中定义。 Assets/MRTK/SDK/Features/UX/Prefabs/Pointers 下提供的每个指针预制件包含一个附加的指针组件。
远指针
LinePointer
LinePointer 是指针基类,它从输入源(即控制器)沿指针方向绘制一根线条,并支持沿此方向投射的单条光线。 一般情况下,会实例化并使用诸如 ShellHandRayPointer
之类的子类和传送指针(它们也会绘制线条来指示传送结束位置)而不是此类,此类主要提供常用功能。
对于 Oculus、Vive 和 Windows Mixed Reality 等运动控制器,旋转将匹配控制器的旋转。 对于 HoloLens 2 关节手等其他控制器,旋转与系统提供的手部指向姿势相匹配。
CurvePointer
CurvePointer 扩展了 LinePointer 类,允许沿曲线进行多步光线投射。 此指针基类对于曲线实例很有用,例如直线连贯弯折成抛物线的传送指针。
ShellHandRayPointer
从 LinePointer
扩展而来的 ShellHandRayPointer 的实现用作 MRTK 指针配置文件的默认值。 DefaultControllerPointer 预制件实现 ShellHandRayPointer
类。
GGVPointer
GGVPointer 也称为凝视/手势/语音 (GGV) 指针,主要通过“凝视”和“隔空敲击”或者“凝视”和“语音选择”交互来为 HoloLens 1 风格的注视和点按交互提供支持。 GGV 指针的位置和方向由头部的位置和旋转驱动。
TouchPointer
TouchPointer 负责处理 Unity 触摸输入(即触摸屏)。 之所以将此称作“远距离交互”,是因为触摸屏幕的行为会将光线从相机投射到场景中可能较远的位置。
MousePointer
MousePointer 提供从屏幕到世界的光线投射以实现远距离交互,但方式是使用鼠标而不是触摸。
注意
默认情况下,MRTK 中不提供鼠标支持,但可以通过在 MRTK 输入配置文件添加 MouseDeviceManager
类型的新输入数据提供程序,并将 MixedRealityMouseInputProfile
分配到该数据提供程序来启用这种支持。
近指针
PokePointer
PokePointer 用于与支持“近距交互可触摸元素”的游戏对象进行交互。 这些对象是附加有 NearInteractionTouchable
脚本的 GameObject。 对于 UnityUI,此指针查找 NearInteractionTouchableUnityUIs。 PokePointer 使用 SphereCast 来确定最近的可触摸元素,用于为可按按钮等元素赋能。
使用 NearInteractionTouchable
组件配置 GameObject 时,请确保将 localForward 参数配置为从按钮或其他应设为可触摸的对象的前面指出。 此外,确保可触摸元素的边界与可触摸对象的边界匹配。
有用的戳击指针属性:
- 可触摸距离:可以与可触摸表面交互的最大距离
- 视觉效果:用于呈现指尖视觉效果的游戏对象(默认为手指上的戒指)。
- 线条:从指尖到活动输入表面绘制的可选线条。
- 戳击层掩码 - 高优先级的 LayerMask 数组,用于确定指针可与哪些可能的 GameObject 交互,以及尝试交互的顺序。 请注意,GameObject 还必须有一个
NearInteractionTouchable
组件才能与戳击指针交互。
SpherePointer
SpherePointer 使用 UnityEngine.Physics.OverlapSphere 来识别可交互的最近 NearInteractionGrabbable
对象进行交互,这对于“可抓取”输入(例如 ManipulationHandler
)非常有用。 与 PokePointer
/NearInteractionTouchable
函数对类似,为了与球体指针进行交互,游戏对象必须包含一个组件,即 NearInteractionGrabbable
脚本。
有用的球体指针属性:
- 球体投射半径:用于查询可抓取对象的球体半径。
- 近距对象边距:要查询的球体投射半径顶部的距离,用于检测对象是否靠近指针。 近距对象检测总半径为球体投射半径 + 近距对象边距
- 近距对象扇区角度:围绕指针前轴的角度,用于查询附近的对象。 将
IsNearObject
查询函数视为一个圆锥体。 此角度默认设置为 66 度以匹配 Hololens 2 行为
- 近距对象平滑因子:近距对象检测的平滑因子。 如果在近距对象半径范围内检测到某个对象,则查询半径变为近距对象半径 * (1 + 近距对象平滑因子),以降低灵敏度并使对象更难离开检测范围。
- 抓取层掩码 - 高优先级的 LayerMask 数组,用于确定指针可与哪些可能的 GameObject 交互,以及尝试交互的顺序。 请注意,GameObject 还必须有
NearInteractionGrabbable
才能与 SpherePointer 交互。注意
空间感知层在 MRTK 提供的默认 GrabPointer 预制件中已禁用。 这是为了降低对空间网格执行球体重叠查询所造成的性能影响。 可以通过修改 GrabPointer 预制件来启用此层。
- 忽略不在 FOV 中的碰撞体 - 是否忽略可能在指针附近但实际上不在视觉 FOV 中的碰撞体。 这可以防止意外抓取,并允许在接近可抓取对象但看不到它时打开手部射线。 出于性能原因,视觉 FOV 是通过圆锥体而不是典型的截锥体定义的。 该圆锥体的中心和方向与相机的截锥体相同,其半径等于显示高度的一半(或垂直 FOV)。
瞬移指针
- 执行操作(即按下传送按钮)以移动用户时,
TeleportPointer
将引发传送请求。 - 执行操作(即按下瞬移按钮)和抛物线光线投射以移动用户时,
ParabolicTeleportPointer
将引发瞬移请求。
混合现实平台的指针支持
下表详细说明了 MRTK 中常见平台通常使用的指针类型。 注意:可将不同的指针类型添加到这些平台。 例如,可以向 VR 添加戳击指针或球体指针。 此外,带游戏手柄的 VR 设备可以使用 GGV 指针。
指针 | OpenVR | Windows Mixed Reality | HoloLens 1 | HoloLens 2 |
---|---|---|---|---|
ShellHandRayPointer | 有效 | 有效 | 有效 | |
TeleportPointer | 有效 | 有效 | ||
GGVPointer | 有效 | |||
SpherePointer | 有效 | |||
PokePointer | 有效 |
通过代码进行指针交互
指针事件接口
实现以下一个或多个接口并分配到具有 Collider
的 GameObject 的 MonoBehaviour 将接收关联接口定义的指针交互事件。
事件 | 说明 | Handler |
---|---|---|
Before Focus Changed / Focus Changed | 每次指针更改焦点时,都会在失去焦点和获得焦点的游戏对象上引发该事件。 | IMixedRealityFocusChangedHandler |
Focus Enter / Exit | 当第一个指针进入获得焦点的游戏对象时,以及最后一个指针离开失去焦点的游戏对象时,将在这些对象上引发该事件。 | IMixedRealityFocusHandler |
Pointer Down / Dragged / Up / Clicked | 引发该事件以报告指针按下、拖动和释放操作。 | IMixedRealityPointerHandler |
Touch Started / Updated / Completed | 由触摸感知指针(例如 PokePointer )引发,以报告触摸活动。 |
IMixedRealityTouchHandler |
注意
应在引发 IMixedRealityFocusChangedHandler
和 IMixedRealityFocusHandler
的对象中处理这两个事件。 可以全局接收焦点事件,但与其他输入事件不同,全局事件处理程序不会基于焦点阻止接收事件(事件将由全局处理程序和相应的聚焦对象接收)。
操作中的指针输入事件
MRTK 输入系统以与普通输入事件类似的方式识别和处理指针输入事件。 不同之处在于,指针输入事件仅由触发输入事件的指针聚焦的 GameObject 以及任何全局输入处理程序处理。 普通输入事件由所有活动指针聚焦的 GameObject 处理。
- MRTK 输入系统识别到已发生的输入事件
- MRTK 输入系统对所有已注册的全局输入处理程序触发输入事件的相关接口函数
- 输入系统确定哪个 GameObject 已由触发事件的指针聚焦
- 输入系统利用 Unity 的事件系统对聚焦的 GameObject 上的所有匹配组件触发相关接口函数
- 如果在任意时间某个输入事件标记为已使用,则该过程将会结束,并且不再有 GameObject 接收回调。
- 示例:在实现接口
IMixedRealityFocusHandler
的组件中搜索获得或失去焦点的 GameObject - 注意:如果在当前 GameObject 中找不到与所需接口匹配的组件,Unity 事件系统将努力搜索父 GameObject。
- 示例:在实现接口
- 如果未注册全局输入处理程序并且未找到具有匹配组件/接口的 GameObject,则输入系统将调用每个已注册的回退输入处理程序
示例
当指针获得或离开焦点或指针选择对象时,以下示例脚本会更改附加的渲染器的颜色。
public class ColorTap : MonoBehaviour, IMixedRealityFocusHandler, IMixedRealityPointerHandler
{
private Color color_IdleState = Color.cyan;
private Color color_OnHover = Color.white;
private Color color_OnSelect = Color.blue;
private Material material;
private void Awake()
{
material = GetComponent<Renderer>().material;
}
void IMixedRealityFocusHandler.OnFocusEnter(FocusEventData eventData)
{
material.color = color_OnHover;
}
void IMixedRealityFocusHandler.OnFocusExit(FocusEventData eventData)
{
material.color = color_IdleState;
}
void IMixedRealityPointerHandler.OnPointerDown(
MixedRealityPointerEventData eventData) { }
void IMixedRealityPointerHandler.OnPointerDragged(
MixedRealityPointerEventData eventData) { }
void IMixedRealityPointerHandler.OnPointerClicked(MixedRealityPointerEventData eventData)
{
material.color = color_OnSelect;
}
}
查询指针
可以收集当前处于活动状态的所有指针,方法是循环访问可用的输入源(即可用的控制器和输入),以发现哪些指针附加到了这些源。
var pointers = new HashSet<IMixedRealityPointer>();
// Find all valid pointers
foreach (var inputSource in CoreServices.InputSystem.DetectedInputSources)
{
foreach (var pointer in inputSource.Pointers)
{
if (pointer.IsInteractionEnabled && !pointers.Contains(pointer))
{
pointers.Add(pointer);
}
}
}
主指针
开发人员可以订阅 FocusProviders PrimaryPointerChanged 事件,以便在聚焦的主指针发生更改时收到通知。 这对于识别用户当前是通过凝视、手部射线还是其他输入源来与场景交互非常有用。
private void OnEnable()
{
var focusProvider = CoreServices.InputSystem?.FocusProvider;
focusProvider?.SubscribeToPrimaryPointerChanged(OnPrimaryPointerChanged, true);
}
private void OnPrimaryPointerChanged(IMixedRealityPointer oldPointer, IMixedRealityPointer newPointer)
{
...
}
private void OnDisable()
{
var focusProvider = CoreServices.InputSystem?.FocusProvider;
focusProvider?.UnsubscribeFromPrimaryPointerChanged(OnPrimaryPointerChanged);
// This flushes out the current primary pointer
OnPrimaryPointerChanged(null, null);
}
PrimaryPointerExample
(Assets/MRTK/Examples/Demos/Input/Scenes/PrimaryPointer) 场景展示了如何使用事件的 PrimaryPointerChangedHandler
来响应新的主指针。
指针结果
指针 Result
属性包含用于确定聚焦对象的场景查询的当前结果。 如同默认为运动控制器、凝视输入和手部射线创建的指针一样,光线投射指针包含光线投射命中的位置和法线。
private void IMixedRealityPointerHandler.OnPointerClicked(MixedRealityPointerEventData eventData)
{
var result = eventData.Pointer.Result;
var spawnPosition = result.Details.Point;
var spawnRotation = Quaternion.LookRotation(result.Details.Normal);
Instantiate(MyPrefab, spawnPosition, spawnRotation);
}
PointerResultExample
场景 (Assets/MRTK/Examples/Demos/Input/Scenes/PointerResult/PointerResultExample.unity) 展示了如何使用指针 Result
在命中位置生成对象。
禁用指针
若要启用和禁用指针(例如,禁用手部射线),请通过 PointerUtils
为给定指针类型设置 PointerBehavior
。
// Disable the hand rays
PointerUtils.SetHandRayPointerBehavior(PointerBehavior.AlwaysOff);
// Disable hand rays for the right hand only
PointerUtils.SetHandRayPointerBehavior(PointerBehavior.AlwaysOff, Handedness.Right);
// Disable the gaze pointer
PointerUtils.SetGazePointerBehavior(PointerBehavior.AlwaysOff);
// Set the behavior to match HoloLens 1
// Note, if on HoloLens 2, you must configure your pointer profile to make the GGV pointer show up for articulated hands.
public void SetHoloLens1()
{
PointerUtils.SetPokePointerBehavior(PointerBehavior.AlwaysOff, Handedness.Any);
PointerUtils.SetGrabPointerBehavior(PointerBehavior.AlwaysOff, Handedness.Any);
PointerUtils.SetRayPointerBehavior(PointerBehavior.AlwaysOff, Handedness.Any);
PointerUtils.SetGGVBehavior(PointerBehavior.Default);
}
有关更多示例,请参阅 PointerUtils
和 TurnPointersOnOff
。
通过编辑器进行指针交互
对于 IMixedRealityPointerHandler
处理的指针事件,MRTK 以 PointerHandler
组件的形式提供更大的便利,它允许通过 Unity 事件直接处理指针事件。
指针范围
远指针的某些设置可以限制它们的光线投射距离,以及与场景中其他对象交互的距离。 默认情况下,此值设置为 10 米。 选择此值是为了与 HoloLens shell 的行为保持一致。
可以通过更新 DefaultControllerPointer
预制件的 ShellHandRayPointer
组件字段来更改此值:
指针范围 - 控制指针的最大交互距离。
默认指针范围 - 控制当指针不与任何组件交互时要呈现的指针射线/线条长度。