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

image

Об игре


Основной особенностью Tap Tap Builder является симбиоз с кликером, а сей жанр крепко обосновался среди широкой публики. Итак, для возведения зданий от игрока потребуется, помимо некоторого количества ресурсов, изрядно понабивать пальцы, жмакая по экрану. Правда, можно построить строительный кран, который будет строить за вас, но весьма медленно. Большинство зданий можно улучшать, при этом через каждые 5 уровней изменяется их модель. Каждое здание в игре играет свою определенную роль.

image

В жилых домах живут люди. Без них население города расти не будет. Когда горожане обзаводятся жильем, они начинают искать работу. Например, могу пойти работать в офис. Офисы — основной источник поступления кредитов в городской бюджет в виде налогов. Кто-то может пойти работать за завод. Заводы производят ресурсы — металл и бетон. Для работы всех зданий нужна электроэнергия. Для этого придется построить электростанцию. Кроме того, в некоторых зданиях можно работать самому, при этом будет расходоваться энергия игрока, которая восстанавливается со временем. К примеру, игроку не хватает металла. Вместо того, чтобы ждать пару часов, пока он будет производиться на заводе, игрок может перейти в здание завода и поработать там сам. И конечно же, суть работы заключается в том, чтобы 100 раз кликнуть по кнопке с надписью «Работать»)

Если жители не смогут найти работу, придется платить им пособие по безработице. Однако, если же работников будет не хватать, то здания будут работать неэффективно. Поэтому важно постоянно следить за балансом рабочих мест и количеством жителей.

Зданий в игре довольно много. Есть отели и рестораны, полицейские участки и пожарные станции, банки, фитнес центры, биржы и т.д. За строительство и улучшение зданий игрок получает опыт и может повысить уровень города. При этом он получает 1 золотой ключ, который понадобится при строительстве специальных построек. Что-то вроде очков умения в RPG. Например, можно построить банк, тогда при выходе из игры все накопленные средства будут перечисляться на депозит, а игрок будет получать проценты за все время отсутствия в игре. Или можно построить налоговую инспекцию, которая увеличивает поступление налогов в городской бюджет на 10%. Кроме основных и специальных построек, есть еще служебные, уникальные и декоративные.

Транспорт


Еще в городе есть транспортные средства. Сделаны они только для красоты. Если построить городскую администрацию, по городу начнут ездить лимузины. Можно также построить шоу-рум, тогда по городу начнут гонять спорткары.

image

Взаимодействие игроков


Среди служебных построек есть пивной паб и почта. Построив их, игроки смогут взаимодействовать друг с другом, например, обмениваться инвайтами и ресурсами. При этом не нужен доступ в сеть. Игроки будут обмениваться бит-кодами, это что-то вроде промо-кодов или купонов. Каждый город имеет уникальный индекс, который отображается в здании почты. Его нужно указывать в качестве получателя при отправке посылки. Кроме того, конечно же, нужно будет выбрать, что отправить. Например, можно отправить 1000 единиц металла, а полученный код надо будет сообщить другу. При этом этот код не получится отправить нескольким игрокам, поскольку в нем зашифрован индекс получателя.

image

Похожим образом генерируются промо-коды, которые вставляются в сообщения на стену в социальных сетях.

image

Насколько удачно это решение, покажет время. Буду надеяться, что игроки оценят оригинальную идею.

Участие в Games Jam


Проект успел принять участие в Games Jam Kanobu 2016 и оказаться в подборке интересных проектов. Увы, до финала не дошел. Зато удалось пообщаться с другими разработчиками и получить полезные отзывы. К сожалению, реальных игроков на таких мероприятиях практически не бывает. Но самое главное, игра наша издателя. Им стала российская компания Herocraft. И это круто! Кроме того, было еще 2 предложения, обсуждение которых закончилось без результата.
image
Итак, вкратце поделюсь поделюсь своими советами по поводу хакатонов (hackathon) и джемов (jam):
— ищите команду. Вероятность найти команду на хакатонах невелика. Обычно участвуют уже сформированные команды, а свободные художники на хакатоны заглядывают крайне редко. Пробуйте, но не рассчитывайте;
— смотрите на другие проекты и их результаты. Определяйте для себя наиболее популярные направления в разработке и «заимствуйте» хорошие идеи);
— ищите отзывы об игре;
— ищите издателя;

Опыт работы с издателем


Первым делом составляется договор, в котором оговариваются платформы для выпуска игры, юридические и финансовые вопросы. До подписания договора стоит подготовить список вопросов и получить на них ответы. Например:
— какие доработки потребуются?
— на сколько может растянуться процесс доработок?
— какие условия расторжения договора?
После этого начинается процесс доработки игры. Издатель составляет список задач, которые нужно выполнить. Советую сразу определиться с удобной системой управления проектом. В моем случае это был GitHub. Там можно хранить исходники игры, билды и прочие файлы, а также заводить задачи и баги.
image

Касательно Tap Tap Builder основными задачами оказались:
— доработка системы обучения
— улучшение интерфейса
— интеграция инструментов аналитики
— увеличение производительности игры
— создание контента для игровых магазинов (иконка, баннер, описание, скриншоты, видео)
— разработка тестов

Замечу, что многие подзадачи пришлось отложить до релиза ввиду трудоемкости их выполнения. На этапе разработки тестов проходит итерация тестирования на стороне разработчика, ведь все переданные тесты должны быть как минимум корректными.
После доработки игры начинается итерация тестирования на стороне издателя и исправление обнаруженных багов.
И вот доведенный до кондиции и отлежавшийся билд отправляется на софтланч (soft launch). Софтланч это ограниченная публикация игры, например в какой-то одной стране и на одной платформе. Задача софтланча — получить отзывы реальных игроков и определить основные метрики игры (доход, удержание и прочие). На основе этих данных принимается решение о целесообразности мирового релиза. В случае провала софтланча, контракт расторгается, игру возвращают разработчику, и он может самостоятельно опубликовать ее.

Оптимизация игры


Первые попытки запустить игру на мобильных устройствах выявили существенные проблемы с производительностью. Игра показывала 10-15 FPS на пустом острове, при этом устройства были далеко не самые слабые (Sony Z1 и Asus Transformer). Последующая терапия позволила увеличить скорость игры в несколько раз. Итак, по порядку.

Оптимизация скриптов


Не стоит лихорадочно начинать искать проблемы в игре и оптимизировать наиболее вероятные узкие места. Первым делом надо запустить Profiler (доступен в бесплатной версии Unity 5).
У меня сразу же обнаружились проблемы в скриптах. Исправление не заняло много времени, вся оптимизация свелась к кешированию информации и сокращению количества вызовов. Кстати, рекомендую шаблон проектирования Memoizer. Суть его состоит в запоминании результата работы функции для последующих вызовов с идентичными параметрами. Параметрами могут быть не только простые типы, но и структуры и классы. Ниже приведу пример использования этого шаблона.
public static readonly Func<int, int, int> SumMemoized = Memoizer.Memoize<int, int, int>(Sum);

private static int Sum(int a, int b)
{
    return a + b;
}

Имплементация шаблона Memoizer
public class Memoizer
{
    public static Func<TReturn> Memoize<TReturn>(Func<TReturn> func)
    {
        object cache = null;

        return () =>
        {
            if (cache == null) cache = func();

            return (TReturn) cache;
        };
    }

    public static Func<TSource, TReturn> Memoize<TSource, TReturn>(Func<TSource, TReturn> func)
    {
        var cache = new Dictionary<TSource, TReturn>();

        return s =>
        {
            if (!cache.ContainsKey(s))
            {
                cache[s] = func(s);
            }

            return cache[s];
        };
    }

    public static Func<TSource1, TSource2, TReturn> Memoize<TSource1, TSource2, TReturn>(Func<TSource1, TSource2, TReturn> func)
    {
        var cache = new Dictionary<string, TReturn>();

        return (s1, s2) =>
        {
            var key = s1.GetHashCode() + "." + s2.GetHashCode();

            if (!cache.ContainsKey(key))
            {
                cache[key] = func(s1, s2);
            }

            return cache[key];
        };
    }
}

Оптимизация трехмерных моделей


После того, как проблема со скриптами была решена, пришло время заняться рендерингом (rendering). Значительное влияние на производительность оказывал тот факт, что модели зданий были сделаны из примитивов, которые имели разные материалы (и цвета). Статистика показывала около 600 draw call (после работы автобатчинга) и 100.000 треугольников. Для сравнения — на текущий момент для сферической игры в вакууме рекомендуется иметь до 100 draw call и до 30.000 треугольников.

image

Первое решение — уменьшение количества поверхностей. Пришлось импортировать ассет для редактирования мешей. Многие здания содержали цилиндры, а стандартный цилиндр имеет 20 граней и 80 треугольников. Упрощение геометрии цилиндров до 12 граней дало ощутимый эффект в ущерб небольшой угловатости зданий.
image
Следующий шаг — удаление невидимых граней из примитивов. В результате существенное сокращение общего количества треугольников (около 20%) никакого прироста FPS не принесло. Вероятно, движок и так не тратит ресурсы на отрисовку невидимой геометрии, даже если она находится в пределах видимости камеры. Итог — бесполезный шаг.

Батчинг (batching)


Следующий шаг — попытка использования статического батчинга (batching). Вкратце расскажу про батчинг, для тех, кто не в курсе. Батчинг — это группировка мешей (mesh) в 1 общий меш перед вызовом перерисовки (draw call). Просто так получается, что видеокарте проще нарисовать 1 объект сразу, чем по частям за несколько вызовов. При этом есть несколько нюансов. Объединение возможно только для мешей, которые использую один материал. Во вторых, суммарное количество вершин в общем не должно превышать 900. И в третьих, изменение transform (позиция, поворот и масштаб) дочерних объектов невозможно. К примеру, у «сбатченой» модели автомобиля не будут крутиться колеса. Батчинг бывает статический и динамический. Динамический батчинг работает во время выполнения и не требует никаких действий со стороны разработчика. Результат работы динамического батчинга можно увидеть в окне Statistics.

image

Статический батчинг можно реализовать двумя способами — поставить галочку Static для объекта на сцене или префаба (prefab). Это могут быть статические игровые объекты, например элементы ландшафта, растительность и здания. Второй способ — использование StaticBatchingUtility во время выполнения.

Вполне логично было выполнить статический батчинг для зданий, что я и сделал. В результате игра стала работать еще медленнее! Как оказалось, принудительный статический батчинг нарушает работу динамического батчинга, который работает эффективнее и группирует мешы из разных логических объектов, например, принадлежащие разным зданиям. Кроме того, автоматически сгруппированные объекты могут двигаться относительно друг друга (при этом группировка отменяется). Итог — применение статического батчинга может быть полезно только при глубоком понимании логики его работы.

Оптимизация теней


Следующее наблюдение — отключение динамических теней сокращает количество треугольников ровно в 2 раза, что давало прирост FPS около 20%. Изменение качества теней видимого эффекта не дало. В данной ситуации есть простое стандартное решение — замена теней спрайтами. Такие тени часто называют статическими. Сразу же возникает вопрос — как получить тени объектов, ведь не рисовать же их вручную?! Самое простое решение — использовать размытые окружности. Это подошло бы для теней персонажей в простом раннере или стрелялке. Мне такое решение не подошло — тени должны повторять геометрию зданий. Что же, пришлось написать скрипт для дампа (dump) теней. Алгоритм работы простой — скрипт по очереди создает здания (из prefab) и делает скриншот здания с тенью, сохраняя его на диск. Затем второй скрипт производит элементарную обработку полученных изображений, выполняя заливку тени черным цветом и удаляя фон (по цвету). Увы, готовых решений я не нашел, поделитесь своим опытом в комментариях.

image

Итог — увеличение скорости на 20% при появлении незначительных артефактов (при наложении и наслоении теней). Кстати, спрайтам с тенями нужно установить Packing Tag, например, Shadow. Тогда Unity создаст для них атлас (atlas), и отрисовка ВСЕХ теней будет выполняться за 1 draw call. И еще одно замечание — на некоторых устройствах возможны проблемы с отображение динамических теней, так что замена их на статические тени позволит избежать проблем.

Дальнейшая оптимизация трехмерных моделей


К сожалению, результат проведенной оптимизации оказался неудовлетворительным — игра показывала 20-25 FPS на мощном устройстве. Я решился на написание еще одного скрипта для запекания (bake) моделей. Суть в том, чтобы сгенерировать новый mesh и наложить на него текстуру для раскраски зданий. Текстура представляет собой простую палитру цветов размером 8х8, которая умеет автоматически заполняться при появлении новых материалов (цветов). При этом необходимо сформировать корректную UV-развертку (UV-mapping). На Asset Store есть плагины для запекания объектов, но они не умели генерировать текстуру по цветам материалов. Не буду подробно останавливаться на деталях реализации, т.к. понимаю, что моя ситуация довольно специфичная. Хотя с другой стороны, этот метод может найти применение в популярной ныне воксельной (voxel) графике. Ведь делать прототипы моделей в Unity гораздо удобнее, чем изучать моделирование. Если конечно, у вас в команде, нет моделлера, как у меня. У меня вообще никого не было).

image

Конечный результат превзошел все ожидания — скорость игры увеличилась в разы. Игра показывает 50-60 FPS на огромном городе. Дальнейшая оптимизация не требуется.

Заключение


Прошу прощения за то, что статья оказалась весьма большой. Ведь хотелось и про игру рассказать, и опытом поделиться. И напоследок мой совет — делайте классные прототипы и не тратьте силы на оптимизацию игры до появления рабочей версии.
Буду рад услышать ваши советы по оптимизации в комментариях, а также готов ответить на вопросы!

Ссылка на игру в Google Play
Поделиться с друзьями
-->

Комментарии (22)


  1. PatientZero
    18.05.2016 10:00
    +1

    Видел репост вашей новости вчера в ВК-группе Unity. Красивая игра получилась. Долго ли пришлось всё делать в одиночку?


    1. natexriver
      18.05.2016 10:12

      Около года в вялом режиме. Есть даже график активности на GitHub)
      image


  1. Forced2bebad
    18.05.2016 10:13

    Спасибо за статью. Очень интересно и полезно (по крайней мере, для меня).
    По поводу оптимизации согласен с автором: оптимизацию производительности действительно лучше выполнять на последнем прототипе. Просто следующий прототип может сильно отличатся от предыдущего и проводить оптимизацию производительности каждый раз – большая потеря времени. Но что если определённая оптимизация позволит ускорить процесс создания самого прототипа?
    А в целом, автор молодец! У меня вот не получается работа с Unity. Видимо, без команды энтузиастов не обойтись.


    1. natexriver
      18.05.2016 10:13

      Спасибо! Конечно, разумная и целесообразная оптимизация всегда будет полезной.


  1. KumoKairo
    18.05.2016 11:38

    Первым делом надо запустить Profiler. К сожалению, данная функция есть только в pro-версии юнити.
    А в какой версии Unity вы делаете / делали проект?


    1. natexriver
      18.05.2016 13:12

      Начинал в 4.6, затем был довольно простой перенос в 5-ку. Сейчас последняя версия 5.3, это обоснованное требование издателя.


      1. KumoKairo
        18.05.2016 14:24

        Начиная с 5й версии Unity для использования профайлера не требуется pro версия.
        Мне ещё интересно — вы подписку оформляете или one-time покупку лицензии? Какой вариант по вашему мнению наиболее удобный в плане затрат / других параметров?


        1. natexriver
          18.05.2016 14:39
          +1

          Да, я про это забыл. Обновлю текст, спасибо! Оптимизацией занимался на 4-й версии, была лицензия с работы. Тип зависит от проекта. По моему мнению для большинства инди-проектов вполне хватает бесплатной лицензии. Кроме того, часто финальной сборкой занимается издатель.


  1. Neuyazvimy1
    18.05.2016 12:05

    Насчет оптимизации теней можно также запекать тени.
    docs.unity3d.com/410/Documentation/Manual/Lightmapping.html


    1. natexriver
      18.05.2016 12:06

      Можно, данный метод хорош для статического окружения. Но в данной ситуации это оказалось неприменимым, я не нашел способ запечь тень от одного объекта в префабе.


      1. PatientZero
        18.05.2016 20:53

        А в чём вы делали 3D-модели? Блендер, да и 3D Max, я думаю, могут рендерить объект с тенью, не рендеря при этом фон.


        1. natexriver
          18.05.2016 23:15

          Все модели делал в Unity из примитивов и использовал материалы для раскраски. Увы, редакторы пока не освоил.


          1. PatientZero
            18.05.2016 23:52

            Все модели делал в Unity из примитивов и использовал материалы для раскраски.

            Забавно. А как же тогда получались более сложные формы — корона, звёзды, покерная фишка? Неужели в Юнити есть редактор?
            А 3D-программу какую-нибудь советую освоить, это довольно просто, а возможности открываются гораздо шире.


            1. natexriver
              19.05.2016 09:10

              Это пара импортированных ассетов с более сложными примитивами. +есть ассет для редактирования мешей.


  1. inborn_killer
    18.05.2016 15:30

    Итог — увеличение скорости на 20% при появлении незначительных артефактов (при наложении и наслоении теней)

    А что за артефакты появились? Вы говорите про то, что участки, где одна тень наложилась на другую, становятся ещё темнее?


    1. natexriver
      18.05.2016 23:16

      Да, наложение теней друг на друга. И еще наложение тени-картинки на объемный объект (в моем случае на автомобили и другие здания).


  1. elmm
    18.05.2016 15:52

    а какая формула смешивания используется при наложении теней?


    1. natexriver
      18.05.2016 23:17

      Никакой, каждая тень дампилась в PNG с полупрозрачностью.


  1. afrokick
    18.05.2016 19:40

    Давно хотел сделать ситибилдер+кликер. Но идея ничего не стоит как говорится) Удачи с проектом!

    Интересно было бы глянуть код кодогенератора!

    ЗЫ В закладках нашел еще одну статью(eng) про оптимизацию для мобилок.


    1. natexriver
      18.05.2016 23:26

      Спасибо! Код низкоуровневый и запутанный, проще алгоритм рассказать. По сути передается лишь одно число — код команды. Это 5 бит. Остальные биты — рандомный payload + хэш этого payload и команды. Таким образом сделана защита от модификации кода и повторной генерации одинаковых кодов. Длина кода 30 бит, что проецируется на пятизначный код с усеченным алфавитом (цифры и заглавные буквы без O и I).
      Далее последовательность симметрично шифруется индексом получателя, чтобы только он мог дешифровать код. Дополнительно используется текущая дата как ключ, чтобы у кодов был срок действия. С этим все немного сложнее, но суть в том, чтобы вчерашний код нельзя было расшифровать завтра.
      При всем этом механизм нельзя назвать безопасным, хотя безопасность может быть выше с увеличением длины кода. В игре есть таймауты и лимиты на случай перебора. Кроме того, на механизм завязаны лишь незначительные транзакции (можно отправлять мало ресурсов), которые бессмысленны при большом прогрессе в игре.
      За статью благодарствую!


  1. MorfeusKiev
    24.05.2016 15:12

    Во вторых, суммарное количество вершин в общем не должно превышать 900


    А вам нужно все таки лучше изучить как работает батчинг и какие у него ограничения. А так же в чем разница между Dynamic Batching и Static batching.

    Указанные вами данные относятся к Vertex Data и относятся к Dynamic batchin (которые включают в себя не только Mesh Vertex, а и UV Vertex(а также UV2, UV3, UV4 и сколько их есть), Vertex Color Data, Skin и прочее что присутствует в модели). В реале же стоит рассчитывать на не более 300 вершин меша.

    Статический же батчинг ограничен 2 в 16 степени (65536). На сколько мне известно это хардварное ограничение.

    К тому же за счет того что статически батчинг ломает VBO — все комбайны становятся уникальными — то это раздувает размер билда до невообразимых высот. Используйте Static batching utility (описание найдете в script reference).


    1. MorfeusKiev
      24.05.2016 15:22

      Хотя я с мобильными платформами не работал. Могу и ошибаться по поводу ограничений батчинга.