Interactive 元素 [实验] - MRTK2

MRTK 输入系统的简化集中式入口点。 包含核心交互状态的状态管理方法、事件管理和状态设置逻辑。

交互元素是 Unity 2019.3 和更高版本中支持的试验性功能,它利用了 Unity 2019.3 中的新功能:序列化引用

交互元素检查器

在播放模式下,交互元素检查器提供视觉反馈来指示当前是否处于活动状态。 如果处于活动状态,视觉反馈将以青色突出显示。 如果不处于活动状态,则颜色不会改变。 检查器中状态旁边的数字是状态值。如果处于活动状态,则值为 1;如果不处于活动状态,则值为 0。

Interactive Element with virtual hand interaction

核心状态

交互元素包含核心状态并支持添加自定义状态。 核心状态是已具有 BaseInteractiveElement 中定义的状态设置逻辑的状态。 下面是当前输入驱动的核心状态列表:

当前核心状态

近距和远距交互核心状态:

近距交互核心状态:

远距交互核心状态:

其他核心状态:

如何通过检查器添加核心状态

  1. 在交互元素的检查器中导航到“添加核心状态”。

    Add a Core State via Inspector

  2. 选择“选择状态”按钮以选择要添加的核心状态。 菜单中的状态将按交互类型排序。

    Add a Core State via Inspector with state selected

  3. 打开“事件配置”折叠菜单以查看与状态关联的事件和属性。

    Add a Core State via Inspector with event configuration

如何通过脚本添加核心状态

使用 AddNewState(stateName) 方法来添加核心状态。 若要查看可用核心状态名称的列表,请使用 CoreInteractionState 枚举。

// Add by name or add by CoreInteractionState enum to string

interactiveElement.AddNewState("SelectFar");

interactiveElement.AddNewState(CoreInteractionState.SelectFar.ToString());

状态内部结构

交互元素中状态的类型为 InteractionStateInteractionState 包含以下属性:

  • 名称:状态的名称。
  • 值:状态值。 如果状态为打开,则状态值为 1。 如果状态为关闭,则状态值为 0。
  • 活动:当前是否处于活动状态。 如果状态为打开,则 Active 属性的值为 true;如果状态为关闭,则该属性值为 false。
  • 交互类型:状态的交互类型是状态针对的交互类型。
    • None:不支持任何形式的输入交互。
    • Near:近距交互支持。 当关节手直接接触另一个游戏对象(即关节手的位置靠近游戏对象在世界空间中的位置)时,输入被视为近距交互。
    • Far:远距交互支持。 不需要直接接触游戏对象时,输入被视为远距交互。 例如,通过控制器射线或视线提供的输入被视为远距交互输入。
    • NearAndFar:包括近距和远距交互支持。
    • Other:独立于指针的交互支持。
  • 事件配置:状态的事件配置是序列化的事件配置文件入口点。

所有这些属性都是在交互元素中包含的 State Manager 内部设置的。 要修改状态,请使用以下帮助器方法:

状态设置帮助器方法

// Get the InteractionState
interactiveElement.GetState("StateName");

// Set a state value to 1/on
interactiveElement.SetStateOn("StateName");

// Set a state value to 0/off
interactiveElement.SetStateOff("StateName");

// Check if a state is present in the state list
interactiveElement.IsStatePresent("StateName");

// Check whether or not a state is active
interactiveElement.IsStateActive("StateName");

// Add a new state to the state list
interactiveElement.AddNewState("StateName");

// Remove a state from the state list
interactiveElement.RemoveState("StateName");

如何获取状态的事件配置取决于该状态本身。 每种核心状态都有特定的事件配置类型,下面介绍每种核心状态的部分概述了这些类型。

下面是获取状态事件配置的通用示例:

// T varies depending on the core state - the specific T's are specified under each of the core state sections
T stateNameEvents = interactiveElement.GetStateEvents<T>("StateName");

默认状态

交互元素上始终存在默认状态。 仅当所有其他状态都不是“活动”时,此状态才是“活动”。 如果任何其他状态变为“活动”,则默认状态将在内部设置为“关闭”。

使用状态列表中的“默认”和“焦点”状态初始化交互元素。 默认状态始终需要出现在状态列表中。

获取默认状态事件

默认状态的事件配置类型:StateEvents

StateEvents defaultEvents = interactiveElement.GetStateEvents<StateEvents>("Default");

defaultEvents.OnStateOn.AddListener(() =>
{
    Debug.Log($"{gameObject.name} Default State On");
});

defaultEvents.OnStateOff.AddListener(() =>
{
    Debug.Log($"{gameObject.name} Default State Off");
});

焦点状态

焦点状态是一种近距和远距交互状态,可将其视为等同于混合现实中的悬停。 焦点状态的近距和远距交互之间的区分因素是当前活动指针类型。 如果焦点状态的指针类型是戳击指针,则交互被视为近距交互。 如果主指针不是戳击指针,则交互被视为远距交互。 交互元素中默认存在焦点状态。

焦点状态行为Focus state with virtual hand interaction

焦点状态检查器Focus state in the Inpsector

获取焦点状态事件

焦点状态的事件配置类型:FocusEvents

FocusEvents focusEvents = interactiveElement.GetStateEvents<FocusEvents>("Focus");

focusEvents.OnFocusOn.AddListener((pointerEventData) =>
{
    Debug.Log($"{gameObject.name} Focus On");
});

focusEvents.OnFocusOff.AddListener((pointerEventData) =>
{
    Debug.Log($"{gameObject.name} Focus Off");
});

近距聚焦与远距聚焦行为

Focus near and far with virtual hand interaction

近距聚焦状态

在引发焦点事件并且主指针是戳击指针时设置近距聚焦状态,指示近距交互。

近距聚焦状态行为Focus near state with virtual hand interaction

近距聚焦状态检查器Focus near component in the Inspector

获取近距聚焦状态事件

近距聚焦状态的事件配置类型:FocusEvents

FocusEvents focusNearEvents = interactiveElement.GetStateEvents<FocusEvents>("FocusNear");

focusNearEvents.OnFocusOn.AddListener((pointerEventData) =>
{
    Debug.Log($"{gameObject.name} Near Interaction Focus On");
});

focusNearEvents.OnFocusOff.AddListener((pointerEventData) =>
{
    Debug.Log($"{gameObject.name} Near Interaction Focus Off");
});

远距聚焦状态

当主指针不是戳击指针时,将设置远距聚焦状态。 例如,默认的控制器射线指针和 GGV(视线、手势、语音)指针被视为远距交互指针。

远距聚焦状态行为Focus state far with virtual hand interaction

远距聚焦状态检查器Focus far component in the Inspector

获取远距聚焦状态事件

远距聚焦状态的事件配置类型:FocusEvents

FocusEvents focusFarEvents = interactiveElement.GetStateEvents<FocusEvents>("FocusFar");

focusFarEvents.OnFocusOn.AddListener((pointerEventData) =>
{
    Debug.Log($"{gameObject.name} Far Interaction Focus On");
});

focusFarEvents.OnFocusOff.AddListener((pointerEventData) =>
{
    Debug.Log($"{gameObject.name} Far Interaction Focus Off");
});

触摸状态

触摸状态是当关节手直接触摸对象时设置的近距交互状态。 直接触摸意味着关节手的食指非常靠近对象的世界位置。 默认情况下,如果将触摸状态添加到状态列表,则会将 NearInteractionTouchableVolume 组件附加到对象。 检测触摸事件需要存在 NearInteractionTouchableVolumeNearInteractionTouchable 组件。 NearInteractionTouchableVolumeNearInteractionTouchable 之间的区别在于,NearInteractionTouchableVolume 基于对象的碰撞体检测触摸,而 NearInteractionTouchable 检测定义的平面区域内部的触摸。

触摸状态行为Touch state with virtual hand interaction

触摸状态检查器Touch state component in the Inspector

获取触摸状态事件

触摸状态的事件配置类型:TouchEvents

TouchEvents touchEvents = interactiveElement.GetStateEvents<TouchEvents>("Touch");

touchEvents.OnTouchStarted.AddListener((touchData) =>
{
    Debug.Log($"{gameObject.name} Touch Started");
});

touchEvents.OnTouchCompleted.AddListener((touchData) =>
{
    Debug.Log($"{gameObject.name} Touch Completed");
});

touchEvents.OnTouchUpdated.AddListener((touchData) =>
{
    Debug.Log($"{gameObject.name} Touch Updated");
});

远距选择状态

远距选择状态是表面上的 IMixedRealityPointerHandler。 此状态是一种远距交互状态,它检测远距交互单击(隔空敲击),并保持到使用了远距交互指针(例如默认控制器射线指针或 GGV 指针)为止。 远距选择状态在名为 Global 的事件配置折叠菜单下有一个选项。 如果 Global 为 true,则 IMixedRealityPointerHandler 将注册为全局输入处理程序。 如果处理程序已注册为全局处理程序,则无需聚焦对象即可触发输入系统事件。 例如,如果用户希望不管聚焦了哪个对象都能随时知道执行了隔空敲击/选择手势,请将 Global 设置为 true。

远距选择状态行为Select far with virtual hand interaction

远距选择状态检查器Select far component in the Inspector

获取远距选择状态事件

远距选择状态的事件配置类型:SelectFarEvents

SelectFarEvents selectFarEvents = interactiveElement.GetStateEvents<SelectFarEvents>("SelectFar");

selectFarEvents.OnSelectUp.AddListener((pointerEventData) =>
{
    Debug.Log($"{gameObject.name} Far Interaction Pointer Up");
});

selectFarEvents.OnSelectDown.AddListener((pointerEventData) =>
{
    Debug.Log($"{gameObject.name} Far Interaction Pointer Down");
});

selectFarEvents.OnSelectHold.AddListener((pointerEventData) =>
{
    Debug.Log($"{gameObject.name} Far Interaction Pointer Hold");
});

selectFarEvents.OnSelectClicked.AddListener((pointerEventData) =>
{
    Debug.Log($"{gameObject.name} Far Interaction Pointer Clicked");
});

已单击状态

已单击状态默认由远距交互单击(远距选择状态)触发。 此状态在内部切换为打开,调用 OnClicked 事件,然后立即切换为关闭。

注意

对于已单击状态,检查器中不会提供基于状态活动的视觉反馈,因为此状态在打开后立即关闭。

已单击状态行为Clicked state with virtual hand interactions

已单击状态检查器Click state component in the Inspector

近距和远距已单击状态示例
可以使用 interactiveElement.TriggerClickedState() 方法通过附加的入口点来触发已单击状态。 例如,如果用户还希望通过近距交互触摸来触发对象单击,可以添加 TriggerClickedState() 方法作为触摸状态下的侦听器。

Near and far state with virtual hand interactions

获取已单击状态事件

已单击状态的事件配置类型:ClickedEvents

ClickedEvents clickedEvent = interactiveElement.GetStateEvents<ClickedEvents>("Clicked");

clickedEvent.OnClicked.AddListener(() =>
{
    Debug.Log($"{gameObject.name} Clicked");
});

打开和关闭状态

打开和关闭状态是成对出现的,切换行为需要存在这两种状态。 打开和关闭状态默认是通过远距交互单击(远距选择状态)触发的。 默认情况下,在启动时关闭状态为“活动”,这意味着切换将初始化为“关闭”。 如果用户希望在启动时打开状态为“活动”,请在打开状态中将 IsSelectedOnStart 设置为 true。

打开和关闭状态行为Toggle on and off with virtual hand interactions

打开和关闭状态检查器Toggle component in the Inspector

近距和远距切换状态示例
类似于已单击状态,可以使用 interactiveElement.SetToggleStates() 方法在切换状态设置中包含多个入口点。 例如,如果用户希望通过附加的入口点触摸以设置切换状态,可以将 SetToggleStates() 方法添加到处于触摸状态的事件之一。

Near and far toggle with virtual hand interactions

获取打开和关闭状态事件

打开状态的事件配置类型:ToggleOnEvents
关闭状态的事件配置类型:ToggleOffEvents

// Toggle On Events
ToggleOnEvents toggleOnEvent = interactiveElement.GetStateEvents<ToggleOnEvents>("ToggleOn");

toggleOnEvent.OnToggleOn.AddListener(() =>
{
    Debug.Log($"{gameObject.name} Toggled On");
});

// Toggle Off Events
ToggleOffEvents toggleOffEvent = interactiveElement.GetStateEvents<ToggleOffEvents>("ToggleOff");

toggleOffEvent.OnToggleOff.AddListener(() =>
{
    Debug.Log($"{gameObject.name} Toggled Off");
});

语音关键字状态

语音关键字状态收听混合现实语音配置文件中定义的关键字。 任何新关键字都必须在运行时之前注册到语音命令配置文件中(通过以下步骤)。

语音关键字状态行为Speech keyword with virtual interaction

语音关键字状态检查器Speech keyword component in the Inspector

注意

通过按下以上 gif 中的 F5 键,在编辑器中触发了语音关键字状态。 以下步骤概述了如何在编辑器中设置语音测试。

如何注册语音命令/关键字

  1. 选择“MixedRealityToolkit”游戏对象

  2. 选择复制并自定义当前配置文件

  3. 导航到“输入”部分,选择“克隆”以启用输入配置文件的修改

  4. 向下滚动到输入配置文件中的“语音”部分,克隆语音配置文件

    Speech keyword profile in the MRTK game object

  5. 选择“添加新语音命令”

    Adding a new speech keyword in the MRTK profile

  6. 输入新关键字。 可选:将 KeyCode 更改为 F5(或其他 KeyCode),以便能够在编辑器中测试。

    Configuring speech keyword in the MRTK profile

  7. 返回到交互元素语音关键字状态检查器,选择“添加关键字”

    Adding keyword to interactive element component

    Keyword validation and registration

  8. 输入刚刚在语音配置文件中注册的新关键字

    Entering new speech keyword

若要在编辑器中测试语音关键字状态,请按下在步骤 6 中定义的 KeyCode (F5),以模拟语音关键字识别的事件。

获取语音关键字状态事件

语音关键字状态的事件配置类型:SpeechKeywordEvents

SpeechKeywordEvents speechKeywordEvents = interactiveElement.GetStateEvents<SpeechKeywordEvents>("SpeechKeyword");

speechKeywordEvents.OnAnySpeechKeywordRecognized.AddListener((speechEventData) =>
{
    Debug.Log($"{speechEventData.Command.Keyword} recognized");
});

// Get the "Change" Keyword event specifically
KeywordEvent keywordEvent = speechKeywordEvents.Keywords.Find((keyword) => keyword.Keyword == "Change");

keywordEvent.OnKeywordRecognized.AddListener(() =>
{ 
    Debug.Log("Change Keyword Recognized"); 
});

自定义状态

如何通过检查器创建自定义状态

将使用默认状态事件配置来初始化通过检查器创建的自定义状态。 自定义状态的默认事件配置类型为 StateEvents,包含 OnStateOn 和 OnStateOff 事件。

  1. 在交互元素的检查器中导航到“创建自定义状态”。

    Creating a custom state

  2. 输入新状态的名称。 此名称必须是唯一的,且不能与现有核心状态的名称相同。

    Entering the name of a new custom state

  3. 选择“设置状态名称”以将该状态添加到状态列表中。

    Add custom state to state list

    使用包含 OnStateOnOnStateOff 事件的默认 StateEvents 事件配置来初始化此自定义状态。 若要为新状态创建自定义事件配置,请参阅:使用自定义事件配置创建自定义状态

    New state shown in the interactive element component

如何通过脚本创建自定义状态

interactiveElement.AddNewState("MyNewState");

// A new state by default is initialized with a the default StateEvents configuration which contains the 
// OnStateOn and OnStateOff events

StateEvents myNewStateEvents = interactiveElement.GetStateEvents<StateEvents>("MyNewState");

myNewStateEvents.OnStateOn.AddListener(() =>
{
    Debug.Log($"MyNewState is On");
});

使用自定义事件配置创建自定义状态

以下位置提供了名为 Keyboard 的自定义状态的示例文件:MRTK\SDK\Experimental\InteractiveElement\Examples\Scripts\CustomStateExample

以下步骤演练了创建自定义状态事件配置和接收器文件的现有示例。

  1. 设想一个状态名称。 此名称必须是唯一的,且不能与现有核心状态的名称相同。 对于本示例,状态名称将是 Keyboard。

  2. 创建以状态名称 +“Receiver”和状态名称 +“Events”命名的两个 .cs 文件。 这些文件的命名是在内部考虑的,必须遵循“状态名称 + Event/Receiver”约定。

    Keyboard state scripts

  3. 有关文件内容的更多详细信息,请参阅 KeyboardEvents.cs 和 KeyboardReceiver.cs 文件。 新的事件配置类必须从 BaseInteractionEventConfiguration 继承,新的事件接收器类必须从 BaseEventReceiver 继承。 CustomStateSettingExample.cs 文件中提供了有关键盘状态设置的示例。

  4. 使用状态名称将状态添加到交互元素,如果事件配置和事件接收器文件存在,则会识别到该状态名称。 自定义事件配置文件中的属性应显示在检查器中。

    Adding custom state to interactive elementCustom state recognized in the interactive element

  5. 有关事件配置和事件接收器文件的更多示例,请参阅以下路径中的文件:

  • MRTK\SDK\Experimental\InteractiveElement\InteractiveElement\Events\EventConfigurations
  • MRTK\SDK\Experimental\InteractiveElement\InteractiveElement\Events\EventReceivers

示例场景

以下位置提供了交互元素 + 状态可视化工具的示例场景:MRTK\SDK\Experimental\InteractiveElement\Examples\InteractiveElementExampleScene.unity

Example scene with Interactive Element and State Visualizer

可压缩按钮

示例场景包含名为 CompressableButtonCompressableButtonToggle 的预制件,这些预制件镜像了使用交互元素和状态可视化工具构造的 PressableButtonHoloLens2 按钮的行为。 CompressableButton 组件目前是 PressableButton + PressableButtonHoloLens2 与用作基类的 BaseInteractiveElement 的组合。

状态可视化工具 [试验性]

状态可视化工具组件根据链接的交互元素组件中定义的状态向对象添加动画。 此组件创建动画资产,将其放置在 MixedRealityToolkit.Generated 文件夹中,并通过向目标游戏对象添加 Animatable 属性来启用简化的动画关键帧设置。 若要在状态之间启用动画过渡,需要创建一个动画程序控制器资产,并生成一个具有关联参数和任何状态过渡的默认状态机。 可以在 Unity 的“动画程序”窗口中查看状态机。

状态可视化工具和 Unity 动画系统

状态可视化工具目前利用 Unity 动画系统。

按下状态可视化工具中的“生成新动画剪辑”按钮时,将根据交互元素中的状态名称生成新的动画剪辑资产并将其放置在 MixedRealityToolkit.Generated 文件夹中。 每个状态容器中的动画剪辑属性设置为关联的动画剪辑。

Animation clips in state visualizer component

还会生成一个动画程序状态机来管理动画剪辑之间的平滑过渡。 默认情况下,状态机利用任意状态来实现交互元素中任何状态之间的过渡。

还将为每种状态生成动画程序中触发的状态可视化工具,触发器参数在状态可视化工具中用于触发动画。

Unity state machine

运行时限制

必须通过检查器将状态可视化工具添加到对象,而不能通过脚本添加。 用于修改 AnimatorStateMachine/AnimationController 的属性包含在编辑器命名空间 (UnityEditor.Animations) 中,生成应用时会删除该命名空间。

如何使用状态可视化工具

  1. 创建一个立方体

  2. 附加交互元素

  3. 附加状态可视化工具

  4. 选择“生成新的动画剪辑”

    Generating new animation clips

    Showing generated animation clips in visualizer and interactive element components

  5. 在焦点状态容器中,选择“添加目标”

    Adding state visualizer target

  6. 将当前游戏对象拖放到目标字段中

    Setting state visualizer target

  7. 打开“立方体动画属性”折叠菜单

  8. 选择“可动画显示对象属性”下拉菜单并选择“颜色”

    Setting state visualizer color

  9. 选择“添加颜色可动画显示对象属性”

    Selecting the visualizer color animatable property

  10. 选择颜色

    Choosing a visualizer color from color wheel

  11. 按“播放”并观察颜色过渡变化

    Transitional color change example with virtual hand interaction

可动画显示对象属性

可动画显示对象属性的主要用途是简化动画剪辑的关键帧设置。 如果用户熟悉 Unity 动画系统并且偏好于直接在生成的动画剪辑上设置关键帧,则他们可以不用将可动画显示对象属性添加到目标对象,而只需在 Unity 的“动画”窗口中打开剪辑即可(“窗口”>“动画”>“动画”)。

如果将可动画显示对象属性用于动画,则曲线类型将设置为 EaseInOut。

当前的可动画显示对象属性:

比例偏移量

比例偏移量可动画显示对象属性采用对象的当前比例,并添加定义的偏移量。

Scale offset with virtual hand interaction

位置偏移量

位置偏移量可动画显示对象属性采用对象的当前位置,并添加定义的偏移量。

Position offset with virtual hand interaction

颜色

如果材料具有主颜色属性,则颜色可动画显示对象属性表示材料的主颜色。 此属性以动画显示 material._Color 属性。

Focus color change with virtual hand interaction

着色器颜色

着色器颜色可动画显示对象属性是指颜色类型的着色器属性。 所有着色器属性都需要一个属性名称。 以下 gif 以动画形式演示了为名为 Fill_Color 的着色器颜色属性,该属性不是主材料颜色。 在材料检查器中观察值的变化。

Shade color with virtual hand interaction

着色器浮点数

着色器浮点数可动画显示对象属性是指浮点类型的着色器属性。 所有着色器属性都需要一个属性名称。 在以下 gif 中,在材料检查器中观察“金属”属性值的变化。

Shader float with virtual hand interaction

着色器向量

着色器向量可动画显示对象属性是指 Vector4 类型的着色器属性。 所有着色器属性都需要一个属性名称。 在以下 gif 中,在材料检查器中观察“磁砖 (主 Tex_ST)”属性值的变化。

Shader vector with virtual hand interaction

如何查找可动画显示对象着色器属性名称

  1. 导航到“窗口”>“动画”>“动画”

  2. 确保在层次结构中选择了带有状态可视化工具的对象

  3. 在“动画”窗口中选择任一动画剪辑

  4. 选择“添加属性”,打开“网格渲染器”折叠菜单

    Adding animation property in the Animator window

  5. 此列表包含所有可动画显示对象属性名称

    Mesh renderer animation properties in the Animator window

请参阅