Во времена обилия и доступности качественных игровых движков вроде Unity и Unreal необходимость в разработке собственных возникает редко. Об одном из исключений хочу рассказать в этой статье. Речь пойдет об игровом движке компании Playrix, в которой я работаю почти 5 лет. Расскажу о его прошлом и настоящем, текущей функциональности, о том, к каким техническим решениям мы пришли и почему не стали использовать Unity.
В первую очередь эта статья может быть интересна молодым студиям, которые еще не решили, по какому пути идти: создавать свой движок или выбрать существующее решение. А может, статья подбросит новые идеи, если вы уже используете движок собственного производства.
Для начала давайте познакомимся. Меня зовут Виталий, я программист визуальных эффектов в Playrix. В компанию пришел почти пять лет назад, в конце 2015 года. Начинал с портирования проекта Gardenscapes на Mac-платформу, а затем перешел на проект Fishdom, где целиком погрузился в работу с графикой, анимацией и программными эффектами.
Придя в компанию, обнаружил, что на всех проектах используется единый игровой движок собственного производства. Он имеет длинную историю и как отдельный проект начал свое существование в 2009 году из общей части игры Brickshooter Egypt. На момент написания статьи движку уже 11 лет! Можно сказать, он самый старый из работающих проектов Playrix.
Итак, немного о функционале: первоначально движок поддерживал только платформу Windows, а спустя несколько лет появилась поддержка мобильных платформ. Он покрывал весь базовый набор потребностей и был хорошо заточен под наши проекты. Что мы имели тогда:
Несколько раз поднимался вопрос использования Unity, но для этого понадобилось бы перевести уже существующие проекты с C++ на Unity. Этот процесс требует много времени и ресурсов. Избежать его не представлялось возможным, так как мы часто используем наработки одних проектов в других. Разные технологии сильно затруднили бы процесс.
В этой ситуации логичным решением стало развитие собственного инструмента — Visual Scene Object (VSO). В первой версии VSO еще не было ни кодогенерации, ни разнообразия редакторов. Он был написан за два месяца в связке с игровым проектом и стал основой дальнейшего развития. Опираясь на четкое понимание потенциала и востребованности продукта, команда из 4 человек активно работала над проектом, пусть это и не всегда совпадало с основным вектором разработки движка.
Дальше разработка шла эволюционно и постепенно, с учетом интересов и пожеланий проектов. Сейчас в команде VSO больше 10 человек, а времени на выход новых версий уходит намного больше, чем раньше. Помимо основной команды, в развитии VSO участвуют программисты других проектов. Когда Fishdom начал свой переход на VSO, я быстро включился в процесс написания новых behaviour-сценариев для упрощения процесса разработки. Часть этих наработок перекочевала в общую VSO-библиотеку.
Мы используем наборы объектов, собранных в сцены. Они состоят из узлов, которые, в свою очередь, состоят из ряда сущностей:
Пример кода компоненты доступен тут.
Система, ориентируясь на класс, а также учитывая теги, генерирует код, необходимый для работы с данными (сохранение и загрузка), функционирования редакторов, поддержания клонирования и прочего функционала.
Генерацию и сериализацию редакторов используют не только для визуальных объектов, но и для любых других классов, наследуемых от Serializable. Нужно только пометить свойства тегами. Ну а если наследовать свой класс от класса Asset, получим полноценный ассет, похожий по функционалу на ScriptableObject в Unity. Такая модель библиотеки ускоряет разработку новой функциональности и делегирует часть задач профильным специалистам.
Наш генератор — это питоновский скрипт, который парсит заголовочные файлы и на их основе создает нужный код. За счет тегов настройка генерации получилась гибкой. Для некоторых из наших систем код генерируется целиком, например:
Чтобы избежать неопределенностей статуса при генерации кода, мы решили делать библиотеку полностью автоматической. Для этого выбрали CMake. Проводим генерацию при каждой компиляции. Для экономии времени сохраняем кэш с результатами парсинга, и холостой проход кодогенерации занимает всего пару секунд.
К текущей версии генерации пришли не сразу. Изначально основную логику писали на Python, использовали шаблоны по минимуму, а код содержал множество условий и проверок. В таком подходе были свои плюсы: лучшая читабельность, чем в шаблонах, возможность реализовать хитрую логику. Но при этом Python-код соседствовал с большим количество кода на С++, и такая мешанина быстро утомляла. Генераторы на Python частично решали проблему, но не целиком.
По итогу все-таки перешли на генерацию с помощью шаблонов, и Python теперь только готовит данные.
Библиотеки с кодогенерацией, как FlatBuffers и Protobuf, подразумевают рукописные структуры, а сгенерированные структуры невозможно интегрировать в пользовательский код. Результатом использования этих библиотек было бы увеличение в два раза классов исключительно для сериализации.
В этой ситуации cereal выглядела лучшим кандидатом. У этой библиотеки приятный синтаксис, понятная реализация, в ней достаточно комфортная генерация кода для сериализации. Единственный крупный минус — ее бинарный формат. Основным требованием с нашей стороны была независимость формата от «железа» (порядок байтов или разрядность не должны влиять на чтение данных). А также удобство записи бинарного формата из Python. По этим критериям cereal нам также не подходил.
Взяв за основу идею cereal, мы создали библиотеку, в которой находятся базовые архивы чтения и записи данных. Наследники от этих архивов реализуют запись в нужном формате: json, xml. Конвертация из xml в бинарный формат выполняется простым Python-скриптом. Код сериализации в дальнейшем генерируется по классам и пользуется этими архивами при записи данных.
Главная проблема этой библиотеки — ее громоздкость и медлительность. Поэтому применяем ее только для редакторов. Для игровых объектов сделали свою простенькую библиотеку.
В библиотеке RTTR требуется написание биндинга с объявлением всех методов и свойств класса. Этот биндинг генерируется из кода на Python для тех классов, которым нужна возможность редактирования. А за счет того, что RTTR поддерживает метаданные для всех сущностей, генератор создает различные настройки членам класса: специнспектор для поля, границы числовых полей, тултипы. В инспекторе эти метаданные применяются для отрисовки интерфейса редактирования.
Пример объявления класса в RTTR смотрите тут.
Метаданные, указанные в RTTR при регистрации, нужны, чтобы адаптировать для вывода редактируемые данные объекта. Мы можем создавать объекты, которые хранятся по значению или по указателю, а также имеем поддержку примитивных типов и коллекций.
В инспекторе мы заложили возможность отменить операции. Для этого используем команды, которые создаются при любом изменении данных и в которых заложен функционал возврата изначальных данных. Это дало Ctrl+Z.
Редактор сцены — это гибкая система интерфейсов, которая позволяет наполнить сцену объектами, свойствами, настроить верстку и внешний вид.
Редактор событий и квестов — предназначен для создания различных туториалов, квестов и других событий. Может использоваться практически без участия программистов.
В нашей реализации эффект — это набор нескольких систем однотипных частиц, которые изменяются во времени по одинаковым законам. У системы частиц есть как постоянные характеристики — текстура частиц, параметры эмиттера, предельное число частиц, так и переменные — положение (или скорость), размер, угол поворота, цвет. Из-за того, что некоторые параметры системы частиц при старте эффекта выбираются случайно, характеристики каждой отдельной частицы могут отличаться от характеристик другой частицы. Это создает иллюзию, что частицы живут сами по себе.
С помощью эффектов легко оживить статичную сцену, добавить красоты и расставить акценты.
Таким образом, главное приобретение — это свобода действий. Поскольку движок создавался и развивался, ориентируясь на нужды проектов, в нем не освещена функциональность, которая не используется в играх Playrix. Например, в нашем движке нет поддержки больших 3D-сцен. Мы не используем статические и динамические тени, прямое и отложенное освещение — это большой пласт работ, на который мы сейчас не готовы. Однако если будем разрабатывать игру, которая требует использования, например, ландшафта или освещения, то обязательно вернемся к Unity, Unreal Engine или другого популярного движка.
Свой движок — это гибкость и свобода, но и большие затраты на разработку и поддержку. Выбор готового движка — быстрое внедрение, возможность найти специалиста с опытом взаимодействия, но и необходимость подстраиваться и идти на компромиссы. Решение остается за вами.
В первую очередь эта статья может быть интересна молодым студиям, которые еще не решили, по какому пути идти: создавать свой движок или выбрать существующее решение. А может, статья подбросит новые идеи, если вы уже используете движок собственного производства.
Для начала давайте познакомимся. Меня зовут Виталий, я программист визуальных эффектов в Playrix. В компанию пришел почти пять лет назад, в конце 2015 года. Начинал с портирования проекта Gardenscapes на Mac-платформу, а затем перешел на проект Fishdom, где целиком погрузился в работу с графикой, анимацией и программными эффектами.
Придя в компанию, обнаружил, что на всех проектах используется единый игровой движок собственного производства. Он имеет длинную историю и как отдельный проект начал свое существование в 2009 году из общей части игры Brickshooter Egypt. На момент написания статьи движку уже 11 лет! Можно сказать, он самый старый из работающих проектов Playrix.
Итак, немного о функционале: первоначально движок поддерживал только платформу Windows, а спустя несколько лет появилась поддержка мобильных платформ. Он покрывал весь базовый набор потребностей и был хорошо заточен под наши проекты. Что мы имели тогда:
- вывод графики;
- взаимодействие с SDK;
- работа с ОС;
- работа с ресурсами;
- работа с сетью.
Несколько раз поднимался вопрос использования Unity, но для этого понадобилось бы перевести уже существующие проекты с C++ на Unity. Этот процесс требует много времени и ресурсов. Избежать его не представлялось возможным, так как мы часто используем наработки одних проектов в других. Разные технологии сильно затруднили бы процесс.
В этой ситуации логичным решением стало развитие собственного инструмента — Visual Scene Object (VSO). В первой версии VSO еще не было ни кодогенерации, ни разнообразия редакторов. Он был написан за два месяца в связке с игровым проектом и стал основой дальнейшего развития. Опираясь на четкое понимание потенциала и востребованности продукта, команда из 4 человек активно работала над проектом, пусть это и не всегда совпадало с основным вектором разработки движка.
Дальше разработка шла эволюционно и постепенно, с учетом интересов и пожеланий проектов. Сейчас в команде VSO больше 10 человек, а времени на выход новых версий уходит намного больше, чем раньше. Помимо основной команды, в развитии VSO участвуют программисты других проектов. Когда Fishdom начал свой переход на VSO, я быстро включился в процесс написания новых behaviour-сценариев для упрощения процесса разработки. Часть этих наработок перекочевала в общую VSO-библиотеку.
Что мы имеем сейчас
Мы создали свою версию компонентной системы, в которой есть набор редакторов и различных подсистем. При ее разработке отталкивались от нужд и пожеланий игровых проектов и вдохновлялись лучшими из уже существующих движков.Мы используем наборы объектов, собранных в сцены. Они состоят из узлов, которые, в свою очередь, состоят из ряда сущностей:
- Transform — трансформация узла (позиция, поворот, масштаб).
- Component — элемент отрисовки (не более одного, но может и отсутствовать). Это любой объект с возможностью отображения, например sprite или particle.
- Behaviour — элемент, описывающий поведение и любую логику, их количество не ограничено.
- Sorting — элемент управления порядком отрисовки узлов. Помимо регулирования порядка между объектами VSO, этот элемент помогает легко интегрироваться в запущенные проекты и обеспечить связь старых сущностей с новыми. С его помощью управлять порядком отображения может внешний код.
Пример кода компоненты доступен тут.
Система, ориентируясь на класс, а также учитывая теги, генерирует код, необходимый для работы с данными (сохранение и загрузка), функционирования редакторов, поддержания клонирования и прочего функционала.
Генерацию и сериализацию редакторов используют не только для визуальных объектов, но и для любых других классов, наследуемых от Serializable. Нужно только пометить свойства тегами. Ну а если наследовать свой класс от класса Asset, получим полноценный ассет, похожий по функционалу на ScriptableObject в Unity. Такая модель библиотеки ускоряет разработку новой функциональности и делегирует часть задач профильным специалистам.
Основные блоки
Кодогенерация
Так как в языке C++ нет рефлексии (reflection — получение данных о типе из кода), значительную часть кода, обеспечивающую работу системы, приходится писать руками. Но мы смогли добиться генерации большей части этого рутинного кода.Наш генератор — это питоновский скрипт, который парсит заголовочные файлы и на их основе создает нужный код. За счет тегов настройка генерации получилась гибкой. Для некоторых из наших систем код генерируется целиком, например:
- Инструмент сериализации (сохранение/загрузка с диска или по сети).
- Binding для библиотеки рефлексии (инструмент для создания редакторов).
- Код для клонирования объектов.
- Код, необходимый собственной runtime-рефлексии.
Парсинг C++
Одним из решений для разбора заголовочных файлов мог быть парсинг с Clang, но его скорости не хватало. Поэтому остановились на CppHeaderParser — это простая Python-библиотека, состоящая всего из одного файла. Она работает быстро, но имеет ряд ограничений, например, не ходит по #include и не обрабатывает макросы или символы. Поэтому мы ее серьезно доработали, среди прочего добавили нововведения из C++17, и используем до сих пор.Чтобы избежать неопределенностей статуса при генерации кода, мы решили делать библиотеку полностью автоматической. Для этого выбрали CMake. Проводим генерацию при каждой компиляции. Для экономии времени сохраняем кэш с результатами парсинга, и холостой проход кодогенерации занимает всего пару секунд.
Генератор кода
Выбор библиотеки для генерации по шаблонам оказался довольно простым. Мы остановились на Templite+. Она не большая, но обладает всем необходимым функционалом.К текущей версии генерации пришли не сразу. Изначально основную логику писали на Python, использовали шаблоны по минимуму, а код содержал множество условий и проверок. В таком подходе были свои плюсы: лучшая читабельность, чем в шаблонах, возможность реализовать хитрую логику. Но при этом Python-код соседствовал с большим количество кода на С++, и такая мешанина быстро утомляла. Генераторы на Python частично решали проблему, но не целиком.
По итогу все-таки перешли на генерацию с помощью шаблонов, и Python теперь только готовит данные.
Сериализация
Мы рассматривали целый ряд библиотек для сериализации, среди которых были FlatBuffers, cereal, Protobuf и другие.Библиотеки с кодогенерацией, как FlatBuffers и Protobuf, подразумевают рукописные структуры, а сгенерированные структуры невозможно интегрировать в пользовательский код. Результатом использования этих библиотек было бы увеличение в два раза классов исключительно для сериализации.
В этой ситуации cereal выглядела лучшим кандидатом. У этой библиотеки приятный синтаксис, понятная реализация, в ней достаточно комфортная генерация кода для сериализации. Единственный крупный минус — ее бинарный формат. Основным требованием с нашей стороны была независимость формата от «железа» (порядок байтов или разрядность не должны влиять на чтение данных). А также удобство записи бинарного формата из Python. По этим критериям cereal нам также не подходил.
Взяв за основу идею cereal, мы создали библиотеку, в которой находятся базовые архивы чтения и записи данных. Наследники от этих архивов реализуют запись в нужном формате: json, xml. Конвертация из xml в бинарный формат выполняется простым Python-скриптом. Код сериализации в дальнейшем генерируется по классам и пользуется этими архивами при записи данных.
Редакторы
Для написания окон редакторов мы выбрали библиотеку ImGui. На ней созданы окно содержимого сцены, инспектор ассетов, редакторы анимаций и эффектов и прочее. Большая часть кода для редакторов пишется вручную, но для работы со свойствами классов, их просмотра или редактирования используется библиотека RTTR, а также биндинг, сгенерированный под нее, и обобщенный код инспекторов.Библиотека рефлексии — RTTR
Для организации рефлексии в С++ мы выбрали библиотеку RTTR. Она имеет простой API, и ей не нужно вмешиваться в классы. RTTR поддерживает различные обертки над типами (например, умные указатели) и коллекции, а также позволяет регистрировать свои собственные обертки и обладает необходимой функциональностью.Главная проблема этой библиотеки — ее громоздкость и медлительность. Поэтому применяем ее только для редакторов. Для игровых объектов сделали свою простенькую библиотеку.
В библиотеке RTTR требуется написание биндинга с объявлением всех методов и свойств класса. Этот биндинг генерируется из кода на Python для тех классов, которым нужна возможность редактирования. А за счет того, что RTTR поддерживает метаданные для всех сущностей, генератор создает различные настройки членам класса: специнспектор для поля, границы числовых полей, тултипы. В инспекторе эти метаданные применяются для отрисовки интерфейса редактирования.
Пример объявления класса в RTTR смотрите тут.
Инспектор
Обычно код внутри редактора не взаимодействует напрямую с RTTR. Для этого используют рукописные классы-прослойки, которые умеют рисовать ImGui-инспектор для объекта.Метаданные, указанные в RTTR при регистрации, нужны, чтобы адаптировать для вывода редактируемые данные объекта. Мы можем создавать объекты, которые хранятся по значению или по указателю, а также имеем поддержку примитивных типов и коллекций.
В инспекторе мы заложили возможность отменить операции. Для этого используем команды, которые создаются при любом изменении данных и в которых заложен функционал возврата изначальных данных. Это дало Ctrl+Z.
Окна и редакторы
На основе кодогенерации, функционала редакторов и систем создания ассетов было создано большое количество подсистем и полезных игровых редакторов:Редактор сцены — это гибкая система интерфейсов, которая позволяет наполнить сцену объектами, свойствами, настроить верстку и внешний вид.
Редактор событий и квестов — предназначен для создания различных туториалов, квестов и других событий. Может использоваться практически без участия программистов.
Редактор состояний
Редактор анимационных состояний — это набор состояний объекта, управляемых посредством анимации, и переходов между ними. Редактор позволяет также настраивать связи между этими состояниями, их длительность и тонкости перехода. Получив команду на смену состояния, контроллер автоматически найдет к нему путь и проиграет все анимации в промежуточных блоках.Редактор анимаций
Редактор анимаций — основной инструмент для создания анимаций в VSO. Помимо управления трансформацией и цветом объектов, благодаря редактору можно привязать анимацию практически к любому из свойств или управлять их поведением. Например, регулировать скорость проигрывания flash-клипа или запустить эффект в нужный момент.Редактор эффектов
Редактор эффектов позволяет создавать и редактировать эффекты частиц и является основным инструментом работы VFX-художников в компании.В нашей реализации эффект — это набор нескольких систем однотипных частиц, которые изменяются во времени по одинаковым законам. У системы частиц есть как постоянные характеристики — текстура частиц, параметры эмиттера, предельное число частиц, так и переменные — положение (или скорость), размер, угол поворота, цвет. Из-за того, что некоторые параметры системы частиц при старте эффекта выбираются случайно, характеристики каждой отдельной частицы могут отличаться от характеристик другой частицы. Это создает иллюзию, что частицы живут сами по себе.
С помощью эффектов легко оживить статичную сцену, добавить красоты и расставить акценты.
Редактор ICS
Interactive Cutscenes (ICS) предназначен для создания сценариев, по которым на сцене будут происходить различные события в зависимости от заданных условий. Основная идея заключается не в том, чтобы перейти из одного состояния в другое, как это происходит в редакторе состояний, а чтобы проиграть сцену (набор действий). ICS позволяет вручную настроить маршрут, добавить развилки с зависимостью от условий, ожидать обработки кликов и многое другое. Программисты внутри проектов могут создавать особые блоки действий, расширяя функционал редактора.Итоги
Мы понимаем, что Unity — это отличный движок, но для наших текущих разработок использовать его нецелесообразно из-за требуемых для перехода и поддержания усилий и средств. Использование чужого движка лишило бы возможности тонко настраивать инструменты под себя, создавать и развивать только те подсистемы, которые действительно нужны. Кроме этого, интеграция и распространение решений, созданных на проектах, была бы трудно реализуемой.Таким образом, главное приобретение — это свобода действий. Поскольку движок создавался и развивался, ориентируясь на нужды проектов, в нем не освещена функциональность, которая не используется в играх Playrix. Например, в нашем движке нет поддержки больших 3D-сцен. Мы не используем статические и динамические тени, прямое и отложенное освещение — это большой пласт работ, на который мы сейчас не готовы. Однако если будем разрабатывать игру, которая требует использования, например, ландшафта или освещения, то обязательно вернемся к Unity, Unreal Engine или другого популярного движка.
Свой движок — это гибкость и свобода, но и большие затраты на разработку и поддержку. Выбор готового движка — быстрое внедрение, возможность найти специалиста с опытом взаимодействия, но и необходимость подстраиваться и идти на компромиссы. Решение остается за вами.
DOU
DOU – Найбільша спільнота розробників України. Все про IT: цікаві статті, інтервʼю, розслідування, дослідження ринку, свіжі новини та події. Спілкування на форумі з айтівцями на найгарячіші теми та технічні матеріали від експертів. Вакансії, рейтинг IT-компаній, відгуки співробітників, аналітика...
dou.ua