視覺主題 — MRTK2

主題允許彈性控制 UX 資產,以回應各種狀態轉換。 這可能牽涉到變更按鈕的色彩、調整元素大小以回應焦點等等。視覺主題架構是由兩個主要部分所組成:1 個) 組態和 2 個) 執行時間引擎。

主題組態 是屬性和類型的定義,而 主題引擎 是使用組態的類別,並實作邏輯來更新執行時間的轉換、材質等等。

主題設定

主題設定是 ScriptableObjects ,可定義主題引擎在執行時間初始化的方式。 他們會定義哪些屬性和值,以回應應用程式執行時的輸入或其他狀態變更。 身為 ScriptableObjects 資產,主題組態可以定義一次,然後跨不同的 UX 元件重複使用。

若要建立新的 Theme 資產:

  1. 以滑鼠右鍵按一下Project視窗
  2. 選取[建立>Mixed Reality工具組>主題

您可以在 下 MRTK/SDK/Features/UX/Interactable/Themes 找到主題組態資產範例。

Theme ScriptableObject example in inspector

狀態

建立新的 Theme 時,要設定的第一件事是可用的狀態。 States屬性會指出主題設定需要定義多少值,因為每個狀態會有一個值。 在上圖範例中, 針對 [互動] 元件所定義的預設狀態[預設]、[ 焦點]、[ 已按下] 和 [已停用]。 這些定義在資產檔案 (DefaultInteractableStates 資產/MRTK/SDK/功能/UX/Interactable/States) 資產檔案中。

若要建立新的 State 資產:

  1. 以滑鼠右鍵按一下Project視窗
  2. 選取[建立>Mixed Reality工具組>狀態

States ScriptableObject example in inspector

StateScriptableObject 會定義狀態清單,以及要針對這些狀態建立的StateModel類型。 StateModel是一種類別,可擴充 BaseStateModel 及實作狀態機器邏輯,以在執行時間產生目前的狀態。 此類別的目前狀態通常由主題引擎在執行時間使用,以指定要針對材質屬性、GameObject 轉換等設定的值。

主題引擎屬性

狀態之外, Theme 資產也會定義主題引擎的清單,以及這些引擎的相關屬性。 主題引擎會再次定義邏輯,以在執行時間針對 GameObject 設定正確的值。

資產 Theme 可以定義多個主題引擎,以達成以多個 GameObject 屬性為目標的複雜視覺狀態轉換。

主題執行時間

定義將建立之主題引擎的類別類型

寬鬆

某些 主題引擎,如果他們將其屬性 IsEasingSupported 定義為 true,則支援狀態之間的 Easing。 例如,當狀態變更發生時,在兩種色彩之間交錯。 Duration會以秒為單位定義從開始值到結束值之間的輕鬆時間,而動畫曲線會定義該期間內的變更速率。

著色器屬性

如果某些 主題引擎將其屬性 IsShadersSupported 定義為 true,則會在執行時間修改特定的著色器屬性。 著色器和屬性欄位會定義要設為目標的著色器屬性。

透過程式碼建立主題設定

一般而言,透過 Unity 偵測器設計主題設定比較容易,但在某些情況下,主題必須在執行時間透過程式碼動態產生。 下列程式碼片段提供如何完成這項工作的範例。

為了協助加速開發,下列協助程式方法有助於簡化設定。

Interactable.GetDefaultInteractableStates() - 使用 互動 元件中使用的四個預設狀態值,建立新的 States ScriptableObject。

ThemeDefinition.GetDefaultThemeDefinition<T>() - 每個主題引擎都會定義預設組態,其中包含該 Theme 執行時間類型所需的正確屬性。 此協助程式會為指定的主題引擎類型建立定義。

// 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 之間的浮點數用於值之間的 easing/lerping。

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

針對可以識別 ThemeStateProperty.Name 的指定屬性,傳回目標 Host 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 屬性設定的值之間的 easing。

InteractableThemeBase.AreShadersSupported

如果自訂主題引擎可以支援以著色器屬性為目標。 建議您從 InteractableShaderTheme 延伸,以受益于現有的基礎結構,以透過 MaterialPropertyBlocks有效率地設定/取得著色器屬性。 著色器屬性資訊會透過 ThemeStateProperty.TargetShaderThemeStateProperty.ShaderPropertyName 儲存在每個 ThemeStateProperty 中。

注意

如果延伸 InteractableShaderTheme ,也可以透過new覆寫InteractableShaderTheme.DefaultShaderProperty

範例程式碼: protected new const string DefaultShaderProperty = "_Color";

此外,下列類別會擴充 InteractableShaderTheme 類別,其會再次使用 MaterialPropertyBlocks 來修改著色器屬性值。 此方法 有助於效能 ,因為 MaterialPropertyBlocks 不會在值變更時建立新的實例材質。 不過,存取典型的 Material 類別屬性不會傳回預期的值。 使用 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 () 方法中呼叫,以支援在值之間利用 easing/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;
    }
}

另請參閱