Туториал 2. Greeter
Второй шаг обучающего маршрута. Пользователь вводит имя — приветствие обновляется в реальном времени.
Изучаем: [TwoWayBind], реактивность через On*Changed
Предполагается знание: Counter — [OneWayBind], [RelayCommand]
Что строим
[ Введите имя... ] → Привет, Владислав!
InputField Text
Текст обновляется при каждом нажатии клавиши — без кнопки.
Отличие от Counter
В Counter данные шли только в одну сторону: ViewModel → UI.
Здесь добавляется обратный поток: UI → ViewModel. Когда пользователь печатает в InputField, значение записывается обратно в ViewModel, которая реагирует и обновляет приветствие.
ViewModel ──────────────────────► UI
(OneWay: Greeting)
ViewModel ◄──────────────────────► UI
(TwoWay: Name)
Шаг 1: ViewModel
using Aspid.MVVM;
[ViewModel]
public sealed partial class GreeterViewModel
{
// TwoWay — UI может читать и записывать это поле.
// Когда InputField изменится, Name обновится автоматически.
[TwoWayBind] private string _name = "";
// OneWay — только чтение для UI. Мы сами управляем значением.
[OneWayBind] private string _greeting = "Введите имя";
// Partial-метод, который Source Generator вызывает при каждом изменении Name.
// Имя формируется по правилу: On + {PropertyName} + Changed.
partial void OnNameChanged(string newValue) =>
SetGreeting(string.IsNullOrEmpty(newValue)
? "Введите имя"
: $"Привет, {newValue}!");
}
Как работает partial void On*Changed
Source Generator генерирует пустой partial void OnNameChanged(string newValue) в своей части класса. Ваша реализация вызывается внутри сгенерированного сеттера Name каждый раз при изменении значения.
InputField изменился
→ TwoWayBinder записывает значение в Name
→ сгенерированный SetName вызывает OnNameChanged
→ OnNameChanged вызывает SetGreeting
→ TextBinder обновляет Text
Вся цепочка — без рефлексии, прямые вызовы.
Разница между TwoWay и OneWay
[OneWayBind] | [TwoWayBind] | |
|---|---|---|
| ViewModel → UI | Да | Да |
| UI → ViewModel | Нет | Да |
| Генерирует | Set*, property, event | То же + внутренний обратный канал |
| Типичное применение | Текст, счётчики, состояния | InputField, Toggle, Slider |
Шаг 2: View
using UnityEngine;
using Aspid.MVVM;
[View]
public sealed partial class GreeterView : MonoView
{
// InputFieldMonoBinder — читает ввод из InputField и записывает в Name (TwoWay).
[SerializeField] private MonoBinder _name;
// TextMonoBinder — отображает Greeting (OneWay).
[SerializeField] private MonoBinder _greeting;
}
Никаких кнопок — всё реактивно.
Шаг 3: Bootstrap
using UnityEngine;
using Aspid.MVVM;
public sealed class Bootstrap : MonoBehaviour
{
[SerializeField] private GreeterView _greeterView;
private void Awake()
{
_greeterView.Initialize(new GreeterViewModel());
}
private void OnDestroy()
{
_greeterView.DeinitializeView()?.DisposeViewModel();
}
}
Шаг 4: Настройка сцены
Иерархия объектов
Greeter Scene
├── Bootstrap (Bootstrap.cs)
└── Greeter UI
├── GreeterView (GreeterView.cs)
├── Name Input (InputField + InputFieldMonoBinder)
└── Greeting Text (TextMonoBinder)
В Inspector
- Name Input → добавьте
InputField(UI Toolkit или uGUI) и компонентInputFieldMonoBinder - Greeting Text → добавьте
TextMeshProи компонентTextMonoBinder - GreeterView → перетащите
InputFieldMonoBinderв полеName,TextMonoBinderв полеGreeting - Bootstrap → назначьте ссылку на
GreeterView
Шаг 5: раскрасить приветствие конвертером
ViewModel отдаёт "Hi, Vlad" — обычную строку. Покрасить имя это задача View, и во ViewModel её
тащить не надо: для этого есть конвертеры.
У TextMonoBinder есть поле Converter. Нажмите на него и выберите в выпадающем списке
Aspid → Composition → Sequence — цепочку, — а внутрь неё положите два звена:
- Aspid → String → Rich Text Color — заворачивает текст в тег
<color=…>; задайте цвет. - Aspid → String → String Format с форматом
{0}!— добавляет восклицательный знак.
Порядок важен: цепочка применяет звенья сверху вниз, поэтому восклицательный знак останется снаружи тега цвета.
Ни строки кода. Раньше этот сэмпл возил с собой собственный
PaintNameConverter— теперьRichTextColorConverterпоставляется с пакетом, и писать свой не нужно. Полный каталог из 211 конвертеров — в Конвертерах.
Текст, который ввёл игрок, — отдельный разговор. TMP исполняет разметку в любой строке: ник
<size=400%>растянет ярлык на экране каждого, кто его увидит. Если полеNameдоступно другим игрокам, первым звеном цепочки должен идтиRichTextSanitizeConverterилиRichTextNoParseConverter.
Как это сделано в сэмпле пакета
Сэмпл 02. Greeter решает ту же задачу без Bootstrap и без On*Changed:
[ViewModel]
public sealed partial class GreeterMonoViewModel : MonoViewModel
{
[BindAlso(nameof(Greeting))]
[TwoWayBind]
[SerializeField] private string _name;
private string Greeting =>
string.IsNullOrEmpty(Name) ? string.Empty : $"Hi, {Name}!";
[RelayCommand]
private void Clear() => Name = string.Empty;
}
MonoViewModelживёт на GameObject, аViewInitializerв сцене соединяет его сMonoView— кода инициализации нет.[BindAlso(nameof(Greeting))]заново отправляет вычисляемое свойствоGreetingпри каждом измененииName; само свойство никакого атрибута не требует.
Оба подхода равноправны: On*Changed удобен, когда реакция — это действие, [BindAlso] — когда реакция — это производное значение.
Расширение: кнопка сброса
Можно совместить реактивность с командой:
[ViewModel]
public sealed partial class GreeterViewModel
{
[TwoWayBind] private string _name = "";
[OneWayBind] private string _greeting = "Введите имя";
partial void OnNameChanged(string newValue) =>
SetGreeting(string.IsNullOrEmpty(newValue)
? "Введите имя"
: $"Привет, {newValue}!");
[RelayCommand]
private void Clear() => SetName("");
}
[View]
public sealed partial class GreeterView : MonoView
{
[SerializeField] private MonoBinder _name;
[SerializeField] private MonoBinder _greeting;
[SerializeField] private MonoBinder[] _clear;
}
SetName("") очистит InputField — потому что Name — это TwoWay, и изменение в ViewModel отразится в UI.
Резюме
| Концепция | Что сделали |
|---|---|
[TwoWayBind] | _name синхронизируется с InputField в обе стороны |
partial void On*Changed | Реакция на изменение без явной подписки |
| Реактивность | Нет кнопки — UI обновляется при каждом вводе |
| Конвертеры | Цвет и восклицательный знак добавлены во View, ViewModel про них не знает |
Следующий шаг
Bind Modes → — все четыре режима биндинга на одном экране и свой конвертер.