Применяем 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<ProductItemContext> _privateItems = new EZData.Collection<ProductItemContext>(false);
    public EZData.Collection<ProductItemContext> Items { get { return _privateItems; } }
    


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


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

    Подробнее
    Реклама
    Комментарии 5
    • 0
      www.tasharen.com/forum/index.php?topic=8808.0
      www.youtube.com/watch?v=4ufvpR6HHp4
      Сам использую подписку / редирект на паблик методы одного класса в сцене без binding-а каждого поля (часто нужна кастомная валидация с зависимостями и вся прелесть быстрой связки пропадает, все равно нужно писать код), в котором уже разруливаю логику по другим блокам — этакий MVC.
      • +10
        Pido Bear? O_o
        • +4
          P.S. После релиза игры столкнулись со множеством криков о том, что это игра извращенцев/для извращенцев/ название худшее в мире/ упоротый медведь и т.д. Не думали мы, что у всех название будет ассоциироваться исключительно с пресловутым Педобиром) Разве есть у них что-то общее, кроме созвучия?

          Название действительно не очень удачное
          • +6
            P.S. После релиза игры столкнулись со множеством криков о том, что это игра извращенцев/для извращенцев/ название худшее в мире/ упоротый медведь и т.д. Не думали мы, что у всех название будет ассоциироваться исключительно с пресловутым Педобиром) Разве есть у них что-то общее, кроме созвучия?


            А как, по-вашему переводится слово pido с английского? Нету такого слова (кроме http://www.urbandictionary.com/define.php?term=Pido, что как раз по теме педобира). Ну и не надо говорить, что называя так игру, вы не расчитывали на дешевый пиар, все равно никто не поверит :)
            • –4
              Думали, что возможно это даст какой-то толчок в установках, но на деле все оказалось видимо наоборот. Это грабли, на которые наступили в первый раз в выборе названия. Хотя тут же на хабре ребята писали, что нужно подходить к этому вопросу очень внимательно.

              Век живи — век учись!

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.