В этой статье мы поделимся нашим опытом разработки системы SMS поп-апов в Unity, на базе которого мы реализовываем проект в области бизнес-аналитики. Вы узнаете, как можно сделать нативным и понятным добавление информации, и как никогда не программирующие люди могут легко управлять данными и, как следствие, принимать правильные решения на их основе.
Заметка от партнера IT-центра МАИ и организатора магистерской программы “VR/AR & AI” — компании PHYGITALISM.
Система поп-ап окон в приложении
Мы уже писали о нашем решении для бизнес-аналитики в этой статье, направленном на удобное управление информацией. В процессе его разработки нам было важно сделать так, чтобы любой человек вне зависимости от уровня знаний языков программирования и BI-систем мог просто работать с информацией и ее анализировать.
Поскольку целевой аудиторией нашего решения являются аналитики и совет директоров, нам очень было очень важно сделать работу максимально понятной, а информацию – полной.
В режиме презентации к некоторым данным необходимо пояснение, которое можно отобразить с помощью поп-апов, или SMS. Это информационная кнопка, нажав на которую появляются дополнительные сведения.
Нашей целью было создать такую систему, чтоб можно было добавлять информационные блоки в рантайме к любому из существующих блоков на экране (UI элементам) по просьбам.
С какими проблемами мы столкнулись?
Изначально, чтобы добавить такой элемент, мы просто сохраняли позицию клика администратора на экране и там отображали SMS кнопку в нужный момент. Это было достаточно неудобно, так как нам нужно было запоминать на каком именно экране сейчас находимся, чтобы при запуске этого окна, отображалась в нужной позиции SMS. А еще к нам пришла проблема – элементы, которые отображаются на экране, могут быть все динамическими, т.е. быть в движении или каждый раз иметь разную позицию, но SMS кнопка должна всегда корректно отображаться в нужном месте.
В первой версии как только пришел запрос на добавление информации, мы просто вручную помещали данную SMS в нужный Игровой объект (Game Object), а затем присваивали ей ID. Для одной-двух SMS это было приемлемо и не занимало много времени и усилий разработчиков, но перед каждым релизом и показом приложения перед заказчиками, аналитики (администраторы) все чаще просили “быстренько” поменять контент или позицию, или вообще добавить новую SMS. Запросов на добавление таких SMS становилось все больше, у нас возникали трудности с поддержкой данной системы, т.к. параллельно разрабатывался функционал, который менял дизайн элементов, и нужно было постоянно менять и вносить правки. В один прекрасный момент, эти проблемы дошли до критической массы и было решено написать нормальную систему, максимально рассчитанную на кастомизацию администраторами.
Решение проблем: админ-панель и система якорей и контейнеров
Как решение мы придумали систему якорей и контейнеров. Это система, которая говорила администратору, что в данное место можно привязать SMS кнопку. В рантайме приложение можно перевести в режим администрирования, в котором администратор может видеть точки “монтирования” SMS.
В данном подходе необходимо только при разработке и верстке элемента учитывать контейнер для SMS. Заполнять или вводить ID нет нужды, все происходит автоматически: при валидации префаба или объекта происходит генерация уникального ID, который в дальнейшем будет отправляться на сервер и сопоставлять SMS с контейнером. Также в такой системе отпадает необходимость определения на каком экране сейчас находимся.
Разработанная система состоит из нескольких элементов: админ панели, SMS поп-апа и SMS-кнопки.
Каждый элемент разработанной системы можно редактировать в рантайм, прямиком в приложении или через сервер. Вся информация (данные о SMS, позиция на сцене, идентификатор элемента), кстати, также записывается на сервер и хранятся там, что дает постоянный доступ к нужным данным в любое время.
Давайте кратко рассмотрим каждый элемент данной системы по отдельности.
Admin panel
Админ панель – это простое меню, где можно задать контент для поп-апа непосредственно в рантайме приложения.
Админ панель состоит из совокупности нескольких блоков, где блок - это определенный тип информации которое нужно отобразить в раскрывающемся попапе. Блоки обговариваются между разработчиками и командой аналитиков. На данный момент их в приложении 3 вида: ссылка на изображение, изображение с компьютера (загрузка на сервер в последствии) и текстовый блок – аналог HTML страницы.
Реализация меню достаточно простая: каждый блок является динамически добавляемым и легко подменяется через простой механизм “Tabbed Menu”. Нам необходимо лишь для каждого блока наследоваться от указанного Пространства имен.
Блок из себя представляет логический элемент, который необходимо обработать, записать и передать на сервер. Вся информация о данных блока лежит в Мета данных, которые мы получаем с сервера.
Кроме данных о блоках, админ панель содержит информацию о самой SMS, а именно – активна ли SMS в данный момент и какой на данный момент блок выбран. При этом SMS может хранить в себе информацию о всех блоках сразу для удобства работы с информацией.
SMS (pop-up)
SMS, или поп-ап – всего лишь подставляемый презентер во время нажатия на кнопку на сцене. Подставление происходит динамически, в зависимости от того, что хранится в самой мете.
Визуальное представление (или презентер) для каждого блока происходит в зависимости от типа в мете. Презентер в свою очередь может быть любым, хоть это просто in-put строка или какой-то сложный визуальный элемент по типу HTML Editor.
Данное решение позволяет нам не зависеть от какой-то реализации, быстро подменять решения или просто напросто удалять без последствий и рефакторинга.
Заглядываем внутрь: как это работает?
Данный функционал состоит из двух основных классов, которые выполняют всю работу в этой системе: контейнеры и якори. Первый класс – контейнеры, является контейнером как для себя, для других контейнеров и для самого якоря.
public class SMSAnchorContainer : MonoBehaviour
{
public string id;
public string fullID;
protected virtual void OnEnable()
{
// если в иерархии выше нет никого, берем свой ИД как основу
if (transform.parent == null)
{
fullID = id;
return;
}
// ищем по иерархии все контейнеры и объединяем ИД
var parentsContainer = transform.parent.GetComponentInParent<SMSAnchorContainer>();
fullID = parentsContainer != null ? parentsContainer.fullID + "_" + id : id;
}
}
Из кода видно, что при активации объекта он проходится вверх по иерархии, собирает ID и создает один общий ID. Кастомизация становится очень удобной: можно создать общий контейнер, к примеру, само окно и внутри окна по областям сделать еще контейнеры. Класс достаточно простой и легковесный.
Следующий не менее важный класс – это сам по себе якорь, куда администратор может добавлять SMS. Для якоря, также как и для контейнера, нам необходим ID. Это нужно для хранения информации на сервере и сопоставления с SMS. Работает он достаточно просто: достаем первый попавшийся в иерархии контейнер, берем его ID объединяем с ID якоря.
private void OnValidate()
{
if (string.IsNullOrEmpty(id))
id = Guid.NewGuid().ToString();
container = transform;
}
Пример генерация ИД для якоря.
public string FullID
{
get
{
var foundedAnchorContainer = container.GetComponentsInParent<SMSAnchorContainer>(true);
if (foundedAnchorContainer.Length < 1)
{
return id;
}
return $"{foundedAnchorContainer[0].fullID}_{id}_{(parent == null ? transform.GetSiblingIndex() : parent.GetSiblingIndex())}";
}
}
Пример получения конечного ИД для якоря.
Что дальше? Просто в активации объекта подписываемся на нужные события, как на обновления самой SMS, так и на работу режима администрирования.
protected async void OnEnable()
{
// auto update data from server
MessageBroker.Default.Receive<SMSReloadMessage>().Subscribe(OnReload).AddTo(_cleanup);
// events for admin mode
_smsController
.Mode
.Subscribe(OnAdminModeUpdate)
.AddTo(_cleanup);
hover.HoverEvent += HoverOnHoverEvent;
hover.HoverOutEvent += HoverOnHoverOutEvent;
await Task.Yield();
// work with view
Show();
}
Ну и самое последнее что нужно сделать – это отобразить саму SMS, если она есть в данном якоре.
private async void Show()
{
var sms = await _smsStorage.GetByIdAsync(FullID);
if (sms != null)
{
_smsButton = _smsButtonPool.Spawn(sms.ID, transform);
_smsButtonID = sms.ID;
}
}
Для этого нужно достать из нашего хранилища SMS по ID, заспавнить из пула кнопку которая говорит о том что есть SMS и присвоить кнопке SMS.
Что мы получили на выходе?
Данная система очень сильно облегчила совместную работу разработчиков и администраторов – перестал нарушаться запланированный темп работы из-за появления срочных задач на переделку контента SMS или расположения самой кнопки внутри приложения.
Это не означает, что наша система является полностью законченной, у нас в планах есть большой пласт работы над добавлением нового функционала и кастомизации режима администратирования. С каждым обновлением приложения все больше появляется новых элементов, экранов и блоков, и мы планируем в первую очередь добавить возможность администраторам самим из готовых инструментов просто и удобно создавать нужные им экраны, комбинации элементов отображения данных и работой с математической моделью расчетов. Но об этом, мы расскажем вам позже, как данная система будет готова и оправдает свои запросы :) Следите за нашими статьями!