ビジュアル テーマ — MRTK2

テーマを使用すると、さまざまな状態遷移に応じて、UX アセットを柔軟に制御できます。 これには、ボタンの色の変更、フォーカスに応じた要素のサイズ変更などが含まれます。ビジュアル テーマ フレームワークは、1) 構成と 2) ランタイム エンジンという 2 つの主要部分で構成されます。

テーマ構成はプロパティと型の定義であり、一方テーマ エンジンは、構成を使用し、実行時に変換やマテリアルを更新するロジックを実装するクラスです。

テーマの構成

テーマの構成は、実行時にテーマ エンジンを初期化する方法を定義する ScriptableObjects です。 これらにより、アプリの実行時に入力またはその他の状態変更に応じて使用するプロパティと値が定義されます。 テーマの構成は、ScriptableObjects アセットとして一度定義してから、異なる UX コンポーネントに対して再利用できます。

新しい Theme アセットを作成するには:

  1. プロジェクト ウィンドウ内を右クリックします
  2. [作成]>[Mixed Reality ツールキット]>[テーマ] を選択します

MRTK/SDK/Features/UX/Interactable/Themes で、テーマ構成アセットの例を確認できます。

インスペクターの Theme ScriptableObject の例

状態

新しい Theme を作成する場合、最初に設定するのは、使用可能な状態です。 状態ごとに 1 つの値があるため、States プロパティにより、テーマの構成で定義する必要がある値の数を示します。 上の例の図では、Interactable コンポーネントに対して定義されている既定の状態は、DefaultFocusPressedDisabled です。 これらは、DefaultInteractableStates (Assets/MRTK/SDK/Features/UX/Interactable/States) アセット ファイルで定義されています。

新しい State アセットを作成するには:

  1. プロジェクト ウィンドウ内を右クリックします
  2. [作成]>[Mixed Reality ツールキット]>[状態] を選択します

インスペクターの States ScriptableObject の例

State ScriptableObject では、状態の一覧と、それらの状態に対して作成する StateModel の種類の両方が定義されます。 StateModel は、BaseStateModel を拡張したクラスであり、実行時に現在の状態を生成するステート マシン ロジックを実装します。 このクラスからの現在の状態は、マテリアルのプロパティ、GameObject の変換などに対して設定する値を指定するために、実行時にテーマ エンジンによって通常使用されます。

テーマ エンジンのプロパティ

States の他に Theme アセットでも、テーマ エンジンの一覧と、それらのエンジンの関連プロパティが定義されます。 テーマ エンジンにより、実行時に GameObject に対して正しい値を設定するロジックが再び定義されます。

Theme アセットでは、複数の GameObject プロパティを対象とした高度な表示状態遷移を実現するために、複数のテーマ エンジンを定義できます。

テーマ ランタイム

作成されるテーマ エンジンのクラス型を定義します

イージング

一部のテーマ エンジンで、その IsEasingSupported プロパティが true として定義されている場合には、状態間のイージングがサポートされます。 たとえば、状態が変更されたときの 2 つの色の間の Lerp 処理などです。 Duration によって、開始値から終了値までイージングを行う期間 (秒単位) が定義され、Animation Curve によって、その期間中の変化率が定義されます。

シェーダーのプロパティ

一部のテーマ エンジンで、その AreShadersSupported プロパティが true として定義されている場合には、実行時に特定のシェーダー プロパティが変更されます。 [シェーダー] および [プロパティ] フィールドにより、対象となるシェーダー プロパティが定義されます。

コードを使用してテーマの構成を作成する

一般に、Unity インスペクターを使用してテーマの構成を設計する方が簡単ですが、コードを使用して実行時に動的にテーマを生成する必要がある場合もあります。 次のコード スニペットは、このタスクを実行する方法の例を示しています。

開発を迅速に行うために、次のヘルパー メソッドを使用すると、設定を簡単に行うことができます。

Interactable.GetDefaultInteractableStates() - Interactable コンポーネントで使用される 4 つの既定の状態値を含む新しい 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)

ThemeStateProperty.Name によって識別できる特定プロパティに対して、対象の GameObject ホストの現在の状態値を設定します (マテリアルの色を設定するなど)。 index は、アクセスする現在の状態値を示します。また、値間のイージング/Lerp 処理のために、percentage (0 から 1 までの float 値) が使用されます。

public abstract ThemePropertyValue GetProperty(ThemeStateProperty property)

ThemeStateProperty.Name によって識別できる特定プロパティに対して、対象のホスト GameObject で設定されている現在の値を返します (現在のマテリアルの色、現在のローカル位置オフセットなど)。 これは、状態間でイージングを行う場合の開始値をキャッシュするために主に使用されます。

public abstract ThemeDefinition GetDefaultThemeDefinition()

カスタム テーマに必要な既定のプロパティと構成を定義する ThemeDefinition オブジェクトを返します

protected abstract void SetValue(ThemeStateProperty property, ThemePropertyValue value)

パブリック SetValue() 定義の保護されたバリアント。異なるのは、index、percentage、またはこれらの両方の構成を使用するように指定する代わりに ThemePropertyValue を設定する点です。

InteractableThemeBase.Init(GameObject host, ThemeDefinition settings)

ここで、指定の GameObject パラメーターを対象とした初期化手順を、ThemeDefinition パラメーターで定義されているプロパティと構成を使用して実行します。 オーバーライドの開始時に base.Init(host, settings) を呼び出すことをお勧めします。

InteractableThemeBase.IsEasingSupported

カスタム テーマ エンジンが、ThemeDefinition.Easing プロパティを使用して構成される値間のイージングをサポートできる場合。

InteractableThemeBase.AreShadersSupported

カスタム テーマ エンジンが、対象となるシェーダー プロパティをサポートできる場合。 既存のインフラストラクチャを活用して、MaterialPropertyBlocks を介してシェーダー プロパティを効率的に設定/取得するために、InteractableShaderTheme を拡張することをお勧めします。 シェーダー プロパティ情報は、ThemeStateProperty.TargetShaderThemeStateProperty.ShaderPropertyName を介して各 ThemeStateProperty に保存されます。

Note

InteractableShaderTheme を拡張する場合、new を介して InteractableShaderTheme.DefaultShaderProperty をオーバーライドすると便利な場合があります。

コードの例: protected new const string DefaultShaderProperty = "_Color";

さらに、シェーダー プロパティ値を変更するために、次のクラスで、MaterialPropertyBlocks を再使用する InteractableShaderTheme クラスを拡張します。 この方法では、値の変更時に 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 の表示を適切に更新するために、テーマで現在の状態を設定する方法が示されています。

Note

値間でのイージング/Lerp 処理を使用するテーマ エンジンをサポートするために、通常、Update() メソッドで theme.OnUpdate(state,force) を呼び出す必要があります。

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

関連項目