Temi visivi - MRTK2

I temi consentono un controllo flessibile degli asset dell'esperienza utente in risposta alle transizioni di vari stati. Ciò può comportare la modifica del colore di un pulsante, il ridimensionamento di un elemento in risposta allo stato attivo e così via. Il framework dei temi visivi è costituito da due componenti chiave: 1) configurazione e 2) motori di runtime.

Le configurazioni dei temi sono definizioni di proprietà e tipi, mentre i motori dei temi sono classi che utilizzano le configurazioni e implementano la logica per aggiornare trasformazioni, materiali e altro ancora in fase di esecuzione.

Configurazione del tema

Le configurazioni dei temi sono ScriptableObject che definiscono la modalità di inizializzazione dei motori dei temi in fase di esecuzione. Definiscono le proprietà e i valori da usare in risposta a modifiche di input o di altro stato quando l'app è in esecuzione. Come asset ScriptableObjects , le configurazioni dei temi possono essere definite una sola volta e quindi riutilizzate tra diversi componenti dell'esperienza utente.

Per creare un nuovo Theme asset:

  1. Fare clic con il pulsante destro del mouse nella finestra del progetto
  2. Selezionare Crea> Realtà mistaTematoolkit>

Gli asset di configurazione del tema di esempio sono disponibili in MRTK/SDK/Features/UX/Interactable/Themes.

Esempio di Theme ScriptableObject nel controllo

Stati

Quando si crea un nuovo Themeoggetto , la prima cosa da impostare è costituita dagli stati disponibili. La proprietà States indica il numero di valori che una configurazione theme deve definire perché sarà presente un valore per stato. Nell'immagine di esempio precedente, gli stati predefiniti definiti per il componente Interactable sono Default, Focus, Pressed e Disabled. Questi sono definiti nel file di DefaultInteractableStates asset (Assets/MRTK/SDK/Features/UX/Interactable/States).

Per creare un nuovo State asset:

  1. Fare clic con il pulsante destro del mouse nella finestra del progetto
  2. Selezionare Crea>Realtà mista>Toolkit State

Esempio di ScriptableObject negli stati nel controllo

ScriptableObject State definisce sia l'elenco di stati che il tipo di StateModel da creare per questi stati. StateModel è una classe che estende BaseStateModel e implementa la logica della macchina a stati per generare lo stato corrente in fase di esecuzione. Lo stato corrente di questa classe viene in genere usato dai motori di tema in fase di esecuzione per determinare quali valori impostare su proprietà materiali, trasformazioni GameObject e altro ancora.

Proprietà del motore dei temi

All'esterno degli Stati, un Theme asset definisce anche un elenco di motori tema e le proprietà associate per questi motori. Un motore tema definisce di nuovo la logica per impostare i valori corretti su un GameObject in fase di esecuzione.

Un Theme asset può definire più motori di tema per ottenere transizioni sofisticate di stati visivi destinati a più proprietà GameObject.

Runtime del tema

Definisce il tipo di classe del motore Theme che verrà creato

Allentamento

Alcuni motori di tema, se definiscono la proprietà IsEasingSupported come true, supportano l'interpolazione tra gli stati. Ad esempio, la larptura tra due colori quando si verifica una modifica dello stato. La durata definisce in secondi per quanto tempo è possibile passare dal valore iniziale al valore finale e la curva di animazione definisce la frequenza di modifica durante tale periodo di tempo.

Proprietà shader

Alcuni motori di tema, se definiscono la proprietà AreShadersSupported come true, modificheranno specifiche proprietà dello shader in fase di esecuzione. I campi Shader e Property definiscono la proprietà shader di destinazione.

Creare una configurazione del tema tramite codice

In generale, è più semplice progettare le configurazioni del tema tramite il controllo Unity, ma esistono casi in cui i temi devono essere generati dinamicamente in fase di esecuzione tramite codice. Il frammento di codice seguente fornisce un esempio di come eseguire questa attività.

Per accelerare lo sviluppo, i metodi helper seguenti sono utili per semplificare la configurazione.

Interactable.GetDefaultInteractableStates() : crea un nuovo Oggetto ScriptableObject con i quattro valori di stato predefiniti usati nel componente Interactable .

ThemeDefinition.GetDefaultThemeDefinition<T>() - Ogni motore di temi definisce una configurazione predefinita con le proprietà corrette necessarie per il tipo di runtime Theme. Questo helper crea una definizione per il tipo di motore del tema specificato.

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

Motori dei temi

Un motore di tema è una classe che si estende dalla InteractableThemeBase classe . Queste classi vengono create istanze in fase di esecuzione e configurate con un ThemeDefinition oggetto come descritto in precedenza.

Motori dei temi predefiniti

MRTK viene fornito con un set predefinito di motori tema elencati di seguito:

I motori dei temi predefiniti sono disponibili in MRTK/SDK/Features/UX/Scripts/VisualThemes/ThemeEngines.

Motori di tema personalizzati

Come indicato, un motore di tema è definito come una classe che si estende dalla InteractableThemeBase classe . Di conseguenza, il nuovo motore di temi deve estendere solo questa classe e implementare quanto segue:

Implementazioni obbligatorie

public abstract void SetValue(ThemeStateProperty property, int index, float percentage)

Per la proprietà specificata, che può essere identificata da ThemeStateProperty.Name, impostarne il valore di stato corrente sull'host GameObject di destinazione ,ad esempio impostare il colore del materiale e così via. L'indice indica il valore di stato corrente per accedere e la percentuale, un valore float compreso tra 0 e 1, viene usato per l'interpolazione/riduzione tra i valori.

public abstract ThemePropertyValue GetProperty(ThemeStateProperty property)

Per la proprietà specificata, che può essere identificata da ThemeStateProperty.Name, restituire il valore corrente impostato sul GameObject host di destinazione (ad esempio il colore del materiale corrente, l'offset della posizione locale corrente e così via). Viene usato principalmente per memorizzare nella cache il valore iniziale durante l'interpolazione tra gli stati.

public abstract ThemeDefinition GetDefaultThemeDefinition()

Restituisce un ThemeDefinition oggetto che definisce le proprietà e la configurazione predefinite necessarie per il tema personalizzato

protected abstract void SetValue(ThemeStateProperty property, ThemePropertyValue value)

Variante protetta della definizione pubblica SetValue() , ad eccezione di ThemePropertyValue fornita da impostare invece di indirizzare all'uso della configurazione di indice e/o percentuale.

InteractableThemeBase.Init(GameObject host, ThemeDefinition settings)

Eseguire tutti i passaggi di inizializzazione in questo caso destinati al parametro GameObject specificato e usando le proprietà e le configurazioni definite nel parametro ThemeDefinition . È consigliabile chiamare base.Init(host, settings) all'inizio di un override.

InteractableThemeBase.IsEasingSupported

Se il motore dei temi personalizzato può supportare l'interpolazione tra i valori configurati tramite la ThemeDefinition.Easing proprietà .

InteractableThemeBase.AreShadersSupported

Se il motore dei temi personalizzato può supportare le proprietà dello shader. È consigliabile estendere da InteractableShaderTheme per trarre vantaggio dall'infrastruttura esistente per impostare/ottenere in modo efficiente le proprietà dello shader tramite MaterialPropertyBlocks. Le informazioni sulle proprietà dello shader vengono archiviate in ogni ThemeStateProperty oggetto tramite ThemeStateProperty.TargetShader e ThemeStateProperty.ShaderPropertyName.

Nota

Se si estende InteractableShaderTheme, può anche essere utile eseguire l'override di InteractableShaderTheme.DefaultShaderProperty tramite new.

Codice di esempio: protected new const string DefaultShaderProperty = "_Color";

Inoltre, le classi seguenti estendono la InteractableShaderTheme classe che usa di nuovo MaterialPropertyBlocks per modificare i valori delle proprietà dello shader. Questo approccio consente le prestazioni perché MaterialPropertyBlocks non crea nuovi materiali di istanza quando i valori cambiano. Tuttavia, l'accesso alle proprietà tipiche della classe Material non restituirà i valori previsti. Usare MaterialPropertyBlocks per ottenere e convalidare i valori delle proprietà dei materiali correnti,ad esempio _Color o _MainTex.

InteractableThemeBase.Reset

Indirizza il tema a reimpostare le proprietà modificate sui valori originali impostati nell'host GameObject quando il motore del tema è stato inizializzato.

Esempio di motore del tema personalizzato

La classe seguente è un esempio di un nuovo motore di tema personalizzato. Questa implementazione troverà un componente MeshRenderer nell'oggetto host inizializzato e ne controlla la visibilità in base allo stato corrente.

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

Esempio end-to-end

Estendendo il motore del tema personalizzato definito nella sezione precedente, l'esempio di codice seguente illustra come controllare questo tema in fase di esecuzione. In particolare, come impostare lo stato corrente sul tema in modo che la visibilità meshRenderer venga aggiornata in modo appropriato.

Nota

theme.OnUpdate(state,force) in genere deve essere chiamato nel metodo Update() per supportare i motori di tema che utilizzano l'interpolazione/riduzione tra i valori.

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

Vedi anche