Partilhar via


Temas visuais — MRTK2

Os temas permitem o controlo flexível dos recursos UX em resposta a várias transições de estados. Isto pode envolver alterar a cor de um botão, redimensionar um elemento em resposta ao foco, etc. A arquitetura Temas Visuais é composta por duas peças-chave: 1) configuração e 2) motores de runtime.

As configurações de temas são definições de propriedades e tipos, enquanto os Motores de Tema são classes que consomem as configurações e implementam a lógica para atualizar transformações, materiais e muito mais no runtime.

Configuração do tema

As configurações do tema são ScriptableObjects que definem como os Motores de Tema serão inicializados no runtime. Definem as propriedades e valores a utilizar em resposta a alterações de entrada ou de outro estado quando a aplicação está em execução. Como recursos ScriptableObjects , as configurações do tema podem ser definidas uma vez e, em seguida, reutilizadas em diferentes componentes UX.

Para criar um novo recurso Theme :

  1. Clique com o botão direito do rato na Janela do Projeto
  2. Selecione Criar> Mixed RealityTemado Toolkit>

Os recursos de configuração do tema de exemplo podem ser encontrados em MRTK/SDK/Features/UX/Interactable/Themes.

Exemplo de ScriptableObject do Tema no inspetor

Estados

Ao criar um novo Theme, a primeira coisa a definir é que estados estão disponíveis. A propriedade Estados indica quantos valores uma configuração de Tema tem de definir, uma vez que haverá um valor por estado. Na imagem de exemplo acima, os estados predefinidos definidos para o componente Interacionável são Predefinido, Foco, Premido e Desativado. Estes são definidos no DefaultInteractableStates ficheiro de recurso (Assets/MRTK/SDK/Features/UX/Interactable/States).

Para criar um novo recurso State :

  1. Clique com o botão direito do rato na Janela do Projeto
  2. Selecione Criar> Mixed RealityEstado doToolkit>

Estado ScriptableObject exemplo no inspetor

Um State ScriptableObject define a lista de estados, bem como o tipo de StateModel a criar para estes estados. Um StateModel é uma classe que expande BaseStateModel e implementa a lógica da máquina de estado para gerar o estado atual no runtime. O estado atual desta classe é geralmente utilizado pelos Motores de Tema no runtime para ditar os valores a definir em relação às propriedades do material, transformações do GameObject e muito mais.

Propriedades do motor do tema

Fora dos Estados, um Theme recurso também define uma lista de Motores de Tema e as propriedades associadas para estes motores. Um motor de tema define novamente a lógica para definir os valores corretos em relação a um GameObject no runtime.

Um Theme recurso pode definir vários Motores de Tema para obter transições de estados visuais sofisticadas direcionadas para várias propriedades do GameObject.

Runtime do Tema

Define o tipo de classe do motor de tema que será criado

Diminuir

Alguns Motores de Tema, se definirem a sua propriedade IsEasingSupported como verdadeira, suportam o abrandamento entre estados. Por exemplo, ler entre duas cores quando ocorre uma alteração de estado. A Duração define em segundos quanto tempo deve facilitar do valor inicial ao valor final e a Curva de Animação define a taxa de alteração durante esse período de tempo.

Propriedades do Shader

Alguns Motores de Tema, se definirem a respetiva propriedade AreShadersSupported como verdadeiro, modificarão propriedades específicas do sombreador no runtime. Os campos Shader e Property definem a propriedade shader como destino.

Criar uma configuração de tema através de código

Em geral, é mais fácil conceber configurações de temas através do inspetor do Unity, mas há casos em que os Temas têm de ser gerados dinamicamente no runtime através de código. O fragmento de código abaixo dá um exemplo de como realizar esta tarefa.

Para ajudar a agilizar o desenvolvimento, os seguintes métodos auxiliares são úteis para simplificar a configuração.

Interactable.GetDefaultInteractableStates() - cria um novo ScriptableObject dos Estados com os quatro valores de estado predefinidos utilizados no componente Interactable .

ThemeDefinition.GetDefaultThemeDefinition<T>() - Cada Motor de Tema define uma configuração predefinida com as propriedades corretas necessárias para esse tipo de runtime de tema. Este programa auxiliar cria uma definição para o tipo de Motor de Tema especificado.

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

Um Motor de Tema é uma classe que se expande da InteractableThemeBase classe. Estas classes são instanciadas no runtime e configuradas com um ThemeDefinition objeto conforme descrito anteriormente.

Motores de tema predefinidos

O MRTK é fornecido com um conjunto predefinido de Motores de Tema listado abaixo:

Os Motores de Tema predefinidos podem ser encontrados em MRTK/SDK/Features/UX/Scripts/VisualThemes/ThemeEngines.

Motores de tema personalizados

Conforme indicado, um Motor de Tema é definido como uma classe que se estende da InteractableThemeBase classe. Assim, o novo Motor de Tema só precisa de expandir esta classe e implementar o seguinte:

Implementações obrigatórias

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

Para a propriedade especificada, que pode ser identificada pelo ThemeStateProperty.Name, defina o respetivo valor de estado atual no anfitrião GameObject direcionado (ou seja, defina a cor do material, etc.). O índice indica o valor do estado atual a aceder e a percentagem, um float entre 0 e 1, é utilizado para facilitar/ler entre valores.

public abstract ThemePropertyValue GetProperty(ThemeStateProperty property)

Para a propriedade especificada, que pode ser identificada por ThemeStateProperty.Name, devolva o valor atual definido no GameObject anfitrião de destino (ou seja, a cor do material atual, o desvio da posição local atual, etc.). Isto é utilizado principalmente para colocar em cache o valor de início ao facilitar entre estados.

public abstract ThemeDefinition GetDefaultThemeDefinition()

Devolve um ThemeDefinition objeto que define as propriedades predefinidas e a configuração necessárias para o tema personalizado

protected abstract void SetValue(ThemeStateProperty property, ThemePropertyValue value)

Variante protegida da definição pública SetValue() , exceto themePropertyValue fornecido para definir em vez de direcionar para utilizar a configuração de índice e/ou percentagem.

InteractableThemeBase.Init(GameObject host, ThemeDefinition settings)

Execute todos os passos de inicialização aqui direcionados para o parâmetro GameObject fornecido e com as propriedades e configurações definidas no parâmetro ThemeDefinition . Recomenda-se que chame base.Init(host, settings) no início de uma substituição.

InteractableThemeBase.IsEasingSupported

Se o Motor de Tema personalizado conseguir suportar a facilitação entre valores configurados através da ThemeDefinition.Easing propriedade.

InteractableThemeBase.AreShadersSupported

Se o Motor de Tema personalizado conseguir suportar propriedades de sombreado de destino. Recomenda-se que se expanda de InteractableShaderTheme para beneficiar da infraestrutura existente para definir/obter propriedades de sombreador de forma eficiente através de MaterialPropertyBlocks. As informações da propriedade shader são armazenadas em cada uma ThemeStateProperty através de ThemeStateProperty.TargetShader e ThemeStateProperty.ShaderPropertyName.

Nota

Se expandir InteractableShaderThemeo , também pode ser útil substituir o InteractableShaderTheme.DefaultShaderProperty através de novo.

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

Além disso, as seguintes classes abaixo expandem a InteractableShaderTheme classe que utiliza materialPropertyBlocks para modificar valores de propriedade shader. Esta abordagem ajuda no desempenho porque MaterialPropertyBlocks não cria novos materiais instânciados quando os valores mudam. No entanto, o acesso às propriedades típicas da classe Material não devolverá os valores esperados. Utilize MaterialPropertyBlocks para obter e validar valores de propriedades materiais atuais (ou seja , _Color ou _MainTex).

InteractableThemeBase.Reset

Direciona o tema para repor quaisquer propriedades modificadas para os respetivos valores originais que foram definidos no gameObject anfitrião quando este motor de tema foi inicializado.

Exemplo de motor de tema personalizado

A classe abaixo é um exemplo de um novo Motor de Tema personalizado. Esta implementação irá encontrar um componente MeshRenderer no objeto anfitrião inicializado e controlar a sua visibilidade com base no estado atual.

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

Exemplo ponto a ponto

Ao expandir-se do Motor de Tema personalizado definido na secção anterior, o exemplo de código abaixo demonstra como controlar este tema no runtime. Em particular, como definir o estado atual no tema para que a visibilidade meshRenderer seja atualizada adequadamente.

Nota

theme.OnUpdate(state,force) geralmente, deve ser chamado no método Update() para suportar Motores de Tema que utilizam a facilidade/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;
    }
}

Ver também