Другое

Как создавать шаблоны UI Toolkit в пакетах Unity

Полное руководство по созданию и использованию шаблонов UI Toolkit в Unity пакетах. Узнайте стандартные подходы, оптимизацию для редактора и производительности.

Как создавать шаблоны элементов UI Toolkit в своем пакете (Packages) и использовать их в проекте?

Изучаю UI Toolkit и столкнулся с проблемой: хочу создать кнопку с определенным стилем (USS), которая будет представлять собой UXML файл, добавляемый через UI Builder/Library в любой интерфейс. В Assets я мог бы просто создать элемент, и он бы появился в Library/Project/Assets/“название”.uxml. Но в пакете это не работает.

Я обнаружил, что если создать класс, наследующийся от VisualElement и помеченный атрибутом [UxmlElement], он будет отображаться как нужно. Похоже на костыль, ведь это просто класс CustomButton : Button без переопределенной логики.

Вопрос: является ли этот способ единственным или есть более лаконичное решение? Или создание такой пустышки единственный вариант?

Дополнительно: если придется использовать этот подход, нужно ли реализовывать загрузку шаблона через AssetDataBase.LoadAssetAtPath() для редакторского интерфейса? Не будет ли это сильно нагружать Unity и ПК при кастомных окнах и инспекторах с элементами, созданными таким образом?

Чтобы создавать и использовать шаблоны UI Toolkit в своем пакете, нужно использовать комбинацию UXML, USS и C# классов с правильной регистрацией. Ваш подход с наследованием VisualElement и атрибутом [UxmlElement] — это базовый механизм, но есть нюансы для пакетов и оптимизации.


Краткий ответ

В пакетах Unity для создания шаблонов UI Toolkit необходимо:

  1. Создать C# класс с атрибутом [UxmlElement] и UxmlFactory,
  2. Добавить UXML-шаблон через VisualTreeAsset,
  3. Зарегистрировать namespace в UXML.
    Этот подход не костыль, а стандартная практика. Для редакторского интерфейса используйте AssetDatabase.LoadAssetAtPath() с кешированием для производительности.

Contents


Базовый подход для пакетов

В пакетах нельзя просто создать UXML-файл и ожидать, что он появится в UI Builder. Unity требует явной регистрации через C# классы.

Почему это так работает?
Пакеты из PackageManager не автоматически сканируются на UXML-файлы, как Assets. Требуется объявить элемент через:

csharp
[UxmlElement]
public partial class CustomButton : Button
{
    public CustomButton() { }
}

Затем добавить UXML-шаблон через VisualTreeAsset:

csharp
public VisualTreeAsset uxmlTemplate;

И зарегистрировать namespace в UXML:

xml
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:custom="MyPackageNamespace">
    <custom:CustomButton />
</ui:UXML>

Полный пример реализации

1. Структура пакета

MyPackage/
├── Editor/
│   └── CustomButtonEditor.cs
├── Runtime/
│   └── CustomButton.cs
└── Editor/
    └── Resources/
        └── CustomButton.uxml

2. C# класс элемента

csharp
// Runtime/CustomButton.cs
using UnityEngine.UIElements;

[UxmlElement]
public partial class CustomButton : Button
{
    public new class UxmlFactory : UxmlFactory<CustomButton, UxmlTraits> { }
    
    public CustomButton()
    {
        // Добавляем USS по умолчанию
        styleSheets.Add(Resources.Load<StyleSheet>("CustomButtonStyles"));
    }
}

3. UXML-шаблон

xml
<!-- Editor/Resources/CustomButton.uxml -->
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:custom="MyPackageNamespace">
    <ui:VisualElement class="button-container">
        <ui:Label text="Custom Button" name="button-text" />
    </ui:VisualElement>
</ui:UXML>

4. Регистрация для UI Builder

csharp
// Editor/CustomButtonEditor.cs
using UnityEditor.UIElements;
using UnityEngine.UIElements;

[InitializeOnLoad]
public static class CustomButtonRegistration
{
    static CustomButtonRegistration()
    {
        // Регистрируем шаблон в UI Builder
        var template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
            "Packages/com.my.package/Editor/Resources/CustomButton.uxml"
        );
        
        if (template != null)
        {
            UxmlDescriptionCache.RegisterDescription(template);
        }
    }
}

Оптимизация для редактора

Кеширование ассетов

csharp
private static VisualTreeAsset _cachedTemplate;

private VisualTreeAsset GetTemplate()
{
    if (_cachedTemplate == null)
    {
        _cachedTemplate = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
            "Packages/com.my.package/Editor/Resources/CustomButton.uxml"
        );
    }
    return _cachedTemplate;
}

Для инспекторов окон

csharp
public override VisualElement CreateInspectorGUI()
{
    var template = GetTemplate();
    var container = template.CloneTree();
    // Дополнительная логика...
    return container;
}

Почему это не нагружает систему:

  • AssetDatabase.LoadAssetAtPath() кеширует ассеты после первой загрузки,
  • В инспекторах шаблоны используются повторно,
  • Для runtime UI загружайте шаблоны через Resources.Load().

Альтернативные решения

1. UI Builder + Package Manager

Создайте UI-контрол через UI Builder и экспортируйте как C# класс:

csharp
// Автоматически сгенерированный класс
public partial class MyButton : VisualElement
{
    private Label _label;
    
    public MyButton()
    {
        var visualTree = Resources.Load<VisualTreeAsset>("MyButton");
        visualTree.CloneTree(this);
        _label = this.Q<Label>("label");
    }
}

2. Runtime-only шаблоны

Для интерфейсов, не требующих UI Builder:

csharp
public void CreateUI()
{
    var template = Resources.Load<VisualTreeAsset>("CustomButton");
    var instance = template.CloneTree();
    rootElement.Add(instance);
}

Рекомендации по производительности

  1. Для редактора:

    • Используйте статическое кеширование (static VisualTreeAsset),
    • Загружайте шаблоны только при открытии окна,
    • Избегайте повторной загрузки в OnGUI().
  2. Для runtime:

    • Используйте Resources.Load() или Addressables,
    • Кешируйте экземпляры шаблонов, если они используются многократно.
  3. Оптимизация USS:

    • Минимизуйте вложенность селекторов,
    • Используйте классы вместо инлайновых стилей.

Заключение

  1. Стандартный подход с [UxmlElement] + UxmlFactory — это не костыль, а архитектурное требование Unity для пакетов.
  2. Для UI Builder обязательно нужна регистрация через UxmlDescriptionCache.
  3. Производительность обеспечивается кешированием ассетов и избеганием повторных загрузок.
  4. Альтернативы: UI Builder для визуальной разработки или чистые runtime-шаблоны для динамических интерфейсов.

Совет: Для сложных элементов комбинируйте подходы — создавайте базовую логику в C# и визуальную часть через UI Builder, затем экспортируйте как шаблон. Это обеспечит и гибкость, и производительность.

Авторы
Проверено модерацией
Модерация