Главная » Хабрахабр » [Из песочницы] Создание приложения-раскраски на Unity3D

[Из песочницы] Создание приложения-раскраски на Unity3D

image

Да так, чтобы это не просто не тормозило, а летало на 60 fps на самых слабых мобильных девайсах. Эта история началась одним морозным весенним вечером, когда в голову пришел вопрос: а есть ли способ определять степень заливки произвольной геометрической фигуры краской (то есть, на сколько процентов она в данный момент закрашена)?

Есть массив пикселей, подлежащих заливке, есть их границы. Для тех, кто не сразу понял, о чем речь, поясню: к проблеме возможен как растровый подход, так и… не растровый.
В первом случае все просто, тема flood fill и сопутствующих алгоритмов успешно изучена и реализована на ЯП на любой вкус. Но — при большом количестве пикселей (а ppi на современных устройствах сами знаете какое), плюс — если таких фигур много, мы упираемся в кучу вычислений в каждом кадре, которые приятно греют девайс, но не душу. Считаем количество залитых точек, дальше пропорция к общему количеству, и вуаля — имеем заветный процент на выходе.

Взор был обращён в сторону всемогущих полигонов. Да и вообще, работать с растром представлялось занятием неспортивным. Несколько волнующих часов расслабленно-упорного кодинга доказали гипотезу: можно воспользоваться такой штукой, как «вершинный цвет» — vertex color.

Немного про vertex color

Теоретически, его можно использовать для любых целей, смотря что прописать в шейдере, но в данном случае заветный байт будет хранить именно текущее значение заполненности цветом для каждой вершины. Нативно дополнительный канал информации, доступный в структуре данных треугольника — тот самый mesh.colors. Самое интересное — значения vertex color аппаратно интерполируются между собой, что позволяет делать лёгкие градиенты.
Его же шейдер использует при отрисовке, и тогда с одним материалом Unity можно создавать неограниченное количество разноцветных мешей с одним материалом на всех.

Думаю, стоит упомянуть, для чего мне понадобился пресловутый процент закраски, с которого началась статья. Основной идеей приложения-раскраски было следующее: конечная картинка состоит из набора многоугольников. Приложение будет последовательно и автоматически подсовывать пользователю элемент за элементом. Соответственно, пока не раскрасишь до конца один кусок, к следующему не перейдёшь. Подобное решение мне казалось весьма элегантным, прельстивым и в свете глобального засилья «пиксельных» раскрасок в сторах — ещё и свежим.
Само собой, для того, чтобы сделать полноценную раскраску, необходимо было наколхозить накреативить еще много интригующих решений. Во-первых, я хотел, чтобы при всей полигональной природе приложения, раскрашивание воспринималось как самое что ни на есть растровое, то есть краска должна была растекаться под пальцем, и иметь более-менее реалистичный вид. Изначальное требование по максимальной производительности при этом никуда не исчезало и продолжало висеть грозным кучевым облаком над всем техпроцессом.

Ведь если мы заведем массив вершин, и будем туда записывать vertex color по мере закрашивания, то сможем обычным проходом по массиву определять, закрашена ли фигура полностью, и какие ещё куски остались незакрашенными – аналогично пиксельному алгоритму, но с куда большей свободой. Первым делом предстояло сделать человеческую тесселяцию (разбиение большого многоугольника, состоящего из набора треугольников, на стохастическую кучу маленьких треугольников).

image

Как вы понимаете, целиком все находки и секреты я открыть не могу, но скажу, что путем взаимодействия с картой шума и олдскульного испускания лучей Unity из пальцев, эффект кисти был достигнут, и даже с некоторым растеканием краски по близлежащим от пальца треугольникам. Дальше началось увлекательное путешествие в мир шейдеров. Использование vertex color предоставило возможность обойтись одним материалом Unity на абсолютно все составные части фигуры и поэтому draw calls в готовой программе не превышает 5-7 (в зависимости от наличия меню и частиц).

Победить это не удалось, поэтому приоритетная задача — переписать компонент с нуля. Обводка сделана обычным Unity Line Renderer, который предательски глючит на некоторых фигурах, съезжая и демонстрируя изъяны на стыках. «Шахматная» текстура фона помогает, в том числе, оценить размер закрашиваемого элемента: чем он больше, тем меньше будет размер клеточек. След за пальцем — это также стандартный Trail Renderer, но в его шейдере используется z-проверка, чтобы элементы следа не накладывались друг на друга, создавая некрасивые артефакты.

image

В ходе тестирования выяснилось, что часто где-то в углах фигуры оставались незаполненные вершины, что было трудно определить визуально. Несмотря на то, что триггер переключения на следующий элемент срабатывал при степени заливки в 97%, ситуации «а что делать дальше?» – при степени заполненности от 90% до 97% — возникали достаточно часто и смущали пользователей (которым в основном было не более 12 лет). Ставить триггер менее 97% не хотелось, потому что тогда возникал эффект «я еще не докрасил, а оно уже перескочило».

Представьте: многоугольник, куча точек внутри, есть какие-то «особенные», иногда отдельно, иногда – группами. Так я неохотно познакомился с мадам Кластеризацией. Обычная такая математическая задача. Нужно найти и обозначить самую большую «группу». Хак на хаке, но заработало – и недокрашенные области стали выделяться красивым динамическим кругом. Ни один из найденных мной традиционных алгоритмов не подошел по разными причинам, пришлось делать свой. Выглядит вполне органично. В целях оптимизации, этот алгоритм срабатывает раз в 3 секунды, и только после того, как пользователь озадаченно оторвет палец от экрана в стиле «а что делать дальше».

image

Всего-то нужно определить геометрические центры каждого меша и выстроить их, как нам надо: слева направо, сверху вниз и т. После такого мозгового штурма, сделать по требованиям тестеров вариативную «очередь раскраски» — а именно, дать пользователю возможность выбрать, в какой последовательности он хочет раскрашивать элементы – было делом одного вечера. Для большей наглядности, были реализованы частицы на фоне, которые показывают направление очереди. д.

Иллюстрация очереди

image

Если включить режим "очередь по направлению" нажатием на одну из кнопок внизу, очередь раскрашивания изменится, и частицы поедут в указанную сторону.
Здесь показана дефолтная очередь (так, как задумал художник).

Мне вообще импонирует идея контролируемого автоматизма в приложениях, и поэтому каждый элемент центруется и масштабируется так, чтобы его можно было закрасить пальцем без необходимости в прокрутке экрана. Минусом такого подхода стало то, что не всегда понятно, что за часть фигуры сейчас на экране. Как выяснилось, пользователям даже нравится такой небольшой челлендж, так как он тренирует краткосрочную память и соотнесение информации – нужно держать в голове общую картину. Ну а выйти на «обзор фигуры с птичьего полета» можно двумя способами – жестом-щипком (pinch) или нажатием на кнопку zoom.

image

Помимо кнопки zoom in/out и очевидной кнопки выхода в меню, еще есть вызов палитры – красить можно как цветом «по умолчанию», установленным художником, так и по собственному выбору. Следуя заветам Apple Interface Guidelines, было принято решение сократить количество кнопок на экране до минимума.

Да, пришлось запрятать этот функционал, но это вполне оправданно — за все тестирование никто ни разу не спросил, как это сделать… Кроме того, в режиме «из птичьих глаз» можно поменять градиент фона (рандомно генерируется каждое нажатие) или войти в режим «перекрашивания», который позволяет исправить уже закрашенный элемент.

Про палитру

Сама по себе палитра переделывалась два раза. Сначал я просто располагал на экране какое-то количество квадратов с цветами, но пользователи просили больше цветов. Прокрутку в интерфейсе я делать не хотел, и так появилась схема «цвет-оттенок», то есть сначала юзер выбирает нажатием базовый цвет, а затем – один из его оттенков. Палитра убирается кнопкой или вальяжным свайпом вниз.

image

Ключевым нехватающим звеном всей картины был reward – некая визуально-психологическая награда, которую получает пользователь по завершению процесса раскраски. Идея была подсмотрена лежала на поверхности: фигура раскрашивалась автоматически и заново, в ускоренном режиме, причём именно так, как это делал пользователь – проще говоря, timelapse на 15-20 секунд. Записать последовательность, в которой юзер касался вершин фигур, и затем ее проиграть, оказалось делом нехитрым. Намного сложнее было настроить всё так, чтобы это выглядело выигрышно с визуальной точки зрения.

image

Разумеется, timelapse при проигрывании записывается в видеофайл, и после визуальной феерии пользователю предлагается сохранить/поделиться свежесозданным шедевром.

Вместо заключения

В следующих частях планируется поведать про героические сражения с Unity UI при разработке второй части приложения — меню выбора картинок, а также посчитать набитые шишки в нелегком деле ASO.
На этом тысяча слов, отведенных мной на первый опус, заканчивается.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Google оштрафован на рекордные $5 млрд за нарушение антимонопольного законодательства в Европе

Это самый большой штраф в истории компании. Еврокомиссия закончила многолетнее расследование против Google и потребовала рекордный штраф в 5 миллиардов долларов (4,3 млрд евро). Товары из каталога Google Shop, согласно обвинению, намеренно ставились выше, чем остальные. Прошлый рекордный штраф, кстати, ...

«Яндекс» начал работу над созданием собственной системы «умный дом»

Сегодня стало известно о том, что компания «Яндекс» осенью может представить собственную систему «умный дом». Во всяком случае, она зарегистрировала в странах Евразийского экономического союза устройство «Яндекс.Модуль», о чем сообщает «Коммерсант». Этот девайс был создан американской компанией NotAnotherOne, у истоков ...