交互式元素 [试验性] — MRTK2
MRTK 输入系统的简化集中式入口点。 包含核心交互状态的状态管理方法、事件管理和状态设置逻辑。
交互元素是 Unity 2019.3 和更高版本中支持的试验性功能,它利用了 Unity 2019.3 中的新功能:序列化引用。
交互元素检查器
在播放模式下,交互元素检查器提供视觉反馈来指示当前是否处于活动状态。 如果处于活动状态,视觉反馈将以青色突出显示。 如果不处于活动状态,则颜色不会改变。 检查器中状态旁边的数字是状态值。如果处于活动状态,则值为 1;如果不处于活动状态,则值为 0。
核心状态
交互元素包含核心状态并支持添加自定义状态。 核心状态是已具有 BaseInteractiveElement
中定义的状态设置逻辑的状态。 下面是当前输入驱动的核心状态列表:
当前核心状态
近距和远距交互核心状态:
近距交互核心状态:
远距交互核心状态:
其他核心状态:
如何通过检查器添加核心状态
在交互元素的检查器中导航到“添加核心状态”。
选择“选择状态”按钮以选择要添加的核心状态。 菜单中的状态将按交互类型排序。
打开“事件配置”折叠菜单以查看与状态关联的事件和属性。
如何通过脚本添加核心状态
使用 AddNewState(stateName)
方法来添加核心状态。 若要查看可用核心状态名称的列表,请使用 CoreInteractionState
枚举。
// Add by name or add by CoreInteractionState enum to string
interactiveElement.AddNewState("SelectFar");
interactiveElement.AddNewState(CoreInteractionState.SelectFar.ToString());
状态内部结构
交互元素中状态的类型为 InteractionState
。 InteractionState
包含以下属性:
- 名称:状态的名称。
- 值:状态值。 如果状态为打开,则状态值为 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");
});
焦点状态
焦点状态是一种近距和远距交互状态,可将其视为等同于混合现实中的悬停。 焦点状态的近距和远距交互之间的区分因素是当前活动指针类型。 如果焦点状态的指针类型是戳击指针,则交互被视为近距交互。 如果主指针不是戳击指针,则交互被视为远距交互。 交互元素中默认存在焦点状态。
调焦状态行为
调焦状态检查器
获取焦点状态事件
焦点状态的事件配置类型: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");
});
近距聚焦与远距聚焦行为
近距聚焦状态
在引发焦点事件并且主指针是戳击指针时设置近距聚焦状态,指示近距交互。
调焦近距状态行为
调焦近距状态检查器
获取近距聚焦状态事件
近距聚焦状态的事件配置类型: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(视线、手势、语音)指针被视为远距交互指针。
调焦远距状态行为
调焦远距状态检查器
获取远距聚焦状态事件
远距聚焦状态的事件配置类型: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
组件附加到对象。 检测触摸事件需要存在 NearInteractionTouchableVolume
或 NearInteractionTouchable
组件。 NearInteractionTouchableVolume
与 NearInteractionTouchable
之间的区别在于,NearInteractionTouchableVolume
基于对象的碰撞体检测触摸,而 NearInteractionTouchable
检测定义的平面区域内部的触摸。
触摸状态行为
触摸状态检查器
获取触摸状态事件
触摸状态的事件配置类型: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。
选择远距状态行为
选择远距状态检查器
获取远距选择状态事件
远距选择状态的事件配置类型: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 事件,然后立即切换为关闭。
注意
对于已单击状态,检查器中不会提供基于状态活动的视觉反馈,因为此状态在打开后立即关闭。
单击状态行为
单击状态检查器
近距和远距已单击状态示例
可以使用 interactiveElement.TriggerClickedState()
方法通过附加的入口点来触发已单击状态。 例如,如果用户还希望通过近距交互触摸来触发对象单击,可以添加 TriggerClickedState()
方法作为触摸状态下的侦听器。
获取已单击状态事件
已单击状态的事件配置类型:ClickedEvents
ClickedEvents clickedEvent = interactiveElement.GetStateEvents<ClickedEvents>("Clicked");
clickedEvent.OnClicked.AddListener(() =>
{
Debug.Log($"{gameObject.name} Clicked");
});
打开和关闭状态
打开和关闭状态是成对出现的,切换行为需要存在这两种状态。 打开和关闭状态默认是通过远距交互单击(远距选择状态)触发的。 默认情况下,在启动时关闭状态为“活动”,这意味着切换将初始化为“关闭”。 如果用户希望在启动时打开状态为“活动”,请在打开状态中将 IsSelectedOnStart
设置为 true。
打开和关闭状态行为
打开和关闭状态检查器
近距和远距切换状态示例
类似于已单击状态,可以使用 interactiveElement.SetToggleStates()
方法在切换状态设置中包含多个入口点。 例如,如果用户希望通过附加的入口点触摸以设置切换状态,可以将 SetToggleStates()
方法添加到处于触摸状态的事件之一。
获取打开和关闭状态事件
打开状态的事件配置类型: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");
});
语音关键字状态
语音关键字状态收听混合现实语音配置文件中定义的关键字。 任何新关键字都必须在运行时之前注册到语音命令配置文件中(通过以下步骤)。
语音关键字状态行为
语音关键字状态检查器
注意
通过按下以上 gif 中的 F5 键,在编辑器中触发了语音关键字状态。 以下步骤概述了如何在编辑器中设置语音测试。
如何注册语音命令/关键字
选择“MixedRealityToolkit”游戏对象
选择复制并自定义当前配置文件
导航到“输入”部分,选择“克隆”以启用输入配置文件的修改
向下滚动到输入配置文件中的“语音”部分,克隆语音配置文件
选择“添加新语音命令”
输入新关键字。 可选:将 KeyCode 更改为 F5(或其他 KeyCode),以便能够在编辑器中测试。
返回到交互元素语音关键字状态检查器,选择“添加关键字”
输入刚刚在语音配置文件中注册的新关键字
若要在编辑器中测试语音关键字状态,请按下在步骤 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 事件。
在交互元素的检查器中导航到“创建自定义状态”。
输入新状态的名称。 此名称必须是唯一的,且不能与现有核心状态的名称相同。
选择“设置状态名称”以将该状态添加到状态列表中。
使用包含
OnStateOn
和OnStateOff
事件的默认StateEvents
事件配置来初始化此自定义状态。 若要为新状态创建自定义事件配置,请参阅:使用自定义事件配置创建自定义状态。
如何通过脚本创建自定义状态
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
以下步骤演练了创建自定义状态事件配置和接收器文件的现有示例。
设想一个状态名称。 此名称必须是唯一的,且不能与现有核心状态的名称相同。 对于本示例,状态名称将是 Keyboard。
创建以状态名称 +“Receiver”和状态名称 +“Events”命名的两个 .cs 文件。 这些文件的命名是在内部考虑的,必须遵循“状态名称 + Event/Receiver”约定。
有关文件内容的更多详细信息,请参阅 KeyboardEvents.cs 和 KeyboardReceiver.cs 文件。 新的事件配置类必须从
BaseInteractionEventConfiguration
继承,新的事件接收器类必须从BaseEventReceiver
继承。CustomStateSettingExample.cs
文件中提供了有关键盘状态设置的示例。使用状态名称将状态添加到交互元素,如果事件配置和事件接收器文件存在,则会识别到该状态名称。 自定义事件配置文件中的属性应显示在检查器中。
有关事件配置和事件接收器文件的更多示例,请参阅以下路径中的文件:
- MRTK\SDK\Experimental\InteractiveElement\InteractiveElement\Events\EventConfigurations
- MRTK\SDK\Experimental\InteractiveElement\InteractiveElement\Events\EventReceivers
示例场景
以下位置提供了交互元素 + 状态可视化工具的示例场景:MRTK\SDK\Experimental\InteractiveElement\Examples\InteractiveElementExampleScene.unity
可压缩按钮
示例场景包含名为 CompressableButton
和 CompressableButtonToggle
的预制件,这些预制件镜像了使用交互元素和状态可视化工具构造的 PressableButtonHoloLens2
按钮的行为。
CompressableButton
组件目前是 PressableButton
+ PressableButtonHoloLens2
与用作基类的 BaseInteractiveElement
的组合。
状态可视化工具 [试验性]
状态可视化工具组件根据链接的交互元素组件中定义的状态向对象添加动画。 此组件创建动画资产,将其放置在 MixedRealityToolkit.Generated 文件夹中,并通过向目标游戏对象添加 Animatable 属性来启用简化的动画关键帧设置。 若要在状态之间启用动画过渡,需要创建一个动画程序控制器资产,并生成一个具有关联参数和任何状态过渡的默认状态机。 可以在 Unity 的“动画程序”窗口中查看状态机。
状态可视化工具和 Unity 动画系统
状态可视化工具目前利用 Unity 动画系统。
按下状态可视化工具中的“生成新动画剪辑”按钮时,将根据交互元素中的状态名称生成新的动画剪辑资产并将其放置在 MixedRealityToolkit.Generated 文件夹中。 每个状态容器中的动画剪辑属性设置为关联的动画剪辑。
还会生成一个动画程序状态机来管理动画剪辑之间的平滑过渡。 默认情况下,状态机利用任意状态来实现交互元素中任何状态之间的过渡。
还将为每种状态生成动画程序中触发的状态可视化工具,触发器参数在状态可视化工具中用于触发动画。
运行时限制
必须通过检查器将状态可视化工具添加到对象,而不能通过脚本添加。 用于修改 AnimatorStateMachine/AnimationController 的属性包含在编辑器命名空间 (UnityEditor.Animations
) 中,生成应用时会删除该命名空间。
如何使用状态可视化工具
创建一个立方体
附加交互元素
附加状态可视化工具
选择“生成新的动画剪辑”
在焦点状态容器中,选择“添加目标”
将当前游戏对象拖放到目标字段中
打开“立方体动画属性”折叠菜单
选择“可动画显示对象属性”下拉菜单并选择“颜色”
选择“添加颜色可动画显示对象属性”
选择颜色
按“播放”并观察颜色过渡变化
可动画显示对象属性
可动画显示对象属性的主要用途是简化动画剪辑的关键帧设置。 如果用户熟悉 Unity 动画系统并且偏好于直接在生成的动画剪辑上设置关键帧,则他们可以不用将可动画显示对象属性添加到目标对象,而只需在 Unity 的“动画”窗口中打开剪辑即可(“窗口”>“动画”>“动画”)。
如果将可动画显示对象属性用于动画,则曲线类型将设置为 EaseInOut。
当前的可动画显示对象属性:
比例偏移量
比例偏移量可动画显示对象属性采用对象的当前比例,并添加定义的偏移量。
位置偏移量
位置偏移量可动画显示对象属性采用对象的当前位置,并添加定义的偏移量。
Color
如果材料具有主颜色属性,则颜色可动画显示对象属性表示材料的主颜色。 此属性以动画显示 material._Color
属性。
着色器颜色
着色器颜色可动画显示对象属性是指颜色类型的着色器属性。 所有着色器属性都需要一个属性名称。 以下 gif 以动画形式演示了为名为 Fill_Color 的着色器颜色属性,该属性不是主材料颜色。 在材料检查器中观察值的变化。
着色器浮点数
着色器浮点数可动画显示对象属性是指浮点类型的着色器属性。 所有着色器属性都需要一个属性名称。 在以下 gif 中,在材料检查器中观察“金属”属性值的变化。
着色器向量
着色器向量可动画显示对象属性是指 Vector4 类型的着色器属性。 所有着色器属性都需要一个属性名称。 在以下 gif 中,在材料检查器中观察“磁砖 (主 Tex_ST)”属性值的变化。
如何查找可动画显示对象着色器属性名称
导航到“窗口”>“动画”>“动画”
确保在层次结构中选择了带有状态可视化工具的对象
在“动画”窗口中选择任一动画剪辑
选择“添加属性”,打开“网格渲染器”折叠菜单
此列表包含所有可动画显示对象属性名称