Применяем MVVM в Unity3D с помощью NData

в 20:57, , рубрики: game development, Gamedev, mvvm, ngui, unity3d, Программирование

Применяем 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), который добавляет возможность оповещения об изменении данной переменной.

Применяем MVVM в Unity3D с помощью NData
(привязка текста в переменной с текстом в UILabel)

Применяем MVVM в Unity3D с помощью NData
(привязка события нажатия на кнопку к методу)

Пример с гридом:
1.Создаем коллекцию вьюшек (+саму вьюшку), которые хотим впихнуть в грид/таблицу

private readonly EZData.Collection<ProductItemContext> _privateItems = new EZData.Collection<ProductItemContext>(false);
public EZData.Collection<ProductItemContext> Items { get { return _privateItems; } }

2. Привязываем ее к таблице/гриду через биндинг и назначаем prefab, к которому привяжем вьюшку элемента таблицы:
Применяем MVVM в Unity3D с помощью NData

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<Collider>())
		{
			collider.enabled = visible;	
		}

		foreach(var widget in tr.GetComponents<UIWidget>())
		{
			widget.enabled = visible;
		}
	}
...

Вот такой способ проверки изменения текста.

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. После релиза игры столкнулись со множеством криков о том, что это игра извращенцев/для извращенцев/ название худшее в мире/ упоротый медведь и т.д. Не думали мы, что у всех название будет ассоциироваться исключительно с пресловутым Педобиром) Разве есть у них что-то общее, кроме созвучия?

Автор: afrokick

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js