視覺主題 — MRTK2
主題允許彈性控制 UX 資產,以回應各種狀態轉換。 這可能牽涉到變更按鈕的色彩、調整元素大小以回應焦點等等。視覺主題架構是由兩個主要部分所組成:1 個) 組態和 2 個) 執行時間引擎。
主題組態 是屬性和類型的定義,而 主題引擎 是使用組態的類別,並實作邏輯來更新執行時間的轉換、材質等等。
主題組態
主題組態是 ScriptableObjects ,可定義主題引擎在執行時間初始化的方式。 它們會定義哪些屬性和值,以回應應用程式執行時的輸入或其他狀態變更。 身為 ScriptableObjects 資產,主題組態可以定義一次,然後跨不同的 UX 元件重複使用。
若要建立新的 Theme
資產:
- 在專案視窗中按一下滑鼠右鍵
- 選取[建立>Mixed Reality工具組>主題
您可以在 下 MRTK/SDK/Features/UX/Interactable/Themes
找到主題組態資產範例。
狀態
建立新的 Theme
時,要設定的第一件事是可用的狀態。
States屬性會指出主題設定需要定義多少值,因為每個狀態會有一個值。 在上述範例影像 中,針對 Interactable 元件定義的預設狀態 為 Default、 Focus、 Pressed和 Disabled。 這些定義于 DefaultInteractableStates
資產 (/MRTK/SDK/Features/UX/Interactable/States) 資產檔案中。
若要建立新的 State
資產:
- 在專案視窗中按一下滑鼠右鍵
- 選取[建立>Mixed Reality工具組>狀態
State
ScriptableObject 會定義狀態清單,以及要針對這些狀態建立的StateModel類型。
StateModel是一種類別,可擴充 BaseStateModel
並實作狀態機器邏輯,以在執行時間產生目前狀態。 執行時間的主題引擎通常會使用此類別的目前狀態,以決定要針對材質屬性、GameObject 轉換等設定的值。
主題引擎屬性
在 狀態之外, Theme
資產也會定義主題引擎的清單,以及這些引擎的相關屬性。
主題引擎會再次定義邏輯,以在執行時間針對 GameObject 設定正確的值。
Theme
資產可以定義多個主題引擎,以達到以多個 GameObject 屬性為目標的複雜視覺狀態轉換。
主題執行時間
定義將建立之主題引擎的類別類型
寬鬆
某些 主題引擎如果將其屬性 IsEasingSupported 定義為 true,則支援狀態之間的 Easing。 例如,發生狀態變更時,在兩種色彩之間切換。 Duration會以秒為單位定義從開始值到結束值之間的輕鬆時間,而動畫曲線會定義該期間內的變更速率。
著色器屬性
如果某些 主題引擎將其屬性 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 };
主題引擎
Theme Engine是從 類別擴充的 InteractableThemeBase
類別。 這些類別會在執行時間具現化,並以稍早所述的 物件進行設定 ThemeDefinition
。
預設主題引擎
MRTK 隨附一組預設的主題引擎,如下所示:
InteractableActivateTheme
InteractableAnimatorTheme
InteractableAudioTheme
InteractableColorChildrenTheme
InteractableColorTheme
InteractableGrabScaleTheme
InteractableMaterialTheme
InteractableOffsetTheme
InteractableRotationTheme
InteractableScaleTheme
InteractableShaderTheme
InteractableStringTheme
InteractableTextureTheme
ScaleOffsetColorTheme
您可以在 下 MRTK/SDK/Features/UX/Scripts/VisualThemes/ThemeEngines
找到預設的主題引擎。
自訂主題引擎
如前所述,Theme Engine 會定義為從 類別延伸的 InteractableThemeBase
類別。 因此,新的主題引擎只需要擴充此類別並實作下列專案:
強制實作
public abstract void SetValue(ThemeStateProperty property, int index, float percentage)
對於可識別 ThemeStateProperty.Name
的指定屬性,請在目標 GameObject 主機上設定其目前狀態值 (亦即設定材質色彩等) 。
索引會指出要存取的目前狀態值,以及介於 0 到 1 之間的浮點數,用於值之間的 Easing/lerping。
public abstract ThemePropertyValue GetProperty(ThemeStateProperty property)
對於可識別 ThemeStateProperty.Name
的指定屬性,傳回目標 Host GameObject (上設定的目前值,也就是目前的材質色彩、目前的本機位置位移等) 。 這主要用於在狀態之間緩和時快取開始值。
public abstract ThemeDefinition 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.ShaderPropertyName
儲存在每個 ThemeStateProperty
ThemeStateProperty.TargetShader
中。
注意
如果擴充 InteractableShaderTheme
,則透過new覆寫InteractableShaderTheme.DefaultShaderProperty也很有用。
範例程式碼: protected new const string DefaultShaderProperty = "_Color";
此外,下列類別會擴充 InteractableShaderTheme
類別,該類別會再次使用 MaterialPropertyBlocks 來修改著色器屬性值。 此方法 有助於效能 ,因為 MaterialPropertyBlocks 不會在值變更時建立新的實例材質。 不過,存取一般 Material 類別屬性並不會傳回預期的值。 使用 MaterialPropertyBlocks 取得和驗證目前的材質屬性值 (,例如 _Color 或 _MainTex) 。
指示主題將任何已修改的屬性重設為在初始化此主題引擎時,在主機 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;
}
}