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

от автора

Привет, Хабр!
Сегодня хочу вам рассказать историю разработки мобильной (и не только) игры, а также интеграцию с популярным фреймворком 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.

Kosmos Arena

Достаточно тяжело в одной статье рассказать обо всем, поэтому на тему cocos2d-x писать далее не буду. В конце статьи архив на минимальный проект, который включает облегченную обертку которую мы используем. Если у кого-то будут вопросы — обращайтесь.

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

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

Физика

Как вы можете заметить, в игре много динамических объектов, которые одновременно находятся в кадре. Не смотря на это, даже android-версия стабильно держит 60 фпс. Чтобы добиться этого результата, в игре не исползьуется какой-то готовый 2d физический движок (box2d, например). Физический движок написан на основе интегрирования Верлета, что позволяет нам легко манипулировать физическими объектами: анимировать точки по времени и т.д. Виталий написал специальный редактор механизмов, где можно строить физические объекты и управлять их анимацией (автоматические переходы между разными стейтами, управление скоростью и т.д.). Выглядит это так:

Если будут желающие, в следующие статье Виталий может описать физический движок и проблемы, с которыми он столкнулся.

Процесс работы

Мы оба имеем постоянную работу, поэтому проектом занимаемся в свободное время почти каждый день. Для синхронизации используется git-репозиторий и trello доска.
Проект пишется с возможностью портирования на Win, MacOS, Android, iOS.
Да, нам жутко ! не хватает художника!, который сможет рисовать в «нашем» стиле и которому мы готовы отдавать процент от продаж.

Заключение

Если проект/статья заинтересует достаточное количество людей, мы продолжим писать. Возможные темы следующих статей: реализация конкретных эффектов из игры, физика, оптимизации в играх.

Windows prototype
Android APK (старое демо с частью возможностей Windows-версии)

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

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

Считаете ли вы проект интересным?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Никто ещё не голосовал. Воздержавшихся нет.

ссылка на оригинал статьи http://habrahabr.ru/post/222349/


Комментарии

Добавить комментарий

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