Temas visuales: MRTK2

Los temas permiten un control flexible de los recursos de la experiencia de usuario en respuesta a varias transiciones de estados. Esto puede implicar cambiar el color de un botón, cambiar el tamaño de un elemento en respuesta al foco, etc. El marco de temas visuales se compone de dos partes clave: 1) configuración y 2) motores en tiempo de ejecución.

Las configuraciones de tema son definiciones de propiedades y tipos, mientras que Los motores de temas son clases que consumen las configuraciones e implementan la lógica para actualizar transformaciones, materiales y mucho más en tiempo de ejecución.

Configuración del tema

Las configuraciones de tema son ScriptableObjects que definen cómo se inicializarán los motores de temas en tiempo de ejecución. Definen qué propiedades y valores usar en respuesta a los cambios de estado u otros cambios de estado cuando se ejecuta la aplicación. Como recursos scriptableObjects , las configuraciones de tema se pueden definir una vez y, a continuación, volver a usarse en distintos componentes de la experiencia de usuario.

Para crear un nuevo Theme recurso:

  1. Haga clic con el botón derecho en la ventana de Project
  2. Seleccione Crear>Mixed Reality Toolkit>Theme

Los recursos de configuración del tema de ejemplo se pueden encontrar en MRTK/SDK/Features/UX/Interactable/Themes.

Theme ScriptableObject example in inspector

States

Al crear un nuevo Theme, lo primero que se debe establecer es qué estados están disponibles. La propiedad States indica cuántos valores debe definir una configuración del tema, ya que habrá un valor por estado. En la imagen de ejemplo anterior, los estados predeterminados definidos para el componente Interactable son Default, Focus, Pressed y Disabled. Se definen en el DefaultInteractableStates archivo de recursos (Assets/MRTK/SDK/Features/UX/Interactable/States).

Para crear un nuevo State recurso:

  1. Haga clic con el botón derecho en la ventana de Project
  2. Seleccione Crear> Mixed Reality Toolkit State.>

States ScriptableObject example in inspector

Un State ScriptableObject define tanto la lista de estados como el tipo de StateModel que se va a crear para estos estados. StateModel es una clase que extiende BaseStateModel e implementa la lógica de la máquina de estado para generar el estado actual en tiempo de ejecución. El estado actual de esta clase generalmente lo usan los motores de tema en tiempo de ejecución para determinar qué valores establecer en las propiedades de material, las transformaciones GameObject, etc.

Propiedades del motor de temas

Fuera de Estados, un Theme recurso también define una lista de motores de tema y las propiedades asociadas para estos motores. Un motor de tema define de nuevo la lógica para establecer los valores correctos en un GameObject en tiempo de ejecución.

Un Theme recurso puede definir varios motores de temas para lograr transiciones de estados visuales sofisticados destinados a varias propiedades gameObject.

Tiempo de ejecución del tema

Define el tipo de clase del motor de tema que se creará.

Facilitar

Algunos motores de temas, si definen su propiedad IsEasingSupported como true, admiten la aceleración entre estados. Por ejemplo, si se produce un cambio de estado entre dos colores. La duración define en segundos cuánto tiempo se debe facilitar desde el valor inicial hasta el valor final y la curva de animación define la tasa de cambio durante ese período de tiempo.

Propiedades del sombreador

Algunos motores de temas, si definen su propiedad AreShadersSupported como true, modificarán determinadas propiedades de sombreador en tiempo de ejecución. Los campos Sombreador y Propiedad definen la propiedad de sombreador de destino.

Creación de una configuración de tema mediante código

En general, es más fácil diseñar configuraciones de tema a través del inspector de Unity, pero hay casos en los que Los temas se deben generar dinámicamente en tiempo de ejecución a través del código. El fragmento de código siguiente proporciona un ejemplo de cómo realizar esta tarea.

Para ayudar a acelerar el desarrollo, los siguientes métodos auxiliares son útiles para simplificar la configuración.

Interactable.GetDefaultInteractableStates() : crea un nuevo States ScriptableObject con los cuatro valores de estado predeterminados usados en el componente Interactable .

ThemeDefinition.GetDefaultThemeDefinition<T>() - Cada motor de temas define una configuración predeterminada con las propiedades correctas necesarias para ese tipo de tiempo de ejecución theme. Este asistente crea una definición para el tipo de motor de tema determinado.

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

Motores de temas

Un motor de temas es una clase que se extiende desde la InteractableThemeBase clase . Estas clases se crean instancias en tiempo de ejecución y se configuran con un ThemeDefinition objeto como se describió anteriormente.

Motores de temas predeterminados

MRTK se incluye con un conjunto predeterminado de motores de temas que se enumeran a continuación:

Los motores de temas predeterminados se pueden encontrar en MRTK/SDK/Features/UX/Scripts/VisualThemes/ThemeEngines.

Motores de temas personalizados

Como se indica, un motor de temas se define como una clase que se extiende desde la InteractableThemeBase clase . Por lo tanto, el nuevo motor de temas solo necesita ampliar esta clase e implementar lo siguiente:

Implementaciones obligatorias

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

Para la propiedad especificada, que se puede identificar mediante ThemeStateProperty.Name, establezca su valor de estado actual en el host GameObject de destino (es decir, establecer el color del material, etc.). El índice indica el valor de estado actual para tener acceso y el porcentaje, un valor float entre 0 y 1, se usa para facilitar olerping entre valores.

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

Para la propiedad especificada, que se puede identificar mediante ThemeStateProperty.Name, devuelve el valor actual establecido en el objeto GameObject host de destino (es decir, el color del material actual, el desplazamiento de posición local actual, etc.). Esto se usa principalmente para almacenar en caché el valor inicial cuando se acelera entre estados.

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

Devuelve un ThemeDefinition objeto que define las propiedades y la configuración predeterminadas necesarias para el tema personalizado.

protected abstract void SetValue(ThemeStateProperty property, ThemePropertyValue value)

Variante protegida de la definición pública SetValue() , excepto que se proporcionó ThemePropertyValue para establecer en lugar de dirigir a usar la configuración de índice o porcentaje.

InteractableThemeBase.Init(GameObject host, ThemeDefinition settings)

Realice los pasos de inicialización aquí destinados al parámetro GameObject proporcionado y use las propiedades y configuraciones definidas en el parámetro ThemeDefinition . Se recomienda llamar base.Init(host, settings) al principio de una invalidación.

InteractableThemeBase.IsEasingSupported

Si el motor de temas personalizado puede admitir la aceleración entre los valores que se configuran a través de la ThemeDefinition.Easing propiedad .

InteractableThemeBase.AreShadersSupported

Si el motor de temas personalizado puede admitir propiedades del sombreador de destino. Se recomienda ampliar desde para beneficiarse de InteractableShaderTheme la infraestructura existente para establecer o obtener propiedades de sombreador de forma eficaz a través de MaterialPropertyBlocks. La información de la propiedad del sombreador se almacena en cada ThemeStateProperty a través ThemeStateProperty.TargetShader de y ThemeStateProperty.ShaderPropertyName.

Nota

Si extiende InteractableShaderTheme, también puede ser útil invalidar InteractableShaderTheme.DefaultShaderProperty a través de nuevo.

Código de ejemplo: protected new const string DefaultShaderProperty = "_Color";

Además, las siguientes clases amplían la InteractableShaderTheme clase que de nuevo usa MaterialPropertyBlocks para modificar los valores de propiedad del sombreador. Este enfoque ayuda al rendimiento porque MaterialPropertyBlocks no crea nuevos materiales con instancias cuando cambian los valores. Sin embargo, el acceso a las propiedades típicas de la clase Material no devolverá valores esperados. Use MaterialPropertyBlocks para obtener y validar los valores de propiedad de material actuales (es decir , _Color o _MainTex).

InteractableThemeBase.Reset

Dirige el tema para restablecer las propiedades modificadas a sus valores originales que se establecieron en el gameObject host cuando se inicializó este motor de temas.

Ejemplo del motor de temas personalizado

La siguiente clase es un ejemplo de un nuevo motor de temas personalizado. Esta implementación encontrará un componente MeshRenderer en el objeto host inicializado y controlará su visibilidad en función del estado actual.

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

Ejemplo de un extremo a otro

Al extenderse fuera del motor de temas personalizado definido en la sección anterior, el ejemplo de código siguiente muestra cómo controlar este tema en tiempo de ejecución. En concreto, cómo establecer el estado actual en el tema para que la visibilidad de MeshRenderer se actualice correctamente.

Nota

theme.OnUpdate(state,force) por lo general, se debe llamar en el método Update() para admitir motores de tema que usan aceleración olerping entre valores.

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

Consulta también