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
:
- Clique com o botão direito do rato na Janela do Projeto
- Selecione Criar> Mixed RealityTemado Toolkit>
Os recursos de configuração do tema de exemplo podem ser encontrados em MRTK/SDK/Features/UX/Interactable/Themes
.
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
:
- Clique com o botão direito do rato na Janela do Projeto
- Selecione Criar> Mixed RealityEstado doToolkit>
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:
InteractableActivateTheme
InteractableAnimatorTheme
InteractableAudioTheme
InteractableColorChildrenTheme
InteractableColorTheme
InteractableGrabScaleTheme
InteractableMaterialTheme
InteractableOffsetTheme
InteractableRotationTheme
InteractableScaleTheme
InteractableShaderTheme
InteractableStringTheme
InteractableTextureTheme
ScaleOffsetColorTheme
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.
Substituições recomendadas
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 InteractableShaderTheme
o , 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).
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;
}
}