Практика / 15 минут

Соберите небольшой экран с локальным обновлением.

Создадим счётчик проектов внутри Unity UIDocument: смонтируем дерево, вынесем изменяемое значение в State<T> и правильно освободим интерфейс.

Начните с обычного корня UI Toolkit.

Создайте GameObject, добавьте UIDocument и назначьте Panel Settings. LumaFlow не заменяет UI Toolkit: он монтирует своё дерево внутри rootVisualElement.

Граница владения должна быть явной.

MonoBehaviour владеет UIDocument и возвращённым MountHandle. LumaFlow управляет только созданным host-элементом и его дочерним деревом.

Монтируйте дерево один раз при активации host.

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

ProjectCounterScreen.cs
using LumaFlow;
using UnityEngine;
using UnityEngine.UIElements;
using Framework = LumaFlow.LumaFlow;

[RequireComponent(typeof(UIDocument))]
public sealed class ProjectCounterScreen : MonoBehaviour {
  private MountHandle? _mount;

  private void OnEnable() {
    var root = GetComponent<UIDocument>().rootVisualElement;
    _mount = Framework.Mount(BuildScreen(), root);
  }

  private static Widget BuildScreen() =>
    new Text("Проекты");
}

Опишите изменяемое значение явно.

Widget — неизменяемое описание, поэтому счётчик хранится вне него. ReactiveBuilder<T> читает значение; при изменении перестраивается именно эта ветвь.

ProjectCounterScreen.cs
private readonly State<int> _count = new(0);

private Widget BuildScreen() => new Column(
  gap: 12f,
  children: new Widget[] {
    new Text("Обзор проектов"),
    new ReactiveBuilder<int>(
      _count, value => new Text($"Активных проектов: {value}")),
    new Button("Добавить проект", () => _count.Value++)
  });
Не создавайте долгоживущие объекты внутри Build.

Build может вызываться многократно. State и контроллер должны принадлежать компоненту или State объекта StatefulWidget.

Освободите mount при деактивации документа.

Dispose() удаляет host LumaFlow и подписки. Нативные соседние элементы в корне UIDocument он не затрагивает.

ProjectCounterScreen.cs
private void OnDisable() {
  _mount?.Dispose();
  _mount = null;
}

Теперь разберите, почему этот код работает.

Следующий материал объясняет Widget, Element, reconciliation, State и Key без привязки к абстрактным терминам.

Перейти к архитектуре →