视觉主题 - MRTK2

主题支持灵活控制用户体验资产,以响应各种状态转换。 这可能涉及更改按钮颜色、调整元素大小以响应焦点等。视觉主题框架由两个关键部分组成:1) 配置和 2) 运行时引擎。

主题配置是属性和类型的定义,而主题引擎是使用配置并实现逻辑以在运行时更新转换、材料等的类。

主题配置

主题配置是 ScriptableObjects,用于定义运行时初始化主题引擎的方式。 它们定义在应用运行时响应输入或其他状态更改时要使用的属性和值。 作为 ScriptableObjects 资产,主题配置只能定义一次,然后在不同的用户体验组件之间重复使用。

如需创建新Theme资产,请执行以下操作:

  1. 在“项目窗口”中右键单击
  2. 选择“创建”>“混合现实工具包”>“主题”

示例主题配置资产位于 MRTK/SDK/Features/UX/Interactable/Themes 下。

Theme ScriptableObject example in inspector

状态

创建新的 Theme 时,要设置的第一项是可用的状态。 States 属性指示主题配置需要定义多少值(因为每个状态有一个值)。 在上面的示例图像中,为可交互组件定义的 默认状态为“默认”、“焦点”、“按下”和“已禁用”。 这些在 DefaultInteractableStates (Assets/MRTK/SDK/Features/UX/Interactable/States) 资产文件中定义。

如需创建新State资产,请执行以下操作:

  1. 在“项目窗口”中右键单击
  2. 选择“创建”>“混合现实工具包”>“状态”

States ScriptableObject example in inspector

State ScriptableObject 定义状态列表以及为这些状态创建的 StateModel 类型。 StateModel 是扩展 BaseStateModel 并实现状态计算机逻辑以生成运行时当前状态的类。 此类的当前状态通常由运行时的主题引擎用来指示要针对材料属性、GameObject 转换等设置哪些值。

主题引擎属性

除了“状态”,Theme 资产还为这些引擎定义了一系列主题引擎和关联属性。 主题引擎还定义逻辑,以在运行时针对 GameObject 设置正确的值。

Theme 资产可以定义多个主题引擎,以实现针对多个 GameObject 属性的复杂视觉状态转换。

“主题运行时”

定义将创建的主题引擎的类类型

“缓动”

一些主题引擎(如果它们将其属性 IsEasingSupported 定义为 true,则支持在状态间缓动。 例如,当发生状态更改时,两种颜色之间 lerping。 持续时间定义从开始值到结束值的缓动时间(以秒为单位),动画曲线定义该时间段内的变化率。

“着色器属性”

一些主题引擎(如果将其属性 AreShadersSupported 定义为 true),则将在运行时修改特定的着色器属性。 “着色器”和“属性”字段定义要针对的着色器属性。

通过代码创建主题配置

通常,通过 Unity 检查器设计主题配置更简单,但在某些情况下,必须通过代码在运行时动态生成主题。 下面的代码片段提供了如何完成此任务的示例。

为了帮助加速开发,以下帮助程序方法可用于简化设置。

Interactable.GetDefaultInteractableStates() - 使用可交互组件中使用的四个默认状态值创建新的 States ScriptableObject。

ThemeDefinition.GetDefaultThemeDefinition<T>() - 每个主题引擎定义具有该主题运行时类型所需的正确属性的默认配置。 此帮助程序为给定的主题引擎类型创建定义。

// This code example builds a Theme ScriptableObject that can be used with an Interactable component.
// A random color is selected for the on pressed state every time this code is executed.

// Use the default states utilized in the Interactable component
var defaultStates = Interactable.GetDefaultInteractableStates();

// 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
};

// Create the Theme configuration asset
Theme testTheme = ScriptableObject.CreateInstance<Theme>();
testTheme.States = defaultStates;
testTheme.Definitions = new List<ThemeDefinition>() { newThemeType };

主题引擎

主题引擎是从 InteractableThemeBase 类扩展的类。 这些类在运行时进行实例化,并使用前面所述的 ThemeDefinition 对象进行配置。

默认主题引擎

MRTK 附带了一组默认主题引擎,如下所示:

可以在 MRTK/SDK/Features/UX/Scripts/VisualThemes/ThemeEngines 下找到默认主题引擎。

自定义主题引擎

如前文所述,主题引擎定义为从 InteractableThemeBase 类扩展的类。 因此,新的主题引擎只需扩展此类并实现以下内容:

强制实现

public abstract void SetValue(ThemeStateProperty property, int index, float percentage) (xref:Microsoft.MixedReality.Toolkit.UI.InteractableThemeBase.SetValue)

对于给定属性(可以用 ThemeStateProperty.Name 标识该属性),在目标 GameObject 主机上设置其当前状态值(例如,设置材料颜色等)。 索引指示要访问的当前状态值,百分比(0 和 1 之间的浮点值)用于在值之间缓动/lerping。

public abstract ThemePropertyValue GetProperty(ThemeStateProperty property)(xref:Microsoft.MixedReality.Toolkit.UI.InteractableThemeBase.GetProperty)

对于给定属性(可以用 ThemeStateProperty.Name 标识该属性),返回在目标主机 GameObject 上设置的当前值(例如当前材料颜色、当前本地位置偏移量等)。 这主要用于在状态之间缓动时缓冲起始值。

public abstract ThemeDefinition GetDefaultThemeDefinition()(xref:Microsoft.MixedReality.Toolkit.UI.InteractableThemeBase.GetDefaultThemeDefinition)

返回一个 ThemeDefinition 对象,该对象定义自定义主题所需的默认属性和配置

protected abstract void SetValue(ThemeStateProperty property, ThemePropertyValue value)

公共 SetValue() 定义的受保护变体,除了要设置的提供的 ThemePropertyValue,而不是使用索引和/或百分比配置。

InteractableThemeBase.Init(GameObject host, ThemeDefinition settings)

执行此处的任何初始化步骤,针对提供的 GameObject 参数并使用 ThemeDefinition 参数中定义的属性和配置。 建议在替代开始时调用 base.Init(host, settings)

InteractableThemeBase.IsEasingSupported

如果自定义主题引擎可支持通过 ThemeDefinition.Easing 属性配置的值之间的缓动。

InteractableThemeBase.AreShadersSupported

如果自定义主题引擎可支持针对着色器属性。 建议从 InteractableShaderTheme 扩展以利用现有基础结构通过 MaterialPropertyBlocks 高效设置/获取着色器属性。 着色器属性信息通过 ThemeStateProperty.TargetShaderThemeStateProperty.ShaderPropertyName 存储在每个 ThemeStateProperty 中。

注意

如果扩展 InteractableShaderTheme,则通过“新建” 替代 InteractableShaderTheme.DefaultShaderProperty 也是有用的。

示例代码:protected new const string DefaultShaderProperty = "_Color";

此外,以下类扩展 InteractableShaderTheme 类,后者再次使用 MaterialPropertyBlocks 来修改着色器属性值。 此方法有助于性能,因为 MaterialPropertyBlocks 不会在值更改时创建新实例材料。 但是,访问典型的材料类属性将不会返回预期值。 使用 MaterialPropertyBlocks 获取和验证当前材料属性值(例如 _Color 或 _MainTex)。

InteractableThemeBase.Reset

指示主题将任何修改后的属性重置回其原始值(在此主题引擎初始化时,在主机 GameObject 上设置)。

自定义主题引擎示例

下面的类是一个自定义新主题引擎的示例。 此实现将查找已初始化主机对象上的 MeshRenderer 组件,并根据当前状态控制其可见性。

using Microsoft.MixedReality.Toolkit.UI;
using System;
using System.Collections.Generic;
using UnityEngine;

// This class demonstrates a custom theme to control a Host's MeshRenderer visibility
public class MeshVisibilityTheme : InteractableThemeBase
{
    // Bool visibility does not make sense for lerping
    public override bool IsEasingSupported => false;

    // No material or shaders are being modified
    public override bool AreShadersSupported => false;

    // Cache reference to the MeshRenderer component on our Host
    private MeshRenderer meshRenderer;

    public MeshVisibilityTheme()
    {
        Types = new Type[] { typeof(MeshRenderer) };
        Name = "Mesh Visibility Theme";
    }

    // Define a default configuration to simplify initialization of this theme engine
    // There is only one state property with a value per available state
    // This state property is a boolean that defines whether the renderer is enabled
    public override ThemeDefinition GetDefaultThemeDefinition()
    {
        return new ThemeDefinition()
        {
            ThemeType = GetType(),
            StateProperties = new List<ThemeStateProperty>()
            {
                new ThemeStateProperty()
                {
                    Name = "Mesh Visible",
                    Type = ThemePropertyTypes.Bool,
                    Values = new List<ThemePropertyValue>(),
                    Default = new ThemePropertyValue() { Bool = true }
                },
            },
            CustomProperties = new List<ThemeProperty>()
        };
    }

    // When initializing, cache a reference to the MeshRenderer component
    public override void Init(GameObject host, ThemeDefinition definition)
    {
        base.Init(host, definition);

        meshRenderer = host.GetComponent<MeshRenderer>();
    }

    // Get the current state of the MeshRenderer visibility
    public override ThemePropertyValue GetProperty(ThemeStateProperty property)
    {
        return new ThemePropertyValue()
        {
            Bool = meshRenderer.enabled
        };
    }

    // Update the MeshRenderer visibility based on the property state value data
    public override void SetValue(ThemeStateProperty property, int index, float percentage)
    {
        meshRenderer.enabled = property.Values[index].Bool;
    }
}

端到端示例

下面的代码示例扩展了前面部分中定义的自定义主题引擎,演示了如何在运行时控制此主题。 具体而言,如何设置主题的当前状态,确保恰当更新 MeshRenderer 可见性。

注意

theme.OnUpdate(state,force) 应通常在 Update() 方法中调用,以支持利用值之间缓动/lerping 的主题引擎。

using Microsoft.MixedReality.Toolkit.UI;
using System;
using System.Collections.Generic;
using UnityEngine;

public class MeshVisibilityController : MonoBehaviour
{
    private MeshVisibilityTheme themeEngine;
    private bool hideMesh = false;

    private void Start()
    {
        // Define the default configuration. State 0 will be on while State 1 will be off
        var themeDefinition = ThemeDefinition.GetDefaultThemeDefinition<MeshVisibilityTheme>().Value;
        themeDefinition.StateProperties[0].Values = new List<ThemePropertyValue>()
        {
            new ThemePropertyValue() { Bool = true }, // show state
            new ThemePropertyValue() { Bool = false }, // hide state
        };

        // Create the actual Theme engine and initialize it with the GameObject we are attached to
        themeEngine = (MeshVisibilityTheme)InteractableThemeBase.CreateAndInitTheme(themeDefinition, this.gameObject);
    }

    private void Update()
    {
        // Update the theme engine to set our MeshRenderer visibility
        // based on our current state (i.e the hideMesh variable)
        themeEngine.OnUpdate(Convert.ToInt32(hideMesh));
    }

    public void ToggleVisibility()
    {
        // Alternate state of visibility
        hideMesh = !hideMesh;
    }
}

请参阅