Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d-x

в 7:58, , рубрики: cocos2d-x, game development, метки:

Хочу поделиться опытом разработки мультиплатформенной сетевой игры Run In Crowd.
Сразу отмечу, что статья носит исключительно обзорный характер.

Выбор «движка» для игры

С самой первой игры, разработанной нами, мы решили, что хотим делать игры сразу на несколько платформ.
Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x
В качестве фреймворка нами был выбран cocos2d-x. Это замечательная библиотека с открытым исходным кодом, которая должна была стать частью официального sdk китайской мобильной платформы woPhone. Над её созданием работает команда китайских программистов, которые проделали огромную работу, за что им спасибо (и да простят они мне все ругательства, которые я мысленно отправлял в их адрес, когда они что-то «ломали»).
Список поддерживаемых им платформ можно посмотреть здесь.

Благодаря тому, что проект выложен на github, у нас всегда есть возможность внести свои изменения. Это не просто гипотетическая возможность, мы делаем pull-request’ы, у нас есть изменения, необходимые проекту, которые кочуют из версии в версию.

Нам нравится, что с помощью cocos2d-x мы можем писать игру с использованием c++ на windows в Microsoft Visual C++ Express и сталкиваться с платформенно зависимым кодом (objective c/Java) и средами разработки(XCode, Eclipse, QDE) только в том месте, где они нужны.

Геймплей

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

Когда-то мне на глаза попалась игра Async Racing, обладавшая одной интересной особенностью — участники заезда были представлены «призраками» (в дальнейшем буду называть их «реплеями») людей, которые ранее играли в эту игру. С одной стороны, это исключает возможность взаимодействия между игроками, а с другой — позволяет играть даже при ужасном коннекте и не ждать, когда наберется достаточное число игроков.

Поэтому, когда мы думали можно ли добавить в популярный жанр “беги и прыгай” что-то еще, на ум пришел именно асинхронный мультиплеер. К сожалению, поиск в интернете показал, что «всё придумано уже до нас» и похожая игра уже существует, но только в виде flash-игры. Поэтому от идеи сделать такую игру для мобильных платформ мы не отказались, еще и смогли убедиться, что играть в это интересно (есть и обратная сторона — когда игра нравится, трудно не заимствовать удачные решения, так, например, идея с миром, генерирующимся каждый день, взята из Edmus’а).

Для того, чтобы реплеи не были «тяжелыми», они записываются в виде последовательности битов: 0 — нет нажатия, 1 — есть нажатие. Ввод обрабатывается и записывается с фиксированной частотой — 30 раз в секунду. Обновление игрового состояния происходит 60 раз в секунду.

В случае, если за время между обновлениями ввода было отпускание и новое нажатие, необходимо сделать так, чтобы в поток сперва записался 0 и только потом 1.

Сетевая часть

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

К тому моменту, когда мы начинали работать над этой игрой, в cocos2d-x была добавлена библиотека curl. Так что работа с сервером через http-протокол напрашивалась сама собой.

Попробовали выполнить код на доступных мобильных устройствах:

CURL* curl = curl_easy_init();
if (curl)
{
 curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1");
 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _WriteToBufferFunc);
 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &mBuffer);

 CURLcode res = curl_easy_perform(curl);
 if (res == CURLE_OK && !mBuffer.empty())
  CCLog("We can use libcurl");
 curl_easy_cleanup(curl);  
}

Все заработало, поэтому оставалось только решить, на чем реализовать серверную часть.

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

Показалось заманчивым использовать Google App Engine(url). На тот момент мы не понимали точно, как считаются квоты, и были уверены, что обойдемся теми, что предоставляются бесплатно.

Писали на Python, для экономии ресурсов активно использовали memcache. Задачи перед сервером стояли простые:

  • Уметь получить «реплей» от пользователя и сохранить его
  • Уметь выдавать десяток-два случайных реплеев для формирования толпы, в которой бежит персонаж игрок)
  • Уметь показывать лучшие результаты (в качестве мотиватора)
  • Уметь по расписанию (раз в день) очищать базу данных «реплеев»

Бинарные данные реплея сервер возвращает в виде base64 строки. Декодирование писать не пришлось, в cocos2d-x нашлась функция base64Decode.

Единственное, на чем можно заострить внимание — выборка N случайных записей. GQL(язык для запросов к хранилищу данных GAE) скромнее по возможностям, чем SQL, поэтому выбора и не было. При помещении реплея в базу данных мы присваиваем ему случайное число [0.0..1.0]. Когда нужно выбрать N записей, мы берем N/2 записей, у которых значение rand_value меньше случайного значения и N/2 записей, у которых значение rand_value больше или равно этого же случайного значения.

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

После релиза выяснилось, что квот не хватит, поэтому переписали с gae на php и арендовали сервер.
Узким местом оказались даже не instance hours, а запись в базу данных. Мы сохраняем только лучшие результаты, но все равно число игровых сессий было большим.

Графическая часть

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

Еще до того, как браться за Run In Crowd мы закончили разработку другой игры и нашли художника-фрилансера, который стал рисовать для неё графику. К сожалению, он очень неспешно делает свою работу, так что для Run In Crowd рисовать графику я решил самостоятельно, из принципа.
Тот факт, что графику для игры будет рисовать программист, который бегло знаком с графическими редакторами и чуть глубже пакетами трехмерного моделирования, определил основные требования к графике: она должна быть простой, но стильной.
Поэтому стал рисовать игру в черно-белом стиле. Цветными должны были быть только костюмчики для персонажей и небо на заднем плане.

Поскольку я программист, в первую очередь рассматривались варианты процедурной генерации графики(текстуры tiny wings — очень удачный пример использования). Перепробовав разные варианты, оставили только генерацию холмов на заднем фоне.

Графика рисовалась в Inkscape для того, чтобы можно было получать растровые изображения в любом разрешении.

Звуки и музыка

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

Самостоятельно написать музыку — задача куда более сложная, поэтому решили попробовать выпустить без музыки и звуков, но добавив в описание текст «В данный момент в игре нет ни звуков, ни музыки. Если Вы считаете, что они необходимы — напишите нам». Разумеется об этом стали писать, но не как о проблеме, а как о своем желании. Позже заказали музыку и звуки и добавили их в игру. Пользователи довольны, пишут, что игра стала еще лучше.

Поддержка различных разрешений

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

В cocos2d-x есть поддержка retina. Ресурсы для неё должны иметь постфикс -hd и быть в два раза большего размера.
Мы разрабатываем игру для разрешения 480x320 (если поддержка retina включена — 960x640). Для всех остальных разрешений мы создаем «виртуальный экран» (viewport), у которого одна из сторон имеет размер совпадающий с «эталонным», и, если разрешение высокое, форсируем использование -hd ресурсов вместо стандартных. Это делается с помощью небольшого «хака» cocos2d-x.

Например, экран моей Motorola Milestone имеет физическое разрешение 854x480. Мы создаем viewport с размерами 570x320. Один из размеров совпадает с эталонным (для устройств с aspect ratio >= 1.5 это высота, для остальных, например iPad-а — ширина), другой подобран таким образом, чтобы соотношение сторон осталось таким же, как и у устройства.

В игре, там где нужна привязка к границам экрана, используем не фиксированные значения, а

CCSize size = CCDirector::getSharedDirector()->getWinSize();

Благодаря этому наша игра запускается с двумя наборами графики на совершенно разных разрешениях.

Кроме манипуляций с glViewport (через CCEGLView::create(width, height) ) требуется внести изменения в платформенно-зависимые части CCFileUtils для того, чтобы при включенной retina, хватались ресурсы с -hd.

Вот в этом pull-request’е предлагается подобное решение для android’а.

Выпуск игры

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

Так получилось, что Run In Crowd был выпущен раньше, чем задумывалось.
Благодаря хабру я узнал, что RIM проводит акцию, в которой обещает прислать Playbook 16gb за размещенное в AppWorld’е приложение. Незадолго до этого инженеры Research In Motion сделали порт cocos2d-x для своей платформы и я решил посмотреть, а что это за вещь такая. Регистрация разработчика бесплатная — ничем не рискую, инструментарий под windows есть (очередной плевок в сторону Apple с моей стороны).

Скачал native sdk, симулятор, установил их. Собрал cocos2d-x tests. Все работало! Кроме Run In Crowd, который бессовестным образом падал при запуске.
Как выяснилось, из-за самой нелепой ошибки в порте, какую только можно придумать :)

На тот момент я пользовался бета-версией native sdk и у симулятора были проблемы, из-за которых реализовать и отладить InApp-Purchases не представлялось возможным. Также OpenFeint не поддерживает эту платформу, поэтому игру выпустили с выключенным leaderboard’ом и без покупки костюмчиков. Вместо них на экраны повесили надписи “coming soon” с объяснением, что можно будет увидеть в следующих обновлениях.
Отмечу, что native sdk у Blackberry очень понравился. Все логично, аккуратно, с примерами и хорошей тех.поддержкой.

Позднее, вместо OpenFeint’а интегрировали Scoreloop и успешно добавили поддержку inapp-purchases. Отмечу, что в обсуждениях пользователи blackberry playbook приводят нашу игру как пример того, что внутриигровые покупки доступны в Blackberry Appworld. Позднее менеджер по связям с разработчиком Scoreloop подтвердил, что таких игр в Blackberry Appworld — единицы.

Игру встретили тепло. Через некоторое время нас даже разместили в Featured-разделе Appworld Webstore. За месяц игру скачали больше 100 тысяч раз.

Позднее выпустили игру для iOS и совсем недавно, дождавшись звуков и музыки — на Android’е.

Т.к. игра есть на нескольких платформах, давать при упоминании ссылку на appstore/google play/blackberry appworld не хочется, поэтому сделали страницу для игры. Так получилось, что я выступил еще и в роли html-верстальщика :)

Страницу оформили в стиле близком к внутриигровой графике.

Интересное наблюдение — у игры есть вирусный потенциал, традиционно позиции Blackberry сильны в Канаде, статистика по остальным платформам имеет, в нашем случае, похожую картину. Т.е. мы имеем непривычно много игроков с Канады и всплески интереса на iOS, совпадающие с увеличениями скачек в Blackberry Appworld.

Создали страничку в facebook'е, пользователи пишут на стене, участвуют в обсуждении.

Дальнейшие планы

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

Сейчас интегрируем функции Scoreloop: leaderboards, achievements, challenges. Тормознутый OpenFeint, который поддерживает только две платформы и при этом предоставляет на Android’е сильно урезанный функционал, мы больше в своих проектах использовать не будем.

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

Будем стараться подогревать конкуренцию между игроками с разных платформ. В топ-15, можно посмотреть на странице игры, пока засилие blackberry игроков.

Сложности

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

Единственные неудобства доставляет написание частей кода которые зависят от платформы. В самой игре мы избегаем использования платформеннозависимых участков кода. Создаем обертки над плафторменнозависимыми функциями (например OpenFeint/Scoreloop, открытие браузера, localytics). Это достаточно муторно (особенно для android, с jni писанины выходит довольно много, а еще её приходится усерднее отлаживать).

Аккуратно обновляйте cocos2d-x, после одного обновления игра стала падать, как потом выяснилось из-за того, что rtti между .so работает не так, как в случае статической компиляции.

Ну и традиционная проблема android’а — фрагментация. Google Play сообщает, что Run In Crowd доступен для более чем 1200 устройств. Сразу после релиза столкнулись с тем, что игра вылетала на определенных устройствах. Успели словить две негативных ревьюшки прежде, чем заметили.

Заключение

Опыт разработки мультиплатформенной сетевой игры с использованием cocos2d x

Разрабатывать сразу под несколько платформ не сложнее, чем под одну (все мы знаем, какую).

Cocos2d-x — очень хорошая библиотека. Одни говорят, что она еще сырая — чепуха, он уже давно production-ready. Другие жалуются, что мало документации — тоже не должно быть помехой, ответы можно искать на форумах cocos2d для айфонов, примеры кода переносятся в c++ элементарно, даже если знания objective c минимальны.

Мы выбрали cocos2d-x и разработку сразу под несколько платформ и не жалеем об этом.

Ссылка на страницу игры — http://runincrowd.ursinepaw.com.

Автор: moadib


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js