Визуальные темы — MRTK2

Темы позволяют гибко управлять ресурсами пользовательского интерфейса в ответ на различные переходы состояний. Это может включать изменение цвета кнопки, изменение размера элемента в ответ на фокус и т. д. Платформа визуальных тем состоит из двух ключевых элементов: 1) конфигурации и 2) подсистем среды выполнения.

Конфигурации тем — это определения свойств и типов, а подсистемы тем — это классы, использующие конфигурации и реализующие логику для обновления преобразований, материалов и многого другого во время выполнения.

Конфигурация темы

Конфигурации тем — это scriptableObjects , которые определяют, как обработчики тем будут инициализироваться во время выполнения. Они определяют, какие свойства и значения следует использовать в ответ на входные или другие изменения состояния при запуске приложения. В качестве ресурсов ScriptableObjects конфигурации темы можно определить один раз, а затем повторно использовать в разных компонентах пользовательского интерфейса.

Чтобы создать новый ресурс, выполните приведенные Theme далее действия.

  1. Щелкните правой кнопкой мыши в окне проекта
  2. Выберите Создать>Смешанная реальность Тема набора средств>

Пример ресурсов конфигурации темы можно найти в разделе MRTK/SDK/Features/UX/Interactable/Themes.

Пример темы ScriptableObject в инспекторе

Состояния

При создании нового Themeсначала необходимо задать доступные состояния. Свойство States указывает, сколько значений необходимо определить в конфигурации Темы, так как для каждого состояния будет по одному значению. На приведенном выше примере для взаимодействуемого компонента определены состояния по умолчанию : Default, Focus, Pressed и Disabled. Они определены в DefaultInteractableStates файле ресурса (Assets/MRTK/SDK/Features/UX/Interactable/States).

Чтобы создать новый ресурс, выполните приведенные State далее действия.

  1. Щелкните правой кнопкой мыши в окне проекта
  2. Выберите Создать>Смешанная реальность Состояние набора средств>

Пример состояния ScriptableObject в инспекторе

ScriptableObject State определяет как список состояний, так и тип StateModel для этих состояний. StateModel — это класс, который расширяет BaseStateModel и реализует логику конечного автомата для создания текущего состояния во время выполнения. Текущее состояние этого класса обычно используется подсистемами тем во время выполнения, чтобы определить, какие значения следует задать для свойств материала, преобразований GameObject и многого другого.

Свойства подсистемы тем

За пределами состоянийTheme ресурс также определяет список обработчиков тем и связанные свойства для этих обработчиков. Подсистема тем снова определяет логику для задания правильных значений gameObject во время выполнения.

Ресурс Theme может определить несколько обработчиков тем для достижения сложных переходов визуальных состояний, предназначенных для нескольких свойств GameObject.

Среда выполнения темы

Определяет тип класса создаваемой подсистемы Тем.

Ослабление

Некоторые обработчики тем, если они определяют свое свойство IsEasingSupported как true, поддерживают ослабление между состояниями. Например, переключение между двумя цветами при изменении состояния. Длительность определяет, как долго можно упростить от начального до конечного значения, а кривая анимации определяет скорость изменения в течение этого периода времени.

Свойства шейдера

Некоторые обработчики тем, если они определяют свое свойство AreShadersSupported как true, будут изменять определенные свойства шейдера во время выполнения. Поля Шейдер и Свойство определяют целевое свойство шейдера.

Создание конфигурации темы с помощью кода

Как правило, проще создавать конфигурации тем с помощью инспектора Unity, но бывают случаи, когда темы необходимо динамически создавать во время выполнения с помощью кода. В приведенном ниже фрагменте кода приведен пример выполнения этой задачи.

Чтобы ускорить разработку, для упрощения установки полезны следующие вспомогательные методы.

Interactable.GetDefaultInteractableStates() — создает новый объект State ScriptableObject с четырьмя значениями состояния по умолчанию, используемыми в компоненте Interactable .

ThemeDefinition.GetDefaultThemeDefinition<T>() — Каждый модуль тем определяет конфигурацию по умолчанию с правильными свойствами, необходимыми для этого типа среды выполнения темы. Этот вспомогательный компонент создает определение для заданного типа обработчика тем.

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

Подсистемы тем

Модуль тем — это класс, который расширяется от InteractableThemeBase класса . Эти классы создаются во время выполнения и настраиваются с помощью объекта , ThemeDefinition как описано ранее.

Обработчики тем по умолчанию

MRTK поставляется с набором обработчиков тем по умолчанию, перечисленных ниже:

Обработчики тем по умолчанию можно найти в разделе MRTK/SDK/Features/UX/Scripts/VisualThemes/ThemeEngines.

Пользовательские обработчики тем

Как уже говорилось, theme Engine определяется как класс, который расширяется InteractableThemeBase от класса . Таким образом, новому обработчику тем необходимо только расширить этот класс и реализовать следующее:

Обязательные реализации

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

Для заданного свойства, которое можно определить с помощью ThemeStateProperty.Name, задайте текущее значение состояния на целевом узле GameObject (т. е. задайте цвет материала и т. д.). Индекс указывает текущее значение состояния для доступа, а процент (число с плавающей точкой в диапазоне от 0 до 1) используется для облегчения и переключения между значениями.

public abstract ThemePropertyValue GetProperty(ThemeStateProperty property)

Для заданного свойства, которое можно определить с помощью ThemeStateProperty.Name, возвращает текущее значение, заданное в целевом объекте Host GameObject (т. е. текущий цвет материала, текущее смещение локальной позиции и т. д.). В основном используется для кэширования начального значения при смягчении между состояниями.

public abstract ThemeDefinition GetDefaultThemeDefinition()

Возвращает объект , определяющий ThemeDefinition свойства по умолчанию и конфигурацию, необходимые для пользовательской темы.

protected abstract void SetValue(ThemeStateProperty property, ThemePropertyValue value)

Защищенный вариант общедоступного SetValue() определения, за исключением предоставленного ThemePropertyValue, который нужно задать вместо указания на использование конфигурации индекса и (или) процента.

InteractableThemeBase.Init(GameObject host, ThemeDefinition settings)

Выполните все шаги инициализации, предназначенные для предоставленного параметра GameObject и используя свойства и конфигурации, определенные в параметре ThemeDefinition . Рекомендуется вызывать base.Init(host, settings) в начале переопределения.

InteractableThemeBase.IsEasingSupported

Если пользовательский модуль тем может поддерживать упрощение между значениями, настроенными с помощью ThemeDefinition.Easing свойства .

InteractableThemeBase.AreShadersSupported

Если пользовательский модуль тем может поддерживать целевые свойства шейдера. Рекомендуется расширить InteractableShaderTheme возможности для использования существующей инфраструктуры для эффективной настройки и получения свойств шейдера с помощью MaterialPropertyBlocks. Сведения о свойстве шейдера хранятся в каждом из них ThemeStateProperty через ThemeStateProperty.TargetShader и ThemeStateProperty.ShaderPropertyName.

Примечание

При расширении InteractableShaderThemeтакже может быть полезно переопределить InteractableShaderTheme.DefaultShaderProperty с помощью new.

Пример кода: protected new const string DefaultShaderProperty = "_Color";

Кроме того, следующие классы расширяют InteractableShaderTheme класс , который снова использует MaterialPropertyBlocks для изменения значений свойств шейдера. Этот подход помогает повысить производительность , так как MaterialPropertyBlocks не создает новые экземплярные материалы при изменении значений. Однако при доступе к типичным свойствам класса Material ожидаемые значения не возвращаются. Используйте MaterialPropertyBlocks для получения и проверки текущих значений свойств материала (т. е. _Color или _MainTex).

InteractableThemeBase.Reset

Направляет тему для сброса всех измененных свойств до исходных значений, которые были заданы на узле GameObject при инициализации этого обработчика тем.

Пример пользовательского обработчика тем

Приведенный ниже класс является примером пользовательского нового обработчика тем. Эта реализация найдет компонент MeshRenderer в инициализированном объекте узла и контролирует его видимость на основе текущего состояния.

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

Комплексный пример

В примере кода ниже показано, как управлять этой темой во время выполнения, в рамках пользовательского обработчика тем, определенного в предыдущем разделе. В частности, как задать текущее состояние темы, чтобы видимость MeshRenderer обновлялся соответствующим образом.

Примечание

theme.OnUpdate(state,force) как правило, следует вызывать в методе Update() для поддержки обработчиков тем, которые используют упрощение и связь между значениями.

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

См. также раздел