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 trabajo 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 de 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 Proyecto.
  2. Seleccione Crear>Mixed Reality Tema del kit de herramientas>.

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

Ejemplo de ScriptableObject del tema en el 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 de 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 Proyecto.
  2. Seleccione Create Mixed Reality ToolkitState(Crear>Mixed Reality Estado del kit de herramientas).>

Ejemplo scriptableObject de estados en el 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 se usa generalmente por Los motores de tema en tiempo de ejecución para dictar qué valores establecer en las propiedades de material, las transformaciones GameObject, etc.

Propiedades del motor de temas

Fuera de los 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 Objeto GameObject en tiempo de ejecución.

Un Theme recurso puede definir varios motores de temas para lograr transiciones de estados visuales sofisticados que tienen como destino varias propiedades GameObject.

Tiempo de ejecución del tema

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

Facilitar

Algunos motores de temas, si definen su propiedad IsEasingSupported como true, admiten la aceleración entre estados. Por ejemplo, el control entre dos colores cuando se produce un cambio de estado. La duración define en segundos cuánto tiempo se va a 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 tema, 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 del 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 Objeto ScriptableObject de estados 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 del tema. 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 ha descrito anteriormente.

Motores de temas predeterminados

MRTK se incluye con un conjunto predeterminado de motores de tema 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 ha indicado, 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 extender esta clase e implementar lo siguiente:

Implementaciones obligatorias

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

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 al que acceder y el porcentaje, un valor flotante entre 0 y 1, se usa para facilitar o reducir entre valores.

public abstract ThemePropertyValue GetProperty(ThemeStateProperty property)

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 al facilitar entre estados.

public abstract ThemeDefinition 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 si se proporciona 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 de sombreador de destino. Se recomienda ampliar de InteractableShaderTheme para beneficiarse de 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 new.

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 tema personalizado

La clase siguiente 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 a en el método Update() para admitir motores de temas que usan aceleración/lerping 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;
    }
}

Consulte también