시각적 테마 — MRTK2

테마를 사용하면 다양한 상태 전환에 대응하여 UX 자산을 유연하게 제어할 수 있습니다. 여기에는 단추의 색 변경, 포커스에 대한 응답으로 요소 크기 조정 등이 포함될 수 있습니다. Visual 테마 프레임워크는 1) 구성과 2) 런타임 엔진의 두 가지 주요 부분으로 구성됩니다.

테마 구성 은 속성 및 형식의 정의이며 테마 엔진 은 구성을 사용하고 런타임에 변환, 재질 등을 업데이트하는 논리를 구현하는 클래스입니다.

테마 구성

테마 구성은 런타임에 테마 엔진을 초기화하는 방법을 정의하는 ScriptableObject 입니다. 앱이 실행 중일 때 입력 또는 기타 상태 변경에 대한 응답으로 활용할 속성과 값을 정의합니다. ScriptableObjects 자산으로 테마 구성을 한 번 정의한 다음 다른 UX 구성 요소에서 다시 사용할 수 있습니다.

Theme 자산을 만들려면 다음을 수행합니다.

  1. 프로젝트 창을 마우스 오른쪽 단추로 클릭합니다.
  2. 만들기>Mixed Reality 도구 키트 테마를> 선택합니다.

예제 테마 구성 자산은 에서 MRTK/SDK/Features/UX/Interactable/Themes찾을 수 있습니다.

검사기에서 Theme ScriptableObject 예제

상태

Theme를 만들 때 가장 먼저 설정할 상태는 사용 가능한 상태입니다. 상태 속성은 상태당 하나의 값이 있으므로 Theme 구성에서 정의해야 하는 값 수를 나타냅니다. 위의 예제 이미지에서 상호 작용 가능 구성 요소에 대해 정의된 기본 상태는Default, Focus, PressedDisabled입니다. 이는 (Assets/MRTK/SDK/Features/UX/Interactable/States) 자산 파일에 정의 DefaultInteractableStates 되어 있습니다.

State 자산을 만들려면 다음을 수행합니다.

  1. 프로젝트 창을 마우스 오른쪽 단추로 클릭합니다.
  2. 만들기>Mixed Reality 도구 키트>상태를 선택합니다.

검사기에서 ScriptableObject 예제 상태

State ScriptableObject는 상태 목록과 이러한 상태에 대해 만들 StateModel 형식을 모두 정의합니다. StateModel은 런타임에 현재 상태를 생성하기 위해 상태 컴퓨터 논리를 확장하고 BaseStateModel 구현하는 클래스입니다. 이 클래스의 현재 상태는 일반적으로 런타임에 테마 엔진에서 재질 속성, GameObject 변환 등에 대해 설정할 값을 지정하는 데 사용됩니다.

테마 엔진 속성

상태 외부에서 자산은 Theme 테마 엔진 목록과 이러한 엔진에 대한 연결된 속성도 정의합니다. 테마 엔진은 런타임에 GameObject에 대해 올바른 값을 설정하는 논리를 다시 정의합니다.

자산은 Theme 여러 GameObject 속성을 대상으로 하는 정교한 시각적 상태 전환을 달성하기 위해 여러 테마 엔진을 정의할 수 있습니다.

테마 런타임

만들 테마 엔진의 클래스 형식을 정의합니다.

완화

일부 테마 엔진은IsEasingSupported 속성을 true로 정의하면 상태 간 완화를 지원합니다. 예를 들어 상태 변경이 발생할 때 두 색 사이에 lerping합니다. Duration은 시작 값에서 끝 값으로의 완화 기간을 초 단위로 정의하고 애니메이션 곡선은 해당 기간 동안의 변경 속도를 정의합니다.

셰이더 속성

일부 테마 엔진은AreShadersSupported 속성을 true로 정의하는 경우 런타임에 특정 셰이더 속성을 수정합니다. 셰이더속성 필드는 대상으로 지정할 셰이더 속성을 정의합니다.

코드를 통해 테마 구성 만들기

일반적으로 Unity 검사기를 통해 테마 구성을 디자인하는 것이 더 쉽지만 코드를 통해 런타임에 테마를 동적으로 생성해야 하는 경우가 있습니다. 아래 코드 조각은 이 작업을 수행하는 방법의 예를 제공합니다.

개발을 신속하게 진행하기 위해 다음 도우미 메서드는 설정을 간소화하는 데 유용합니다.

Interactable.GetDefaultInteractableStates() - 상호 작용 가능 구성 요소에 사용되는 네 개의 기본 상태 값을 사용하여 새 상태 ScriptableObject를 만듭니다.

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찾을 수 있습니다.

사용자 지정 테마 엔진

설명한 대로 테마 엔진은 클래스에서 InteractableThemeBase 확장하는 클래스로 정의됩니다. 따라서 새 테마 엔진은 이 클래스만 확장하고 다음을 구현해야 합니다.

필수 구현

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

로 식별 ThemeStateProperty.Name할 수 있는 지정된 속성의 경우 대상 GameObject 호스트에서 현재 상태 값을 설정합니다(즉, 재질 색 설정 등). 인덱스는 액세스할 현재 상태 값을 나타내며, 0에서 1 사이의 부동 소수 요소는 값 간의 감속/lerping에 사용됩니다.

public abstract ThemePropertyValue GetProperty(ThemeStateProperty property)

로 식별 ThemeStateProperty.Name할 수 있는 지정된 속성의 경우 대상 호스트 GameObject에 설정된 현재 값(즉, 현재 재질 색, 현재 로컬 위치 오프셋 등)을 반환합니다. 이는 주로 상태 간에 완화할 때 시작 값을 캐싱하는 데 사용됩니다.

public abstract ThemeDefinition GetDefaultThemeDefinition()

사용자 지정 테마에 ThemeDefinition 필요한 기본 속성 및 구성을 정의하는 개체를 반환합니다.

protected abstract void SetValue(ThemeStateProperty property, ThemePropertyValue value)

인덱스 및/또는 백분율 구성을 사용하도록 지시하는 대신 설정할 제공된 ThemePropertyValue를 제외하고 공용 SetValue() 정의의 보호된 변형입니다.

InteractableThemeBase.Init(GameObject host, ThemeDefinition settings)

제공된 GameObject 매개 변수를 대상으로 하고 ThemeDefinition 매개 변수에 정의된 속성 및 구성을 사용하여 여기에서 초기화 단계를 수행합니다. 재정의를 시작할 때 를 호출 base.Init(host, settings) 하는 것이 좋습니다.

InteractableThemeBase.IsEasingSupported

사용자 지정 테마 엔진이 속성을 통해 ThemeDefinition.Easing 구성된 값 간의 감속을 지원할 수 있는 경우

InteractableThemeBase.AreShadersSupported

사용자 지정 테마 엔진이 셰이더 속성의 대상 지정을 지원할 수 있는 경우 기존 인프라의 이점을 활용하려면 에서 InteractableShaderTheme 를 확장하여 MaterialPropertyBlocks를 통해 셰이더 속성을 효율적으로 설정/가져오는 것이 좋습니다. 셰이더 속성 정보는 및 ThemeStateProperty.ShaderPropertyName을 통해 ThemeStateProperty.TargetShader 각각 ThemeStateProperty 에 저장됩니다.

참고

를 확장하는 경우 새로 만들기를 InteractableShaderTheme통해 InteractableShaderTheme.DefaultShaderProperty를 재정의하는 것도 유용할 수 있습니다.

예제 코드: protected new const string DefaultShaderProperty = "_Color";

또한 아래 클래스는 MaterialPropertyBlocks를 다시 사용하여 셰이더 속성 값을 수정하는 클래스를 확장 InteractableShaderTheme 합니다. 이 방법은 값이 변경되면 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() 메서드에서 호출되어 값 간의 감속/lerping을 활용하는 테마 엔진을 지원해야 합니다.

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

추가 정보