Применяем MVVM в Unity3D с помощью NData
Привет! В этом посте хотел бы рассказать тебе, мой любимый хабр, о плагине, который увеличил мою продуктивность в работе с UI в несколько раз. Связка с которой я работаю выглядит следующим образом: Unity3D + NGUI + NData. По желанию, можно использовать IoC+DI, но идеального варианта, чтобы работала под iOS, Android и WinPhone, пока не нашлось.
Про сам паттерн можно прочесть здесь или здесь. Одной из плюх подхода является биндинг данных, т.е. связывание представления с данными и автоматическое оповещение об изменении этих данных нужного представления.
Информацию о плагине NData можно найти на сайте. И да, он стоит 45$)
(на картинке выше одна из последних игр, которую собрал с помощью Unity3d+NGUI+NData)Как было
Раньше, чтобы сделать пользовательский интерфейс, приходилось создавать один/несколько классов (GUIManager, MenuView, GameViewetc.), которые содержали в себе ссылки на компоненты визуальных элементов (UILabel, UISprite, UIGrid и т.д.). Дальше нужно было в каком-то месте обновлять этот компонент. У меня изменение представления происходило в этих же классах. Но стали появляться однотипные действия такие, как:-в зависимости от переменной типа bool показать/скрыть элемент-обновить текст, если переменная типа string изменилась-если выполняется какое-то условие, к примеру, Points> 5, то показать/скрыть элемент
Т.е. приходилось все время писать однотипный код. Стали даже мысли приходить, как можно это упростить. И тут…
Знакомство с NData
Мой приятель как-то рассказал мне о NData. А когда он продемонстрировал заполнение и управление списком элементов в гриде с помощью двух компонентов и одного свойства, я был поражен. Вот она, скорость разработки!
Процитирую список фич с оф. сайта:
— Two-way text bindings (including multi-bindings) with native .NET formatting capabilities.– Two-way float bindings for sliders.– Flexible bindings that allow visible and checked states of UI controls to be bound to properties in your code (also in two-way mode for check-boxes bound to Boolean values).– Command bindings for buttons that will trigger actions in your code.– Items source binding for connecting list control with attached item template prefab to collection of items in your code.– Code snippets for notifiable properties and collections for Visual Studio and Mono Develop.– Editor script for bindings validation.
Как видите, на этом списке биндингов можно построить почти любое приложение. Что я, собственно, и делаю последнее время.Не все так гладко на самом деле. После импорта плагина вылазят ошибки и их нужно исправить. Я записал видосы как установить плагин, как работать с компонентами и пр.
Что собой представляет биндинг? Это обычный MonoBehaviour скрипт, который связывает данный UI-элемент с какой-то переменной в коде через рефлексию. Переменная создается специального generic-типа (например, Property), который добавляет возможность оповещения об изменении данной переменной.
(привязка текста в переменной с текстом в UILabel)
(привязка события нажатия на кнопку к методу)
Пример с гридом:1.Создаем коллекцию вьюшек (+саму вьюшку), которые хотим впихнуть в грид/таблицу
private readonly EZData.Collection
3. Где-то в коде работаем с коллекцией (Add, Remove, Clear, GetItem (n)) и все изменения будут отображаться на экране.
… Items.Add (new ProductItemContext (prod)); … Готовый код можно вставлять из шаблонов/сниппетов
Печалька
Было бы все очень замечательно, если бы это не влияло на скорость работы приложения) Тот же VisibilityBinding делает проход по всей иерархии объекта и выключает UIWidget. Т.е. все время дергается GetComponents (), что печально.
…
private void SetVisible (Transform tr, bool visible)
{
foreach (var collider in tr.GetComponents
foreach (var widget in tr.GetComponents
void Update (){ if (_characterName!= NewCharacterName){ _characterName = NewCharacterName; } } В исходниках другое написано, но принцип тот же.Все мои «хаки» для оптимизации закончились на наследовании класса NguiBaseBinding от CachedMonoBehaviour, в котором закешированы transform и gameObject. Есть идея как можно оптимизировать постоянные GetComponent: сделать флаг для биндингов, указывающий, что элемент статичен и его компоненты можно закешировать + сделать свою реализацию GetComponent, которая бы по флагу брала из кэша компонент. Но похоже на костыль для костыля)
Вообще в ситуации, когда нужно часто менять видимость, проще перенести элемент в какие-нибудь запредельные координаты, это будет намного быстрее (к примеру, если выключаем окно со множеством элементов, то просто переносим его в x:10000 y:10000 z:0 и не нужно проходить по всей иерархии и искать компоненты).
Итого
Теперь собирать UI стало намного веселее и проще. Список биндингов покрывает практически все запросы. Если вдруг чего-то нет, то можно легко дописать свой биндинг, и делается это очень просто. Использую как для игрушек, так и для бизнес-приложений.
Конечно, это не «золотая пуля», но универсальность этого решения поражает. Использую вкупе с Uniject (IoC+DI, который правда под WinPhone не работает), поэтому давно забыл об NullReferenceException (не нужно заботиться об создании/уничтожении объектов, установление ссылок на объекты) и можно легко подменять модель/сервисы/вьюшки.
Хабрахабровцы, поделитесь, пожалуйста, своим опытом разработки интерфейсов!
Спасибо за внимание!
P.S. После релиза игры столкнулись со множеством криков о том, что это игра извращенцев/для извращенцев/ название худшее в мире/ упоротый медведь и т.д. Не думали мы, что у всех название будет ассоциироваться исключительно с пресловутым Педобиром) Разве есть у них что-то общее, кроме созвучия?