Temas visuais — MRTK2

Os temas permitem o controle flexível de ativos de UX em resposta a várias transições de estados. Isso pode envolver a alteração da cor de um botão, redimensionamento de um elemento em resposta ao foco, etc. A estrutura de Temas Visuais é composta por duas partes principais: 1) configuração e 2) mecanismos de runtime.

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

Configuração do tema

As configurações de tema são ScriptableObjects que definem como os Mecanismos de Tema serão inicializados em runtime. Eles definem quais propriedades e valores utilizar em resposta à entrada ou outras alterações de estado quando o aplicativo está em execução. Como ativos ScriptableObjects , as configurações de tema podem ser definidas uma vez e, em seguida, reutilizáveis em diferentes componentes de UX.

Para criar um novo Theme ativo:

  1. Clique com o botão direito do mouse na Janela do Projeto
  2. Selecione Criar>Realidade Misturada Tema do Kit de Ferramentas>

Exemplo de Ativos de configuração de tema podem ser encontrados em MRTK/SDK/Features/UX/Interactable/Themes.

Exemplo scriptableObject de tema no inspetor

Estados

Ao criar um novo Theme, a primeira coisa a definir é quais estados estão disponíveis. A propriedade States indica quantos valores uma configuração de Tema precisa definir, pois haverá um valor por estado. Na imagem de exemplo acima, os estados padrão definidos para o componente Interacionável são Padrão, Foco, Pressionado e Desabilitado. Eles são definidos no DefaultInteractableStates arquivo de ativos (Assets/MRTK/SDK/Features/UX/Interactable/States).

Para criar um novo State ativo:

  1. Clique com o botão direito do mouse na Janela do Projeto
  2. Selecione Criar> Realidade MisturadaEstado do Kit de Ferramentas>

Exemplo de States ScriptableObject no inspector

Um State ScriptableObject define tanto a lista de estados quanto o tipo de StateModel a ser criado para esses estados. Um StateModel é uma classe que estende BaseStateModel e implementa a lógica da máquina de estado para gerar o estado atual em runtime. O estado atual dessa classe geralmente é usado pelos Mecanismos de Tema em runtime para ditar quais valores definir em relação a propriedades materiais, transformações GameObject e muito mais.

Propriedades do mecanismo de tema

Fora dos Estados, um Theme ativo também define uma lista de Mecanismos de Tema e as propriedades associadas para esses mecanismos. Um mecanismo de tema define novamente a lógica para definir os valores corretos em relação a um GameObject em runtime.

Um Theme ativo pode definir vários Mecanismos de Tema para obter transições de estados visuais sofisticados direcionadas a várias propriedades GameObject.

Tempo de execução do tema

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

Atenuação

Alguns Mecanismos de Tema, se definirem sua propriedade IsEasingSupported como true, dão suporte à flexibilização entre estados. Por exemplo, ler entre duas cores quando ocorrer uma alteração de estado. A Duração define em segundos quanto tempo facilitar do valor inicial para o valor final e a Curva de Animação define a taxa de alteração durante esse período de tempo.

Propriedades do sombreador

Alguns Mecanismos de Tema, se definirem sua propriedade AreShadersSupported como true, modificarão propriedades específicas do sombreador em runtime. Os campos Sombreador e Propriedade definem a propriedade de sombreador a ser direcionada.

Criar uma configuração de tema por meio de código

Em geral, é mais fácil criar configurações de tema por meio do inspetor do Unity, mas há casos em que os Temas devem ser gerados dinamicamente no runtime por meio do código. O snippet de código abaixo fornece um exemplo de como realizar essa tarefa.

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

Interactable.GetDefaultInteractableStates() – cria um novo States ScriptableObject com os quatro valores de estado padrão usados no componente Interativo .

ThemeDefinition.GetDefaultThemeDefinition<T>() – Todo Mecanismo de Tema define uma configuração padrão com as propriedades corretas necessárias para esse tipo de runtime de tema. Esse auxiliar cria uma definição para o tipo de Mecanismo de Tema fornecido.

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

Mecanismos de tema

Um Mecanismo de Tema é uma classe que se estende da InteractableThemeBase classe . Essas classes são instanciadas em runtime e configuradas com um ThemeDefinition objeto, conforme descrito anteriormente.

Mecanismos de tema padrão

O MRTK é fornecido com um conjunto padrão de Mecanismos de Tema listado abaixo:

Os Mecanismos de Tema padrão podem ser encontrados em MRTK/SDK/Features/UX/Scripts/VisualThemes/ThemeEngines.

Mecanismos de tema personalizados

Conforme indicado, um Mecanismo de Tema é definido como uma classe que se estende da InteractableThemeBase classe . Portanto, o novo Mecanismo de Tema só precisa estender essa classe e implementar o seguinte:

Implementações obrigatórias

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

Para a propriedade fornecida, que pode ser identificada por ThemeStateProperty.Name, defina seu valor de estado atual no host GameObject de destino (ou seja, defina a cor do material etc.). O índice indica o valor de estado atual a ser acessado e o percentual, um float entre 0 e 1, é usado para facilitar/lerping entre valores.

public abstract ThemePropertyValue GetProperty(ThemeStateProperty property)

Para a propriedade fornecida, que pode ser identificada por ThemeStateProperty.Name, retorne o valor atual definido no GameObject host de destino (ou seja, a cor do material atual, o deslocamento de posição local atual etc.). Isso é usado principalmente para armazenar em cache o valor inicial ao facilitar entre estados.

public abstract ThemeDefinition GetDefaultThemeDefinition()

Retorna um ThemeDefinition objeto que define as propriedades padrão 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 fornecida para definir em vez de direcionar para usar a configuração de índice e/ou percentual.

InteractableThemeBase.Init(GameObject host, ThemeDefinition settings)

Execute todas as etapas de inicialização aqui visando o parâmetro GameObject fornecido e usando as propriedades e configurações definidas no parâmetro ThemeDefinition . É recomendável chamar base.Init(host, settings) no início de uma substituição.

InteractableThemeBase.IsEasingSupported

Se o Mecanismo de Tema personalizado puder dar suporte à facilitação entre valores configurados por meio da ThemeDefinition.Easing propriedade .

InteractableThemeBase.AreShadersSupported

Se o Mecanismo de Tema personalizado puder dar suporte a propriedades de sombreador de destino. É recomendável estender-se de InteractableShaderTheme para se beneficiar da infraestrutura existente para definir/obter propriedades de sombreador com eficiência por meio de MaterialPropertyBlocks. As informações da propriedade do sombreador são armazenadas em cada ThemeStateProperty por meio de ThemeStateProperty.TargetShader e ThemeStateProperty.ShaderPropertyName.

Observação

Se estender InteractableShaderTheme, também poderá ser útil substituir InteractableShaderTheme.DefaultShaderProperty por meio de novo.

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

Além disso, as classes a seguir estendem a InteractableShaderTheme classe que novamente usa MaterialPropertyBlocks para modificar valores de propriedade do sombreador. Essa abordagem ajuda o desempenho porque MaterialPropertyBlocks não cria novos materiais em instâncias quando os valores são alterados. No entanto, o acesso às propriedades típicas da classe Material não retornará os valores esperados. Use MaterialPropertyBlocks para obter e validar valores de propriedade de material atuais (ou seja , _Color ou _MainTex).

InteractableThemeBase.Reset

Direciona o tema para redefinir as propriedades modificadas de volta para seus valores originais que foram definidos no gameObject host quando esse mecanismo de tema foi inicializado.

Exemplo de mecanismo de tema personalizado

A classe abaixo é um exemplo de um novo Mecanismo de Tema personalizado. Essa implementação encontrará um componente MeshRenderer no objeto host inicializado e controlará 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 de ponta a ponta

Estendendo-se do Mecanismo de Tema personalizado definido na seção anterior, o exemplo de código abaixo demonstra como controlar esse tema em runtime. Em particular, como definir o estado atual no tema para que a visibilidade do MeshRenderer seja atualizada adequadamente.

Observação

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

Confira também