可交互 — MRTK2

Interactable

Interactable 组件是一体式的容器,它使所有对象都能轻松交互并响应输入。 “可交互对象”用作各种类型的输入的一个笼统术语,包括触摸、手部射线和语音等等,它将这些交互传送到事件视觉主题响应中。 使用该组件可以轻松制作按钮、通过聚焦更改对象的颜色等。

如何配置可交互对象

该组件允许配置三个主要部分:

  1. 一般输入配置
  2. 面向多个 GameObject 的视觉主题
  3. 事件处理程序

一般输入设置

General Interactable Settings

状态

States 是一个 ScriptableObject 参数,用于定义可交互对象配置文件视觉主题的交互阶段,例如按下或已查看

DefaultInteractableStates (Assets/MRTK/SDK/Features/UX/Interactable/States/DefaultInteractableStates.asset) 随 MRTK 推出,可立即使用,是可交互组件的默认参数

States ScriptableObject example in inspector

DefaultInteractableStates 资产包含四种状态,并利用 InteractableStates 状态模型实现

  • Default:没有任何操作,这是最独立的基本状态

  • Focus:正在指向对象。 这是一个单一状态,当前没有设置其他状态,但它的重要性超过 Default。

  • Press:正在指向对象,并且正在按下按钮或手部。 Press 状态的重要性超过 Default 和 Focus。 该状态也设置为“物理按下”的回退。

  • Disabled:该按钮不应是交互式的,如果此时由于某种原因该按钮不可用,用户将通过视觉反馈获知。 理论上,disabled 状态可以包含所有其他状态,但当 Enabled 状态关闭时,Disabled 状态的重要性超过所有其他状态。

根据列表中的顺序为状态分配位值 (#)。

注意

通常建议在创建可交互对象组件时使用 DefaultInteractableStates (Assets/MRTK/SDK/Features/UX/Interactable/States/DefaultInteractableStates.asset)

但是,有 17 种可交互对象状态可用于驱动主题,尽管部分主题理应由其他组件驱动。 下表是具有内置功能的状态。

  • Visited:已单击可交互对象。
  • Toggled:按钮处于已切换状态或维度索引为奇数。
  • Gesture:手部或控制器已按下,并已从初始位置移动。
  • VoiceCommand:使用语音命令触发了可交互对象。
  • PhysicalTouch:当前检测到触摸输入;使用 NearInteractionTouchable 启用。
  • Grab:一只手当前正在抓取对象的边界;使用 NearInteractionGrabbable 启用

Enabled

切换以决定是否启用可交互对象。 这对应于代码中的 Interactable.IsEnabled

可交互对象的 enabled 属性与通过 GameObject/组件(即 SetActive 等)配置的 enabled 属性不同。 禁用 GameObject 或可交互对象 MonoBehaviour 将阻止运行类中的所有内容,包括输入、视觉主题、事件等。通过 Interactable.IsEnabled 禁用将禁用大多数输入处理并重置相关输入状态。 但是,该类仍将运行每一帧并接收将被忽略的输入事件。 这适用于在禁用状态下显示可交互对象,该操作可以通过视觉主题来完成。 一个典型示例是等待所有必需的输入字段填写完毕的提交按钮。

输入操作

从可交互对象组件应响应的输入配置或控制器映射配置文件中选择输入操作

可以在运行时通过 Interactable.InputAction 在代码中配置此属性。

IsGlobal

如果为 true,这会将组件标记为所选输入操作的全局输入侦听器。 默认行为为 false,这会将输入限制为仅此可交互对象碰撞体/GameObject

可以在运行时通过 Interactable.IsGlobal 在代码中配置此属性。

语音命令

语音命令,来自 MRTK 语音命令配置文件,用于触发 OnClick 事件以实现语音交互。

可以在运行时通过 Interactable.VoiceCommand 在代码中配置此属性。

需要聚焦

如果为 true,则当且仅当可交互对象已从指针获取焦点时,语音命令才会激活可交互。 如果为 false,则可交互对象将充当所选语音命令的全局侦听器。 默认行为为 true,因为难以在一个场景中组织多个全局语音侦听器。

可以在运行时通过 Interactable.VoiceRequiresFocus 在代码中配置此属性。

选择模式

该属性定义选择逻辑。 单击可交互对象后,它会迭代到下一个维度级别。 维度与排名类似,定义输入之外的状态(即 focus、press 等)。 它们适用于定义与按钮关联的 Toggle 状态或其他多级状态。 当前维度级别由 Interactable.DimensionIndex 跟踪。

可用的选择模式如下:

  • 按钮 - 维度 = 1,简单的可单击可交互对象
  • 切换 - 维度 = 2,可交互对象在打开/关闭状态之间交替
  • 多维度 - 维度>= 3,每次单击都会使当前维度级别 + 1。 适用于将按钮状态定义为列表等。

可交互对象还允许每个维度定义多个主题。 例如,如果 SelectionMode=Toggle,则取消选择可交互对象时可以应用一个主题,选择该组件时可以应用另一个主题

可以在运行时通过 Interactable.ButtonMode 查询当前的选择模式。 通过将 Interactable.Dimensions 属性设置为与所需功能匹配,可以实现在运行时更新模式。 此外,可通过 Interactable.CurrentDimension 访问适用于切换模式和多维模式的当前维度

可交互对象配置文件

配置文件是在 GameObject 和视觉主题之间创建关系的项。 配置文件定义状态发生变化时主题将处理的内容。

主题的工作原理与材料非常类似。 它们是可编写脚本的对象,其中包含将根据当前状态分配给对象的一系列属性。 主题也是可重用的,可以跨多个可交互 UX 对象分配

销毁时重置

视觉主题根据所选主题引擎的类和类型修改目标 GameObject 的各种属性。 如果销毁可交互对象组件时“销毁时重置”为 true,则组件会将活动主题中的所有修改过的属性重置为其初始值。 否则,销毁时可交互对象组件会按原样保留所有修改过的属性。 在后一种情况下,值的最后状态将保留,除非被另一个外部组件更改。 默认值为 false。

Profile theams

事件

每个可交互对象组件都有一个 OnClick 事件,选中可交互对象组件时会触发该事件。 但是,可交互对象可用于检测除 OnClick 之外的输入事件

单击“添加事件”按钮,添加新类型的事件接收器定义。 添加后,选择所需的事件类型。

Events example)

不同类型的事件接收器响应不同类型的输入。 MRTK 随附以下一组立即可用的接收器。

可以通过新建扩展 ReceiverBase 的类来创建自定义接收器。

Event Toggle Receiver Example

切换事件接收器示例

可交互对象接收器

InteractableReceiver 组件允许在源可交互对象组件之外定义事件。 InteractableReceiver 将侦听另一个可交互对象触发的经筛选的事件类型。 如果未直接分配“可交互对象”属性,则“搜索范围”属性定义 InteractableReceiver 侦听事件的方向,这些事件位于其自身、父级或子级 GameObject 中

InteractableReceiverList 的作用类似,但适用的是一系列匹配事件。

Interactable reciver

创建自定义事件

视觉主题类似,可以扩展事件以检测任意状态模式或公开功能。

可以通过两种主要方式创建自定义事件:

  1. 扩展 ReceiverBase 类以创建将在事件类型下拉列表中显示的自定义事件。 默认提供一个 Unity 事件,但可以添加更多 Unity 事件或将事件设置为隐藏 Unity 事件。 设计师可以使用此功能与工程师一起处理项目,创建设计人员可在编辑器中设置的自定义事件。

  2. 扩展 ReceiverBaseMonoBehavior 类,以创建可驻留在可交互对象或其他对象上的完全自定义的事件组件ReceiverBaseMonoBehavior 将引用可交互对象以检测状态更改

扩展 ReceiverBase 示例

CustomInteractablesReceiver 类显示有关可交互对象的状态信息,并作为示例说明如何创建自定义事件接收器

public CustomInteractablesReceiver(UnityEvent ev) : base(ev, "CustomEvent")
{
    HideUnityEvents = true; // hides Unity events in the receiver - meant to be code only
}

创建自定义事件接收器时,可以使用以下方法进行重写/实现。 ReceiverBase.OnUpdate() 是一种抽象方法,可用于检测状态模式/转换。 此外,ReceiverBase.OnVoiceCommand()ReceiverBase.OnClick() 方法可用于在选中可交互对象时创建自定义事件逻辑

public override void OnUpdate(InteractableStates state, Interactable source)
{
    if (state.CurrentState() != lastState)
    {
        // the state has changed, do something new
        lastState = state.CurrentState();
        ...
    }
}

public virtual void OnVoiceCommand(InteractableStates state, Interactable source,
                                    string command, int index = 0, int length = 1)
{
    base.OnVoiceCommand(state, source, command, index, length);
    // voice command called, perform some action
}  

public virtual void OnClick(InteractableStates state,
                            Interactable source,
                            IMixedRealityPointer pointer = null)
{
    base.OnClick(state, source);
    // click called, perform some action
}
在检查器中显示自定义事件接收器字段

ReceiverBase 脚本使用 InspectorField 属性在检查器中公开自定义属性。 下面是 Vector3 的示例,它是一个带有工具提示和标签信息的自定义属性。 选中可交互对象 GameObject 并且其已添加了关联的事件接收器类型时,该属性将在检查器中显示为可配置

[InspectorField(Label = "<Property label>",Tooltip = "<Insert tooltip info>",Type = InspectorField.FieldTypes.Vector3)]
public Vector3 EffectOffset = Vector3.zero;

如何使用可交互对象

生成简单按钮

可以通过将可交互对象组件添加到配置为接收输入事件的 GameObject 来创建简单按钮。 它本身或子级上可以有一个碰撞体,用于接收输入。 如果将可交互对象与基于 Unity UI 的 GameObjects 结合使用,则它应位于 Canvas GameObject 下

通过新建配置文件、分配 GameObject 本身和新建主题,可进一步对按钮进行配置。 此外,可以使用 OnClick 事件执行某些操作

注意

若要使按钮可按下,需使用 PressableButton 组件。 另外,需使用 PhysicalPressEventRouter 组件将 press 事件汇集到可交互对象组件

创建切换和多维度按钮

切换按钮

若要使按钮可切换,请将 Selection Mode 字段更改为 Toggle 类型。 在“配置文件”部分,为在打开可交互对象时使用的每个配置文件都添加了新的切换主题

SelectionMode 设为 Toggle 时,可以使用“IsToggled”复选框在运行时初始化时设置控件的默认值

CanSelect 表示可交互可从关闭切换为开启,CanDeselect 则表示反之亦然

Profile Toggle Visual Themes Example

开发人员可以利用 SetToggledIsToggled 接口通过代码获取/设置可交互对象的切换状态

// If using SelectionMode = Toggle (i.e Dimensions == 2)

// Make the Interactable selected and toggled on
myInteractable.IsToggled = true;

// Get whether the Interactable is selected or not
bool isSelected = myInteractable.IsToggled;
切换按钮集合

通常,切换按钮很多且在任意指定时间仅有一个可以处于活动状态,则称为径向按钮或单选按钮等。

使用 InteractableToggleCollection 组件启用此功能。 此控件可确保在任意指定时间仅切换一个可交互对象。 RadialSet (Assets/MRTK/SDK/Features/UX/Interactable/Prefabs/RadialSet.prefab) 也是一个现成可用的极佳起点

若要创建自定义单选按钮组,请执行以下操作:

  1. 创建多个可交互对象 GameObjects/按钮
  2. 将每个可交互对象设置为 SelectionMode = Toggle、CanSelect = true 以及 CanDeselect = false
  3. 在所有可交互对象上创建空的父级 GameObject 并添加 InteractableToggleCollection 组件
  4. 将所有可交互对象添加到 InteractableToggleCollection 上的 ToggleList
  5. 设置 InteractableToggleCollection.CurrentIndex 属性,以确定启动时默认选择的按钮
Toggle collection

多维度按钮

多维度选择模式用于创建顺序按钮,或具有两个以上步骤的按钮,例如使用三个值 Fast (1x)、Faster (2x) 或 Fastest (3x) 控制速度。

在维度是数值的情况下,最多可以添加 9 个主题并对每个步骤使用不同的主题,以控制每个速度设置的按钮的文本标签或纹理。

每个 click 事件会使 DimensionIndex 在运行时加 1,直到达到 Dimensions 值。 然后,循环将重置为 0。

Multi-Dimensional profile example

开发人员可以评估 DimensionIndex 以确定当前处于活动状态的维度。

// If using SelectionMode = Multi-dimension (i.e Dimensions >= 3)

//Access the current DimensionIndex
int currentDimension = myInteractable.CurrentDimension;

//Set the current DimensionIndex to 2
myInteractable.CurrentDimension = 2;

// Promote Dimension to next level
myInteractable.IncreaseDimension();

在运行时创建可交互对象

可以在运行时将可交互对象轻松添加到任何 GameObject。 以下示例演示如何分配具有视觉主题的配置文件。

var interactableObject = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
var interactable = interactableObject.AddComponent<Interactable>();

// Get the default configuration for the Theme engine InteractableColorTheme
var newThemeType = ThemeDefinition.GetDefaultThemeDefinition<InteractableColorTheme>().Value;

// Define a color for every state in our Default Interactable States
newThemeType.StateProperties[0].Values = new List<ThemePropertyValue>()
{
    new ThemePropertyValue() { Color = Color.black},  // Default
    new ThemePropertyValue() { Color = Color.black}, // Focus
    new ThemePropertyValue() { Color = Random.ColorHSV()},   // Pressed
    new ThemePropertyValue() { Color = Color.black},   // Disabled
};

interactable.Profiles = new List<InteractableProfileItem>()
{
    new InteractableProfileItem()
    {
        Themes = new List<Theme>()
        {
            Interactable.GetDefaultThemeAsset(new List<ThemeDefinition>() { newThemeType })
        },
        Target = interactableObject,
    },
};

// Force the Interactable to be clicked
interactable.TriggerOnClick()

借助代码的可交互对象事件

可以按照以下示例使用代码向基本 Interactable.OnClick 事件添加操作。

public static void AddOnClick(Interactable interactable)
{
    interactable.OnClick.AddListener(() => Debug.Log("Interactable clicked"));
}

使用 Interactable.AddReceiver<T>() 函数在运行时动态添加事件接收器。

以下示例代码演示如何添加 InteractableOnFocusReceiver 以侦听聚焦进入/退出,并演示如何进一步定义要在事件实例触发时执行的操作代码。

public static void AddFocusEvents(Interactable interactable)
{
    var onFocusReceiver = interactable.AddReceiver<InteractableOnFocusReceiver>();

    onFocusReceiver.OnFocusOn.AddListener(() => Debug.Log("Focus on"));
    onFocusReceiver.OnFocusOff.AddListener(() => Debug.Log("Focus off"));
}

以下示例代码演示如何添加 InteractableOnToggleReceiver 以侦听可切换的可交互对象上的选定/取消选定状态转换,并演示如何进一步定义要在事件实例触发时执行的操作代码

public static void AddToggleEvents(Interactable interactable)
{
    var toggleReceiver = interactable.AddReceiver<InteractableOnToggleReceiver>();

    // Make the interactable have toggle capability, from code.
    // In the gui editor it's much easier
    interactable.Dimensions = 2;
    interactable.CanSelect = true;
    interactable.CanDeselect  = true;

    toggleReceiver.OnSelect.AddListener(() => Debug.Log("Toggle selected"));
    toggleReceiver.OnDeselect.AddListener(() => Debug.Log("Toggle un-selected"));
}

另请参阅