Unity 6.3
0 онлайн 55 гостей 3 в системе
Вход
UI Toolkit для продвинутых разработчиков Глава 10 из 14 Оригинал, стр. 70

Привязка данных

Привязка данных

Пользовательский интерфейс связывает игроков с данными, управляющими приложением. Именно через него они видят внутреннее состояние и логику игры и взаимодействуют с ними. Игроки не изучают необработанные значения характеристик - вместо этого они видят шкалу здоровья. Они не читают списки предметов напрямую, а пользуются инвентарём с перетаскиванием объектов. Такое взаимодействие интерфейса с данными влияет на структуру проекта.

Интерфейс, отражающий игровые данные Ниже показано окно характеристик персонажа из UI Toolkit Sample - Dragon Crashers. Этот интерфейс отображает основные параметры игры в жанре RPG. Представление - это собственно интерфейс, с которым взаимодействует игрок. Контейнеры с вкладками упорядочивают способности персонажа и упрощают навигацию.

Окно характеристик персонажа отображает игровые данные.

За интерфейсом стоит модель с данными - например, ScriptableObject, в котором хранятся характеристики каждого персонажа.

Ресурс ScriptableObject содержит данные персонажа.

Разделение представления и модели - один из основных принципов архитектуры пользовательских интерфейсов. Отделив визуальный интерфейс от данных, вы сделаете код гибче, пригоднее для повторного использования и удобнее в сопровождении. Но после разделения модели и представления их необходимо синхронизировать. Традиционно для этого данные обновляют напрямую или используют систему событий: при изменении данных наблюдатели обновляют интерфейс. Такой подход работает, но приводит к повторяющемуся шаблонному коду.

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

Привязка данных во время выполнения Привязка данных во время выполнения в Unity 6 предлагает более простой способ решить эту задачу. Данные приложения напрямую связываются с элементами интерфейса, поэтому изменения одной стороны автоматически отражаются на другой. Архитектура Model-View-ViewModel (MVVM - «модель - представление - модель представления») добавляет между представлением и моделью слой логики отображения. ViewModel выступает посредником и предоставляет данные модели в формате, подходящем представлению. Подробнее об MVVM и других шаблонах проектирования читайте в электронной книге Unity «Совершенствуйте код с помощью шаблонов проектирования и SOLID».

Архитектура MVVM (источник: Wikipedia)

Например, шкала здоровья может автоматически отображать запас здоровья игрока, а надпись со счётом - обновляться в реальном времени без дополнительной логики в сценариях и ручной обработки событий. Чем меньше кода синхронизации, тем проще масштабировать проект. Рассмотрим примеры привязки данных UI Toolkit во время выполнения и способы её применения.

Основные понятия привязки данных В Unity 6 появилась система привязки данных во время выполнения, которая обеспечивает структурированный способ связывать элементы интерфейса с данными приложения. Чтобы привязать свойство визуального элемента к источнику данных, создайте экземпляр DataBinding. Ниже перечислены основные понятия: - Data source: объект, содержащий данные для привязок интерфейса. - Data source path: свойство или поле источника данных, с которым связывается элемент интерфейса. - Binding mode: определяет направление передачи данных между источником и интерфейсом; привязка может быть односторонней или двусторонней. Вместе эти части образуют привязку данных. Рассмотрим их подробнее.

Подготовка источника данных Источник данных - это объект, содержащий данные для привязок интерфейса. Им может быть любой объект C#, включая ScriptableObject, MonoBehaviour или пользовательский объект C#. Структуры в роли источников данных способны повысить производительность благодаря небольшому объёму выделяемой памяти и меньшему числу сборок мусора. Привязку можно настроить как из кода, так и в Inspector.

В демонстрационном проекте источниками данных служат ScriptableObject, поскольку содержащиеся в них данные удобно сериализовать через Inspector Unity.

Использование атрибута CreateProperty Чтобы свойства можно было использовать в привязках, UI Toolkit опирается на контейнеры свойств, создаваемые модулем Unity Properties. Они определяют, какие свойства источника данных доступны системе привязки. Пометьте нужные свойства атрибутом CreateProperty, чтобы явно предоставить их системе. Ниже показан распространённый вариант настройки:

C#
[SerializeField, DontCreateProperty] int m_Value;
[CreateProperty] public int Value {
    get => m_Value;
    set => m_Value = value;
}

В этом примере поле m_Value помечено атрибутом SerializeField для сериализации, но исключено из привязки атрибутом DontCreateProperty. Свойство Value, напротив, помечено CreateProperty и поэтому доступно системе привязки. Такое явное разделение упрощает управление потоком данных между моделью и интерфейсом.

Привязки данных во время выполнения используют контейнеры свойств для эффективного обхода и изменения данных типа. По умолчанию Unity создаёт такой контейнер с помощью рефлексии при первом обращении к типу, что вызывает небольшие накладные расходы во время выполнения. Чтобы избежать этого, помечайте объявляемые свойства атрибутом CreateProperty. Тогда код привязки создаётся во время компиляции, рефлексия во время выполнения не требуется, а накладные расходы снижаются.

Источники данных и пути Когда источник данных готов, его можно связать с интерфейсом. Путь к источнику данных указывает свойство или поле, которое нужно связать с элементом интерфейса. Например, если источник содержит свойство health, путь будет указывать непосредственно на него - в UXML или в настройке привязки C#. Рассмотрим практический пример. В UI Builder выберите элемент в Hierarchy, откройте Inspector и в меню параметров ( ) выберите Add Binding.

Добавление привязки в Inspector.

Затем назначьте Data Source - например, ScriptableObject PlayerDataSO - и укажите Data Source Path, например CurrentHealth.

Настройка Data Source и Data Source Path в UI Builder.

В UXML: когда привязка данных настраивается в UI Builder, соответствующий код UXML создаётся автоматически. Путь к источнику данных также можно добавить или изменить вручную в текстовом редакторе. Ниже показан блок кода, создающий привязку:

<Bindings> <ui:DataBinding property="text" data-source-path="Health"/> </Bindings> В C#: создайте экземпляр объекта источника данных или получите ссылку на него в сценарии - например, на ScriptableObject. Присвойте объект свойству dataSource корневого элемента. Точное привязываемое свойство задайте через dataSourcePath. В следующем фрагменте показано, как установить свойства dataSource и dataSourcePath из сценария. Подробнее этот способ рассматривается ниже, в разделе о настройке привязки данных на C#.

C#
var label = new Label();
var parentData = ScriptableObject.CreateInstance<PlayerDataSO>();
playerData.Health = 100;
label.SetBinding("text", new DataBinding() {
    dataSource = playerData, dataSourcePath = new PropertyPath(nameof(PlayerDataSO.Health)),
}
);

Примечание: если определить несколько привязок для одного элемента интерфейса, может возникнуть конфликт. Чтобы избежать путаницы: - Используйте привязки UI Builder/UXML для статических или стандартных конфигураций данных, которые не требуется изменять во время выполнения. - Используйте привязки C# для динамических обновлений и случаев, когда источник данных должен меняться во время игры. Можно также частично настроить привязку в UI Builder/UXML, а завершить её во время выполнения. Дополнительные сведения приведены ниже, в разделе «Работа с неразрешёнными привязками данных».

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

Дочерний элемент может переопределить источник данных родительского элемента.

Если у родительского элемента есть источник данных, дочерние элементы автоматически его наследуют. В UI Builder поле Data Source дочернего элемента заранее заполняется источником родителя, но при необходимости значение можно переопределить.

При работе с C# действует тот же принцип наследования, как показано в следующем примере: var root = new VisualElement(); var parentData = ScriptableObject.CreateInstance<PlayerDataSO>(); parentData.Health = 100;

C#
// Assign a data source to the root element root.dataSource = parentData;

var child = new VisualElement();

var childData = ScriptableObject.CreateInstance<PlayerDataSO>(); childData.Health = 50;

C#
// Override the inherited data source for the child child.dataSource = childData;

root.Add(child);
Здесь дочерний элемент переопределяет значение родителя и получает независимый источник данных.

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

Режимы привязки в UI Builder позволяют управлять потоком данных между источником данных и интерфейсом.

В UI Builder и API C# доступны следующие режимы:

- TwoWay (по умолчанию): изменения передаются в обе стороны - из источника данных в интерфейс и из интерфейса в источник. Используйте этот режим для интерактивных элементов, через которые пользователь может менять данные, например ползунков и текстовых полей.

- ToTarget: данные передаются только из источника в интерфейс. Подходит для элементов интерфейса, доступных только для чтения. - ToSource: данные передаются только из интерфейса в источник. Полезно для полей ввода, в которых не требуется изначально отображать текущее значение. - ToTargetOnce: данные передаются из источника в интерфейс один раз; последующие изменения источника не отслеживаются.

Пример: привязка данных шкалы здоровья Рассмотрим практический пример базовой привязки данных в UI Toolkit. В демонстрационной сцене используется простая шкала здоровья, которая динамически обновляется в соответствии с запасом здоровья игрока.

Демонстрационная сцена. Следующие примеры входят в демонстрацию Data Binding проекта QuizU.

Чтобы открыть её во время выполнения, в главном меню выберите Demos > Data Binding. Также можно отключить загрузчик командой Quiz > Don’t Load Bootstrap Scene on Play и загрузить сцену DataBindingDemo напрямую. В демонстрации есть две шкалы здоровья: привязки одной созданы в UXML с помощью UI Builder, а привязки другой - в C#.

Шкала здоровья отображает данные игрока.

Подготовка источника данных Сведения и характеристики игрока в примере хранятся в ScriptableObject PlayerDataSO. Нужные свойства PlayerDataSO помечены атрибутом CreateProperty, поэтому их можно использовать в привязках. Каждая шкала здоровья представляет лишь часть данных PlayerDataSO: имя игрока и значения здоровья. Во фрагменте класса показаны некоторые свойства и связанные с ними поля:

C#
using System;
using Unity.Properties;
using UnityEngine;
using UnityEngine.UIElements;
[CreateAssetMenu(fileName = "PlayerDataSO", menuName = "Demos/Player_Data")] public class PlayerDataSO : ScriptableObject
C#
public string PlayerName => m_PlayerName;
[CreateProperty] public int CurrentHealth => Mathf.Clamp(m_CurrentHealth, 0, m_MaximumHealth);
[CreateProperty] public int MaximumHealth => m_MaximumHealth;
[SerializeField] string m_PlayerName;
[SerializeField] int m_MaximumHealth = 100;
[SerializeField] [Range(0, k_MaxHealthRange)] int m_CurrentHealth = 100;
const int k_MaxHealthRange = 200;
Для отображения этих сведений на экране интерфейс использует конкретные пути к данным, например PlayerName, CurrentHealth и MaximumHealth.

Источник данных содержит сведения о здоровье из ScriptableObject PlayerDataSO.

Привязка данных в UI Builder/UXML UI Builder предлагает наглядный интерактивный способ связывать элементы интерфейса с данными. Он удобен художникам интерфейсов, предпочитающим работать с дизайном визуально, и разработчикам, которым важна мгновенная обратная связь во время настройки. Кроме того, это полезный учебный инструмент для тех, кто только знакомится с привязкой данных.

В демонстрационной сцене все привязки шкалы здоровья Player One настроены в UI Builder. Для этого необходимо: - Выбрать корневой элемент: в иерархии выберите корневой элемент, содержащий шкалу здоровья. В данном примере самый верхний контейнер - элемент demo_container-uxml.

- Назначить источник данных: в разделе Data Binding окна Inspector укажите ресурс ScriptableObject как источник. Он будет назначен выбранному элементу и унаследован всеми дочерними элементами. - Задать пути к источнику данных: укажите пути, связывающие отдельные элементы интерфейса с соответствующими свойствами ScriptableObject, например PlayerDataSO.PlayerName.

Базовая шкала здоровья

После назначения источника данных корневому элементу он должен появиться как источник по умолчанию у дочерних элементов. Остаётся лишь указать правильный путь к данным. В таблице показано, как привязки соединяют свойства элементов интерфейса со свойствами ScriptableObject: Элемент UI

Свойство элемента UI

Привязанное свойство

Примечание

health-bar__player-name

text

PlayerName

Имя игрока

health-bar__current-health

text

CurrentHealth

Текущее здоровье

health-bar__max-health

text

MaximumHealth

Максимальное здоровье

health-bar__progress

style.width

Progress

Динамическая ширина шкалы

После завершения настройки шкала здоровья обновляется в реальном времени, отображая значения и полосу текущего здоровья игрока. Источник данных легко заменить: назначьте другой ресурс ScriptableObject, и интерфейс автоматически покажет новые значения, сохранив прежние привязки.

При смене источника данных привязки обновляются.

Привязки, настроенные в UI Builder, добавляются непосредственно в файл UXML: для каждого привязанного элемента создаётся блок <Bindings>. Ниже приведён фрагмент созданного UXML, в котором свойство text элемента health-bar__player-name связано со свойством PlayerName. Для удобства чтения некоторые атрибуты опущены:

<ui:Label text="Placeholder" name="health-bar__player-name" class="health-bar__player-name"> <Bindings> />

<ui:DataBinding property="text" data-source-path="PlayerName" binding-mode="ToTarget" </Bindings>

</ui:Label> Опытные пользователи могут создавать такие привязки непосредственно в UXML. При большом числе привязок ручное редактирование кода может обеспечить более точный контроль и ускорить работу. Написанный вручную UXML также формирует более понятные различия в системе контроля версий, поэтому конфликты слияния проще разрешать, а изменения - отслеживать.

Настройка привязки данных в C# UI Builder отлично подходит для прототипирования со статическими данными, например заранее созданными ресурсами ScriptableObject. Однако динамические данные во время выполнения часто удобнее обрабатывать в C#. В следующем примере кода показано, как в демонстрационной сцене работает шкала здоровья Player Two:

C#
using UnityEngine;
using UnityEngine.UIElements;
using Unity.Properties;
public class HealthBar : MonoBehaviour {
    [SerializeField] PlayerDataSO m_HealthData;
    public void Initialize(VisualElement root) {
        var m_PlayerName = root.Q<Label>("health-bar__player-name");
        root.dataSource = m_HealthData;
        m_PlayerName.SetBinding("text", new DataBinding() {
            dataSourcePath = new PropertyPath(nameof(PlayerDataSO.PlayerName)), bindingMode = BindingMode.ToTarget
        }
        );
    }
}

Сценарий HealthBar выполняет настройку в методе Initialize, который основной управляющий сценарий вызывает из OnEnable. - Сначала выполняется запрос элемента health-bar__player-name. Затем данные ScriptableObject назначаются ему как источник. - После этого метод SetBinding связывает свойство text с новым экземпляром DataBinding и задаёт параметры dataSourcePath и bindingMode. Все четыре привязки из приведённой выше таблицы настраиваются аналогично. Изменяйте CurrentHealth ползунком ScriptableObject или пользовательским Property Drawer редактора. Демонстрация содержит элементы управления для тестирования в режиме Play: +, - и Select позволяют увеличивать или уменьшать значение либо выбирать ScriptableObject. Шкала здоровья динамически обновляется при каждом изменении.

HealthBar синхронизируется со значением CurrentHealth.

Работа с неразрешёнными привязками данных Unity 6 также поддерживает гибридный рабочий процесс, сочетающий визуальную настройку в UI Builder с гибкостью сценариев. Вместо жёсткого задания источника данных в UXML можно указать Data Source Type, оставив сам источник неразрешённым. UI Builder отмечает такие незавершённые привязки полым значком. Это означает, что пути и типы уже заданы, но источник данных ещё не назначен.

Неразрешённая привязка данных отмечается полым значком.

Во время выполнения источник данных можно назначить одной строкой кода. Например:

myElement.dataSource = myNewDataSource;

Здесь назначение myNewDataSource элементу myElement разрешает привязки-заполнители, определённые в UXML, после чего интерфейс обновляется автоматически. Это устраняет повторяющиеся вызовы SetBinding и сохраняет гибкость UXML. Например, в проекте Dragon Crashers пути к данным заранее определены в UXML, а фактические источники назначаются во время выполнения. При нажатии кнопок перехода к следующему и последнему персонажу выбранный персонаж становится текущим источником данных. Для смены источника изменять UXML не требуется. После назначения нового источника неразрешённые привязки отображают правильные характеристики персонажа.

Обновление источника данных в примере Dragon Crashers

Примечание: если в UXML указан конкретный источник данных, например data-source="PlayerDataSO.asset", привязка становится фиксированной, и изменить её во время выполнения нельзя. Чтобы разрешить изменение, оставьте атрибут data-source пустым или используйте data-source-type. Пример такого гибридного процесса приведён в разделе о привязке списка к ListView.

Конвертеры типов Конвертеры типов в Unity 6 преобразуют исходные данные в более удобный для пользователя формат отображения. Они работают как посредники между источником данных и интерфейсом, представляя значения в интуитивно понятном виде. Например, конвертер может переводить радианы в градусы или преобразовывать числовой запас здоровья в цвет шкалы. Так интерфейс показывает информацию ясно и наглядно, а писать большой объём логики преобразования вручную не приходится. Unity 6 поддерживает две категории конвертеров типов: - Глобальные конвертеры: применяются к любым привязкам, которым требуется определённое преобразование типа. Например, глобальный конвертер может преобразовывать любое значение здоровья типа float в цвет или объекты Color в значения StyleColor, обеспечивая единообразное поведение во всём интерфейсе. - Конвертеры отдельных привязок: применяются к конкретным привязкам данных и обеспечивают более точное управление.

Пример: преобразование значения в цвет Шкала, меняющая цвет в зависимости от здоровья игрока, наглядно демонстрирует привязку данных с конвертером типа. Текущее здоровье сопоставляется с цветовым градиентом: например, зелёный означает высокий запас здоровья, жёлтый - низкий, красный - критический. Благодаря этому игрок быстро оценивает своё состояние во время игры. Пример можно увидеть в сцене DataBindingDemo проекта QuizU.

Настройка HealthDataConverter В сцене DataBindingDemo класс HealthBarWithConverter использует функции статического класса HealthDataConverter, чтобы зарегистрировать несколько DataConverter:

- Процент здоровья управляет цветовым градиентом шкалы: от зелёного при полном здоровье до красного при критическом. - Одна надпись представляет числовое значение строкой с процентом, например «75%».

- Другая сопоставляет тот же процент здоровья текстовому состоянию, например Full, Mid или Critical.

Ниже приведён фрагмент класса HealthDataConverter:

C#
static class HealthDataConverter {
    static readonly Color s_FullColor = new Color(0.2f, 1f, 0.2f);
    static readonly Color s_MidColor = Color.yellow;
    static readonly Color s_LowColor = new Color(1f, 0.3f, 0f);
    static readonly Color s_CriticalColor = Color.red;
C#
static void Register() { RegisterHealthColorConverter(); // … }

static void RegisterHealthColorConverter() {
    var colorConverter = new ConverterGroup("HealthColor");
    colorConverter.AddConverter((ref float healthPercentage) => {
        if (healthPercentage > 0.5f) {
            return new StyleColor(Color.Lerp(s_MidColor, s_FullColor, (healthPercentage - 0.5f) * 2f));
        }
        else if (healthPercentage > 0.25f) {
            return new StyleColor(Color.Lerp(s_LowColor, s_MidColor, (healthPercentage - 0.25f) * 4f));
        }
        else {
            return new StyleColor(Color.Lerp(s_CriticalColor, s_LowColor, healthPercentage * 4f));
        }
    }
    );
    ConverterGroups.RegisterConverterGroup(colorConverter);
}
// …

}

Приведённая выше логика создаёт ConverterGroup HealthColor, который преобразует долю здоровья типа float в диапазоне от 0 до 1 в соответствующее значение StyleColor между красным при низком здоровье и зелёным при полном. Класс HealthDataConverter также содержит конвертеры для двух надписей. Они представляют свойство HealthPercentage объекта PlayerDataSO в виде форматированных строк. Несколько конвертеров можно объединить в одну ConverterGroup, однако для удобства чтения в демонстрации они разделены по разным группам.

Использование конвертеров типов в UI Builder.

Использование HealthBarWithConverter Обратите внимание: вся фактическая логика находится в классе HealthDataConverter. Класс HealthBarWithConverter лишь выполняет следующее:

C#
public class HealthBarWithConverter : HealthBar {
    #if UNITY_EDITOR [UnityEditor.InitializeOnLoadMethod] #else [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] #endif public static void RegisterConverters() { HealthDataConverter.Register(); }
}

Обратите внимание на следующее:

- UnityEditor.InitializeOnLoadMethod регистрирует ConverterGroup и делает её доступной в UI Builder, чтобы группу можно было увидеть и применить в редакторе. - RuntimeInitializeOnLoadMethod обеспечивает доступность ConverterGroup во время выполнения игры. Директива препроцессора #if UNITY_EDITOR гарантирует вызов нужного метода в зависимости от того, выполняется код в редакторе или во время игры.

Применение DataConverter в UI Builder После регистрации DataConverter можно применить к любой привязке, которой требуется это преобразование. Чтобы использовать его непосредственно в UI Builder: 1. Откройте файл UXML и выберите элемент шкалы. В проекте QuizU пример настройки можно посмотреть в файле RuntimeDataBinding.uxml. 2. Назначьте ScriptableObject PlayerDataSO в качестве Data Source. 3. Свяжите стилевое свойство backgroundColor шкалы с путём данных HealthPercentage. 4. Используйте ConverterGroup HealthColor, чтобы преобразовать процент здоровья в цвет фона шкалы.

Настройка привязки данных для шкалы здоровья.

5. Теперь при перетаскивании ползунка CurrentHealth в ScriptableObject PlayerDataSO цвет шкалы здоровья обновляется. Градиент плавно интерполируется от зелёного при полном здоровье через жёлтый при среднем и оранжевый при низком к красному при критическом.

Теперь этот глобальный DataConverter доступен во всём приложении, где требуется преобразовать значение float в такой цветовой градиент.

Конвертер HealthColor изменяет цвет шкалы.

Рекомендации При работе с конвертерами типов придерживайтесь следующих рекомендаций:

- Сведите выделения памяти к минимуму: делегаты преобразования должны оставаться легковесными, особенно при частых вызовах, чтобы избежать лишних накладных расходов.

- Не усложняйте: создавайте простые специализированные конвертеры для быстрых преобразований. Не помещайте в них сложную или ресурсоёмкую логику. - Выполняйте преобразование в источнике данных: базовые преобразования обрабатывайте непосредственно в источнике - например, заранее форматируйте процент здоровья в свойстве ScriptableObject. Оставляйте DataConverter для преобразований, относящихся именно к привязкам интерфейса.

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

TeamList связывает список с ListView.

С помощью привязки во время выполнения ListView можно напрямую связать с источником данных, например ScriptableObject. ListView автоматически отслеживает изменения, упрощая настройку и сопровождение. Чтобы привязать ListView к списку, сначала создайте несколько неразрешённых привязок, а затем завершите их настройку во время выполнения.

Настройка списка и шаблонов Чтобы подготовить ListView к привязке данных, выполните следующие действия:

1. Определите источник данных. ListView необходим список данных. В этой демонстрации ScriptableObject TeamSO содержит список объектов PlayerDataSO. Каждый элемент списка соответствует одной строке ListView. 2. Создайте шаблон элемента UXML. В UI Builder разработайте шаблон UXML, то есть VisualTreeAsset, который определяет вид одного элемента списка. Например, шаблон team-list-item из демонстрации содержит имя игрока и несколько свойств Texture2D. Не указывайте источник данных напрямую: задайте в UI Builder параметры Data Source Type и Data Source Path. Привязка останется неразрешённой, и её можно будет завершить позднее во время выполнения.

Разработка ресурса визуального дерева в UI Builder.

3. Добавьте ListView в основной интерфейс. В другом файле UXML добавьте элемент ListView, который будет отображать весь список игроков. Назначьте созданный шаблон свойству Item Template списка. Теперь ListView знает, как должна выглядеть каждая строка, но конкретный источник данных ещё не определён.

Добавление шаблона в ListView.

В ListView демонстрационной сцены используются лишь несколько базовых параметров, показанных выше. Расширенные возможности описаны в официальной документации ListView.

Завершение привязки во время выполнения Во время выполнения простой сценарий TeamList завершает привязку, предоставляя фактический источник данных. Следующие строки разрешают ранее незавершённые привязки:

C#
// Set the data source m_ListView.dataSource = m_TeamData;
// Bind the "itemsSource" to the Players list m_ListView.SetBinding("itemsSource", new DataBinding {

    dataSourcePath = new PropertyPath("Players")
}
);
Здесь m_TeamData, экземпляр TeamSO, назначается списку ListView. Однократный вызов SetBinding связывает свойство Players с itemsSource, после чего ListView заполняет строки интерфейса. Поскольку до начала выполнения эти привязки в UXML остаются неразрешёнными, подключать каждый элемент списка отдельно не требуется. UI Toolkit самостоятельно разрешает привязки и заполняет данными все элементы. Любые изменения списка в источнике - добавление, удаление или перестановка игроков - сразу появляются в интерфейсе без дополнительного кода.

Интерфейс отражает изменения списка Player.

Помните: гибридный подход к привязке данных позволяет значительно сократить объём повторяющегося шаблонного кода. Задав в UXML пути-заполнители, можно отложить назначение фактического источника до времени выполнения. Если модель данных изменится, переписывать всю логику привязки не придётся: достаточно одного обновления при запуске, чтобы переключить интерфейс на новый источник. Полное руководство по привязке ListView к списку приведено на этой странице документации. .

Оптимизация привязки данных Эффективные привязки помогают поддерживать высокую производительность интерфейса. Избыточные и дублирующиеся привязки перегружают систему, вызывают лишние обновления и снижают производительность. Это особенно важно для сложных или ресурсоёмких интерфейсов. По умолчанию система привязки во время выполнения обновляет элементы интерфейса каждый кадр. Для небольшого приложения это обеспечивает хорошую отзывчивость, но при большом числе привязок может стать узким местом. В этом разделе рассматриваются способы повысить эффективность привязки данных в крупных проектах.

Работа с типами-значениями Если источник данных использует типы-значения, например int, float или struct, учитывайте затраты на упаковку. Свойство dataSource имеет тип object, поэтому частое преобразование типов-значений создаёт дополнительные расходы. Чтобы снизить их, избегайте лишних привязок и повторных обновлений свойств типов-значений.

Сокращение накладных расходов Сначала найдите привязки, которые несколько раз обновляют одни и те же элементы или отслеживают редко меняющиеся данные. Объедините либо удалите их, чтобы сократить лишнюю работу. По возможности используйте плоские простые структуры данных вместо сложных иерархий - это помогает избежать задержек из-за частого поиска значений. Ресурсоёмкие вычисления стоит выполнять заранее или кэшировать их результаты. Привязка к заранее рассчитанным значениям уменьшает вычислительную нагрузку и исключает повторные расчёты. Часто обновляемые привязки оставляйте только у элементов, которым это действительно необходимо. Если постоянная синхронизация не нужна, удалите привязку и назначайте значение напрямую либо обновляйте его по событию.

Использование условий обновления Привязки обновляются в соответствии с условиями, которые определяют частоту синхронизации интерфейса с источником данных. Это позволяет найти баланс между производительностью и отзывчивостью. Доступны следующие варианты:

- Каждый кадр: непрерывное обновление. Используйте для элементов, которым требуется постоянная синхронизация, например для шкал здоровья из примера. - При обнаружении изменений: обновление выполняется, когда меняется источник данных, либо каждый кадр, если обнаружить изменение невозможно. Подходит, например, для панелей характеристик и списков инвентаря, основанных на наблюдаемых данных.

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

Версионирование и отслеживание изменений Чтобы сократить число лишних обновлений, добавьте в источники данных версионирование и отслеживание изменений. Повысить эффективность привязки помогают два интерфейса: - IDataSourceViewHashProvider: отслеживает общие изменения с помощью хеша версии и запускает обновление только при изменении источника. Полезен для статических и полустатических данных, которые обновляются редко. - INotifyBindablePropertyChanged: отслеживает изменения отдельных свойств и обновляет только затронутые привязки, обеспечивая более точное управление. Добавьте эти интерфейсы в источник данных. Их можно использовать по отдельности или вместе для более точного управления обновлениями. Примеры и рекомендации приведены на этой странице документации.

Совет: дополнительные рекомендации по оптимизации UI Toolkit В докладе Unite 2024 об оптимизации UI Toolkit рассматриваются цепочки вызовов отрисовки и влияние размеров буферов, рекомендации по динамическим атласам, а также работа с ограничениями, включая пользовательские шейдеры и трёхмерные интерфейсы.