Kosmos Arena — разработка игры

Привет, Хабр! d9578c367bcc7737d62cee30373d13cd.pngСегодня хочу вам рассказать историю разработки мобильной (и не только) игры, а также интеграцию с популярным фреймворком cocos2d-x. Наверняка у многих из вас бывало желание написать свою, хоть и не большую, игру. Моя история начинается еще с 10–11 класса. Тогда небольшая демо-версия 2д проекта позволила мне выиграть конкурс Nokia N950 (помните такую?) и я оказался в числе 250 счастливчиков, которые получили девайс. С тех пор создание игр для меня является мечтой.Проект, о котором я хочу вам рассказать, изначально придуман и реализован совершенно другим человеком — Виталием. Чтобы была мотивация читать статью далее, показываю скрины:

image image image image ПК-версия до сих пор доступна для скачивания и вы можете его оценить.

Прошло немало времени, мы объединились с автором оригинальной игры и теперь трудимся над логическим продолжением проекта (включая версию для мобильных платформ). Как видите, проект требует достаточно большое количество эффектов и игровых компонент, поэтому первым и основным техническим требованием является использование C++ и минимального количества прослоек в выводе графики.

Я активно изучал cocos2d-x и пытался использовать его в небольших демо-проектах, на которых проверял актуальность геймплея моих идей.Это довольно хорошая и богатая функционалом библиотека. Недавно разработчики выпустили 3ю версию, полностью переписав код отрисовки. Cocos2d-x берет на себя много рутинных дел: кроссплатформенные обертки над графическими объектами, сборка под разные платформы и т.д. Отказываться от всего этого было бы глупо, но использовать этот фреймворк по назначению тяжело. Вся эта система нодов и событий (actions) удобна только в теории и для небольших примеров. В действительно же это показало себя медленным и неудобным монстром.

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

Ядром всего является интерфейс DeviceBase. Каждая платформа должна наследовать этот интерфейс и реализовывать некроссплатформенный функционал. Например — загрузка текстур.

namespace Graphics { class DeviceBase { public: // … virtual void beginScene () = 0; virtual void endScene () = 0;

virtual ShaderManager& shaders () const = 0; virtual TextureCache& textures () const = 0; virtual RenderTextureManager& renderTextures () const = 0;

virtual TextureID loadTexture (const char* fileName, /* … */) = 0; virtual float getDelta () = 0; virtual void renderBatch (Batch& batch) = 0; // … }; } Одна из самых важных функций, которую опишу позже, это renderBatch: непосредственный вывод треугольников на экран.Следующим важным объектом системы является Batch. Это то, что может выводиться на экран: спрайт, текст (шрифт), графические примитивы.У батча есть следующие характеристики:

массив точек (из которых будут формироваться треугольники, например) прикрепленный шейдер рендер текстура (номер текстуры, в которую нужно рисовать батч) wrap/filter/blending — режимы текстуры Этого уже достаточно для вывода примитивов на экран: создаем батч, который наполняет массив вершин нужными точками и передаем его в DeviceBase: renderBatch, который внутри использует прямые openGL вызовы: void AndroidDevice: renderBatch (Batch& batch) { applyTarget (batch); applyTexture (batch); applyTextureWrapping (batch); applyTextureFilter (batch); applyShader (batch); applyBlending (batch);

glVertexAttribPointer (kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, sizeof (Vertex), &batch.data ()[0].x); glVertexAttribPointer (kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, sizeof (Vertex), &batch.data ()[0].tx); glVertexAttribPointer (kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof (Vertex), &batch.data ()[0].col);

if (batch.mode () == Mode: Strip) glDrawArrays (GL_TRIANGLE_STRIP, 0, batch.data ().size ()); else glDrawArrays (GL_TRIANGLES, 0, batch.data ().size ()); } Как показывает практика, у новичков проблемы возникают именно здесь: в понятии того, как нужно связать opengl и cocos2d-x, как и где нужно использовать и применять шейдеры.Рендер текстуры Не буду описывать полный процесс создания обертки, но расскажу о проблеме, с которой мы столкнулись. Как видите выше, мы напрямую используем некоторые OpenGL-вызовы из кода. В связи с этим теряется местами связь между кокосом и OpenGL — кокос имеет обертки над некоторыми функциями, чтобы иметь возможность обрабатывать некоторые вызовы и действия. Пример тому — уход приложения в фон. После обратной активации, нужно пересоздать все текстуры и перезагрузить ресурсы (в том числе и рендер текстуры). Поэтому нам пришлось ловить сигнал ухода в фон. Для этих целей у класса AppDelegate есть два метода: applicationDidEnterBackground и applicationWillEnterForeground.Рендер цикл Следующий шаг — обойти систему нодов кокоса. Как вы знаете, у этого фреймворка нет update/render функций: все далается через ноды и события (actions). Как уже писал выше, этот подход совершенно не подходит нам. Выход такой: кокос требует создание минимум одного объекта типа cocos2d: CCScene, который есть точкой входа в логику игры. Этот объект уже имеет функцию draw, которую достаточно перегрузить. Осталось еще добавить функционал update: this→schedule (schedule_selector (SCENE_CLASS: tick)); Внутри кокоса есть специальный schedule-класс, который позволяет вызывать функции с некоторыми интервалами времени (или на каждый тик игрового цикла).Сигнатура tick метода имеет один аргумент: float delta time. В этой функции теперь можно просчитывать любую вашу игровую логику.С рисованием сложнее: нам нужно подготовить кокос к тому, что в функции draw будет происходить рисование, причем рисование вручную через вызов OpenGL-функций. void SCENE_CLASS: draw () { setShaderProgram (CCShaderCache: sharedShaderCache ()→programForKey (kCCShader_PositionTextureColorAlphaTest)); CC_NODE_DRAW_SETUP ();

// Рендер код } Применяем на рендер стандартный кокосовый шейдер (в исходниках можете посмотреть его код).CC_NODE_DRAW_SETUP — обычный макрос, внутри которого вызывается use шейдера и обновление состояния его юниформ-объектов.

Шейдеры Особое внимание хочу обратить на шейдеры, это одна из самых сложных тем для начинающих, даже если вы не начинающих в геймдеве, вам будет сложно понять что и к чему в связке с кокосом. Создание шейдера: const char* pixelFileName = »…»; CCGLProgram* program = new CCGLProgram ();

program→initWithVertexShaderFilename («vert.h», pixelFileName); program→addAttribute (kCCAttributeNamePosition, kCCVertexAttrib_Position); program→addAttribute (kCCAttributeNameColor, kCCVertexAttrib_Color); program→addAttribute (kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords); program→link (); program→updateUniforms (); Как помните, выше я писал о том, что batch объект хранит id-шейдера. Достаточно связать в какой-то ассоциативном контейнере id → CCGLProgram и передавать этот id в batch. Функция applyShader выглядит так: void AndroidDevice: applyShader (const Batch& batch) { uint shaderTag = batch.getShader ();

const auto floatUniforms = batch.floatUniforms (); const auto vec2Uniforms = batch.vec2Uniforms (); CCGLProgram* shaderHandle = …; // Получаем из контейнера по id shaderHandle→use ();

if (! vec2Uniforms.empty () || ! floatUniforms.empty ()) { for (auto it: vec2Uniforms) shaderHandle→setUniform2f (it.first, it.second);

for (const auto it: floatUniforms) shaderHandle→setUniform1f (it.first, it.second); } } Массивы юниформ это обычные std: map, которые хранят uniformName → uniformValue.[embedded content]

Kosmos Arena Достаточно тяжело в одной статье рассказать обо всем, поэтому на тему cocos2d-x писать далее не буду. В конце статьи архив на минимальный проект, который включает облегченную обертку которую мы используем. Если у кого-то будут вопросы — обращайтесь.Kosmos Arena это Sci-Fi шутер в космосе с видом сверху. ПК-версия писалась для конкурса, поэтому особого геймплея или разнообразия миссий там нет. Сейчас мы разбили всю работу на этапы и собираемся разнообразить геймплей интересными компонентами. Например, на поверхности кристаллов будут передвигаться паукоподобные роботы, которыми можно будет управлять:

8bfbd2ad7834de86752a1193286fdb1c.jpg Интерьер игры выглядит в подобном стиле:

9412e7945fe5f2b311bbf3034e01dd35.jpg Физика Как вы можете заметить, в игре много динамических объектов, которые одновременно находятся в кадре. Не смотря на это, даже android-версия стабильно держит 60 фпс. Чтобы добиться этого результата, в игре не исползьуется какой-то готовый 2d физический движок (box2d, например). Физический движок написан на основе интегрирования Верлета, что позволяет нам легко манипулировать физическими объектами: анимировать точки по времени и т.д. Виталий написал специальный редактор механизмов, где можно строить физические объекты и управлять их анимацией (автоматические переходы между разными стейтами, управление скоростью и т.д.). Выглядит это так: 49b828e21762aa151a20008b352b6bbe.jpg Если будут желающие, в следующие статье Виталий может описать физический движок и проблемы, с которыми он столкнулся.Процесс работы Мы оба имеем постоянную работу, поэтому проектом занимаемся в свободное время почти каждый день. Для синхронизации используется git-репозиторий и trello доска.Проект пишется с возможностью портирования на Win, MacOS, Android, iOS.Да, нам жутко ! не хватает художника!, который сможет рисовать в «нашем» стиле и которому мы готовы отдавать процент от продаж.Заключение Если проект/статья заинтересует достаточное количество людей, мы продолжим писать. Возможные темы следующих статей: реализация конкретных эффектов из игры, физика, оптимизации в играх.Windows prototypeAndroid APK (старое демо с частью возможностей Windows-версии)

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

Если вы обнаружите какие-то проблемы с Android-версией, пишите, пожалуйста, название вашего девайса.

© Habrahabr.ru