指针 - MRTK2

Pointer

本文介绍在实践中如何配置和响应指针输入。 若要更好地大致了解如何控制多个指针,请参阅指针体系结构

检测到新控制器时,指针会在运行时自动实例化。 可将多个指针附加到一个控制器。 例如,使用默认的指针配置文件时,Windows Mixed Reality 控制器将获得一条直线和一个抛物线指针,分别用于法线选择和传送。

指针配置

指针通过 MixedRealityPointerProfile 配置为 MRTK 中输入系统的一部分。 这种类型的配置文件将分配到 MRTK 配置检查器中的 MixedRealityInputSystemProfile。 指针配置文件确定光标、运行时可用的指针类型,以及这些指针如何相互通信以确定哪个指针处于活动状态。

  • 指向范围 - 定义指针可与 GameObject 交互的最大距离

  • 指向光线投射层掩码 - 这是一个高优先级的 LayerMask 数组,用于确定任一给定指针可与哪些可能的 GameObject 交互,以及尝试交互的顺序。 这有助于确保指针在其他场景对象之前首先与 UI 元素交互。 Pointer Profile Example

指针选项配置

默认 MRTK 指针配置文件配置包含以下现成的指针类和关联的预制件。 在运行时可供系统使用的指针列表在指针配置文件中的“指针选项”下定义。 开发人员可以利用此列表来重新配置现有指针、添加新指针或删除指针。

Pointer Options Profile Example

每个指针条目由以下数据集定义:

  • 控制器类型 - 指针对其有效的控制器集

    • 例如,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 的脚本

游标配置

通过编辑器中 GazeCursorPrefabMixedRealityInputSystemProfile 属性可直接配置凝视光标。 若要配置用于其他指针的光标,需要更改对应 BaseControllerPointerCursorPrefab 字段中使用的 prefab。 若要以编程方式更改光标,请修改相应 IMixedRealityPointer 行为的 BaseCursor 属性。

Cursor Prefab Property

请参阅我们在 Assets/MRTK/SDK/Features/UX/Prefabs/Cursors 中的游标预制件,例如实现游标行为的示例。 特别是,DefaultGazeCursor 提供了基于上下文状态更改光标图形的可靠实现。

默认指针类

以下类是现成可用的 MRTK 指针,在前面所述的默认 MRTK 指针配置文件中定义。 Assets/MRTK/SDK/Features/UX/Prefabs/Pointers 下提供的每个指针预制件包含一个附加的指针组件

MRTK Default Pointers

远指针

LinePointer

LinePointer 是指针基类,它从输入源(即控制器)沿指针方向绘制一根线条,并支持沿此方向投射的单条光线。 一般情况下,会实例化并使用诸如 ShellHandRayPointer 之类的子类和传送指针(它们也会绘制线条来指示传送结束位置)而不是此类,此类主要提供常用功能。

对于 Oculus、Vive 和 Windows Mixed Reality 等运动控制器,旋转将匹配控制器的旋转。 对于 HoloLens 2 关节手等其他控制器,旋转与系统提供的手部指向姿势相匹配。

MRTK Pointer Line
CurvePointer

CurvePointer 扩展了 LinePointer 类,允许沿曲线进行多步光线投射。 此指针基类对于曲线实例很有用,例如直线连贯弯折成抛物线的传送指针。

ShellHandRayPointer

LinePointer 扩展而来的 ShellHandRayPointer 的实现用作 MRTK 指针配置文件的默认值。 DefaultControllerPointer 预制件实现 ShellHandRayPointer

GGVPointer

GGVPointer 也称为凝视/手势/语音 (GGV) 指针,主要通过“凝视”和“隔空敲击”或者“凝视”和“语音选择”交互来为 HoloLens 1 风格的注视和点按交互提供支持。 GGV 指针的位置和方向由头部的位置和旋转驱动。

TouchPointer

TouchPointer 负责处理 Unity 触摸输入(即触摸屏)。 之所以将此称作“远距离交互”,是因为触摸屏幕的行为会将光线从相机投射到场景中可能较远的位置。

MousePointer

MousePointer 提供从屏幕到世界的光线投射以实现远距离交互,但方式是使用鼠标而不是触摸

Mouse pointer

注意

默认情况下,MRTK 中不提供鼠标支持,但可以通过在 MRTK 输入配置文件添加 MouseDeviceManager 类型的新输入数据提供程序,并将 MixedRealityMouseInputProfile 分配到该数据提供程序来启用这种支持

近指针

PokePointer

PokePointer 用于与支持“近距交互可触摸元素”的游戏对象进行交互。 这些对象是附加有 NearInteractionTouchable 脚本的 GameObject。 对于 UnityUI,此指针查找 NearInteractionTouchableUnityUIs。 PokePointer 使用 SphereCast 来确定最近的可触摸元素,用于为可按按钮等元素赋能。

使用 NearInteractionTouchable 组件配置 GameObject 时,请确保将 localForward 参数配置为从按钮或其他应设为可触摸的对象的前面指出。 此外,确保可触摸元素的边界与可触摸对象的边界匹配

有用的戳击指针属性:

  • 可触摸距离:可以与可触摸表面交互的最大距离
  • 视觉效果:用于呈现指尖视觉效果的游戏对象(默认为手指上的戒指)
  • 线条:从指尖到活动输入表面绘制的可选线条
  • 戳击层掩码 - 高优先级的 LayerMask 数组,用于确定指针可与哪些可能的 GameObject 交互,以及尝试交互的顺序。 请注意,GameObject 还必须有一个 NearInteractionTouchable 组件才能与戳击指针交互。
Poke Pointer
SpherePointer

SpherePointer 使用 UnityEngine.Physics.OverlapSphere 来识别可交互的最近 NearInteractionGrabbable 对象进行交互,这对于“可抓取”输入(例如 ManipulationHandler)非常有用。 与 PokePointer/NearInteractionTouchable 函数对类似,为了与球体指针进行交互,游戏对象必须包含一个组件,即 NearInteractionGrabbable 脚本。

Grab Pointer

有用的球体指针属性:

  • 球体投射半径:用于查询可抓取对象的球体半径
  • 近距对象边距:要查询的球体投射半径顶部的距离,用于检测对象是否靠近指针。 近距对象检测总半径为球体投射半径 + 近距对象边距
  • 近距对象扇区角度:围绕指针前轴的角度,用于查询附近的对象。 将 IsNearObject 查询函数视为一个圆锥体。 此角度默认设置为 66 度以匹配 Hololens 2 行为

Sphere pointer modified to only query for objects in the forward direction

  • 近距对象平滑因子:近距对象检测的平滑因子。 如果在近距对象半径范围内检测到某个对象,则查询半径变为近距对象半径 * (1 + 近距对象平滑因子),以降低灵敏度并使对象更难离开检测范围。
  • 抓取层掩码 - 高优先级的 LayerMask 数组,用于确定指针可与哪些可能的 GameObject 交互,以及尝试交互的顺序。 请注意,GameObject 还必须有 NearInteractionGrabbable 才能与 SpherePointer 交互。

    注意

    空间感知层在 MRTK 提供的默认 GrabPointer 预制件中已禁用。 这是为了降低对空间网格执行球体重叠查询所造成的性能影响。 可以通过修改 GrabPointer 预制件来启用此层。

  • 忽略不在 FOV 中的碰撞体 - 是否忽略可能在指针附近但实际上不在视觉 FOV 中的碰撞体。 这可以防止意外抓取,并允许在接近可抓取对象但看不到它时打开手部射线。 出于性能原因,视觉 FOV 是通过圆锥体而不是典型的截锥体定义的。 该圆锥体的中心和方向与相机的截锥体相同,其半径等于显示高度的一半(或垂直 FOV)。
Sphere Pointer

瞬移指针

  • 执行操作(即按下传送按钮)以移动用户时,TeleportPointer 将引发传送请求。
  • 执行操作(即按下瞬移按钮)和抛物线光线投射以移动用户时,ParabolicTeleportPointer 将引发瞬移请求。
Pointer Parabolic

混合现实平台的指针支持

下表详细说明了 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

注意

应在引发 IMixedRealityFocusChangedHandlerIMixedRealityFocusHandler 的对象中处理这两个事件。 可以全局接收焦点事件,但与其他输入事件不同,全局事件处理程序不会基于焦点阻止接收事件(事件将由全局处理程序和相应的聚焦对象接收)。

操作中的指针输入事件

MRTK 输入系统以与普通输入事件类似的方式识别和处理指针输入事件。 不同之处在于,指针输入事件仅由触发输入事件的指针聚焦的 GameObject 以及任何全局输入处理程序处理。 普通输入事件由所有活动指针聚焦的 GameObject 处理。

  1. MRTK 输入系统识别到已发生的输入事件
  2. MRTK 输入系统对所有已注册的全局输入处理程序触发输入事件的相关接口函数
  3. 输入系统确定哪个 GameObject 已由触发事件的指针聚焦
    1. 输入系统利用 Unity 的事件系统对聚焦的 GameObject 上的所有匹配组件触发相关接口函数
    2. 如果在任意时间某个输入事件标记为已使用,则该过程将会结束,并且不再有 GameObject 接收回调。
      • 示例:在实现接口 IMixedRealityFocusHandler 的组件中搜索获得或失去焦点的 GameObject
      • 注意:如果在当前 GameObject 中找不到与所需接口匹配的组件,Unity 事件系统将努力搜索父 GameObject。
  4. 如果未注册全局输入处理程序并且未找到具有匹配组件/接口的 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 来响应新的主指针。

Primary Pointer Example

指针结果

指针 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 在命中位置生成对象。

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

有关更多示例,请参阅 PointerUtilsTurnPointersOnOff

通过编辑器进行指针交互

对于 IMixedRealityPointerHandler 处理的指针事件,MRTK 以 PointerHandler 组件的形式提供更大的便利,它允许通过 Unity 事件直接处理指针事件。

Pointer Handler

指针范围

远指针的某些设置可以限制它们的光线投射距离,以及与场景中其他对象交互的距离。 默认情况下,此值设置为 10 米。 选择此值是为了与 HoloLens shell 的行为保持一致。

可以通过更新 DefaultControllerPointer 预制件的 ShellHandRayPointer 组件字段来更改此值:

指针范围 - 控制指针的最大交互距离

默认指针范围 - 控制当指针不与任何组件交互时要呈现的指针射线/线条长度

另请参阅