1. Вводная часть
В этой статье речь пойдет о разработке прототипа игры под Android/Linux с помощью Cocos2d-x. Cocos2d-x это кросс-платформенный фреймворк для создания двумерных игр и других графических приложений. Он создан на основе cocos2d-iphone, но вместо Objective-C Cocos2d-x использует C++. Приложения могут быть запущены на платформах: iOS, Android, Windows Phone, OS X, Windows, Linux.
Эта статья о разработке приложения под Android/Linux, платформа разработки — Ubuntu.
К сожалению, Cocos IDE существует только под Window и Mac, а под Linux версии не предвидится, но так как я очень люблю Ubuntu и это та платформа, с которой я работаю каждый день, я использую для разработки Clion.
Некоторые части этой статьи — перевод соответствующих частей документации (http://www.cocos2d-x.org/wiki).
Статья ориентирована на тех, кто начинает свое знакомство с Cocos2d-x. В ней я расскажу о том, как создать самое простое приложение со спрайтами, сценами и как работать с акселерометром. Это может быть неким стартом, чтобы в будущем идти дальше, углубляясь в API Cocos2d-x.
2. Требования
Определим необходимый набор инструментов, который нам понадобится.
Ubuntu 14.04 — платформа разработки
Cocos2d-x v3.8.1 — https://cocos2d-x.org/download
CMake 2.8+
JDK 1.6+
Android SDK
Android NDK r9d+
Apache Ant — для сборки Android-сборки
Python 2.7.5
Clion 1.1 — www.jetbrains.com/clion
3. Cocos2d-x
Cocos2d-x появился в 2010 году, это проект с открытым исходным кодом, распространяющейся под лицензией MIT. Cocos2d-x позволяет писать на таких языках как C++, Lua и Javascript. Cocos2d-x быстрый, простой и обладает большими возможностями. В настоящее время много игр, написанных с помощью этого фреймворка, находятся в топе AppStore и Google Play.
«Большие ребята», такие как Zynga, Wooga, Glu, Big Fish Games, Konami используют Cocos2d-x для разработки игр.
Тут я должен сделать маленькое отступление. Дело в том, что Cocos2d-x позволяет использовать C++ для написания кода, и все это будет собираться под Android и Linux. Если использовать Cocos2d-JS, то можно писать на Javascript. Тогда в случае сборки для на Linux/Ubuntu код будет транслироваться в C++, а затем компилироваться. Это влечет много разных проблем (усложняет работу со сторонними библиотеками, например). В случае же web будет выполнятся java-script (кстати, время сборки в этом случае равна нулю, что неплохо).
Если же использовать Cocos2d-x, то под web это не работает, но под Linux и Android код компилируется напрямую. Для Android, конечно же, используется Android NDK.
Главные особенности:
- OpenGL
- Управление сценами
- Эффекты смены сцен
- Спрайты
- Визуальные эффекты
- Различные экшены, такие как движение, масштабирование, повороты и так далее
- Комбинации экшенов
- Меню и кнопки
- Обработка касаний на мобильных устройствах
- Поддержка звука
- Лейауты
- ListView, PageView, ScrollView
- Button, CheckBox, LoadingBar, Slider, TextField
Ссылки:
http://www.cocos2d-x.org/wiki/Cocos2d-x
http://www.cocos2d-x.org/wiki/About_Cocos2d-x
4. Установка и запуск приложения
Эта часть статьи показывает как создать и запустить первое приложение.
Для начала, нам необходимо загрузить и распаковать Cocos2d-x. Или же можно использовать последнюю версию из репозитория:
git clone https://github.com/cocos2d/cocos2d-x.git
После того, как все было успешно загружено, нужно, нужно установить все зависимости. Для этого можно запустить скрипт*:
~/work/cocos/cocos2d-x-3.8.1/build/install-deps-linux.sh
* Здесь и далее предполагается, что cocos2d-x-3.8.1 распакован в каталог ~/work/cocos/cocos2d-x-3.8.1.
Или же устанавливаем их вручную:
список
libx11-dev
libxmu-dev
libglu1-mesa-dev
libgl2ps-dev
libxi-dev
g+±4.9
libzip-dev
libpng12-dev
libcurl4-gnutls-dev
libfontconfig1-dev
libsqlite3-dev
libglew-dev
libssl-dev
glfw3
libxmu-dev
libglu1-mesa-dev
libgl2ps-dev
libxi-dev
g+±4.9
libzip-dev
libpng12-dev
libcurl4-gnutls-dev
libfontconfig1-dev
libsqlite3-dev
libglew-dev
libssl-dev
glfw3
Для установки glfw3 нужно запустить еще один скрипт.
~/work/cocos/cocos2d-x-3.8.1$ tools/travis-scripts/install_glfw.sh
После чего, запускаем установку:
~/work/cocos/cocos2d-x-3.8.1$ ./setup.py
Запустим cmake для создания makefile:
cd ~/work/cocos/cocos2d-x-3.8.1/build
mkdir linux-build
cd linux-build
cmake ../..
Теперь осталось запустить тестовое приложение:
make
cd bin/cpp-tests/
./cpp-tests
Всё. Если все прошло хорошо, вы должны увидеть тестовое приложение, что-то вроде демо-версии. Если все получилось, то можно идти дальше. Нам нужно создать проект.
Для создания приложения, запускаем:
cocos new MyGame -p com.your_company.mygame -l cpp -d NEW_PROJECTS_DIR
где MyGame — имя, cpp — язык. Можно указать js или lua. Но мы указываем cpp.
Теперь проект создан, с ним мы и будем дальше работать. Пустой проект содержит лейбл и картинку. Можно сразу на это посмотреть:
cocos run -s ~/work/cocos/MyCompany/MyGame/ -p android
или
cd ~/work/cocos/MyCompany/MyGame
cocos run -p linux
Вот, собственно, и можно начинать работать.
5. Clion
Как я уже писал, Cocos IDE нет для Ubuntu, но ведь нам не хочется писать код в блокноте? К счастью, существует Clion от Jetbrains.
Clion поддерживает cmake, а это значит, что достаточно просто импортировать проект MyGame, и можно писать код. И не просто писать, но и отлаживить его! Правда, запускается все это под Linux. Но, я думаю, что для начала этого вполне достаточно. После отладки приложение для Android всегда можно собрать через консоль.
Подробнее про Clion: http://habrahabr.ru/company/JetBrains/blog/255723/
6. Основы
Итак, мы загрузили Cocos2d-x, установили все инструменты, импортировали проект в Clion, запустили и… и теперь пора разобраться, что к чему. Начнем.
Cocos2d-x это кросс-платформенный игровой движок. Но что такое игровой движок? Игровой движок предоставляет общую функциональность которая должна быть у всех игр. Он включает в себя компоненты, которые вместе позволяют ускорить разработку. Например renderer (Часть терминов в статье я не перевожу, так как они общеприняты. Слово «Активность» я тоже использовать не буду), графику, механизм обнаружения столкновений, физику, звук, анимацию. Cocos2d-x предоставляет простой API для разработки кросс-платформенных приложений (вернее, позволяет собирать приложение под разные платформы).
Cocos2d-x предоставляет такие объекты как Scene, Transition, Sprite, Menu, Sprite3D, Audio и другие.
Главные компоненты
В начале может показаться не так, но на самом деле начать работать с Cocos2d-x очень просто. Ядро Cocos2d-x это Scene, Node, Sprite и Action. Посмотрим, что их них что.
Большинство игр выглядит как-то так:
Вот все эти компоненты:
Director
Director в Cocos2d-x это как режиссер на съемочной площадке. Он контролирует все объекты и говорит им, что они должны делать. Director управляет сменой сцен и эффектами перехода, он является синглтоном и доступен отовсюду (хотя в кино, наверно, не так).
Scene
В вашей игре скорее всего будет меню, несколько уровней, и еще несколько экранов («Вы проиграли!»). Каждый такой экран — это сцена (Scene). Опять же, как в кино. Каждое кино разбито на сцены — отдельные части одной истории. Сцена отрисовывается объектом Renderer. Renderer отвечает за отрисовку спрайтов и других объектов сцены. Для лучшего понимания это процесса, поговорим о Scene Graph.
Scene Graph
Scene Graph это структура данных, которая используется в Scene. Scene Graph содержит узлы (Node). Кстати говоря, Scene Graph называется Scene Graph, но на самом деле это дерево.
Это уже выглядит слегка сложновато. Вы должны спросить, на кой черт мне нужно знать о том, как устроена Scene внутри, если мне просто нужен двигающийся человечек? Это важно для понимания того, как именно отрисовывается Scene. Добавляя спрайты и анимацию в вашу игры вы должны быть уверены, что получите результат, который хотите.
Cocos2d-x выполняет симметричный обход дерева (при котором посещается сначала левое поддерево, затем узел, затем — правое поддерево). Это значит, что правая часть дерева отрендерится последней, и значит, будет видна «сверху» (то есть визуально остальные — под ней).
Проиллюстрировать это очень легко, давайте просто взглянем на эту сцену:
Которую упрощенно (часть объектов являются не листами, а ветками) можно представить в виде дерева:
Связанный с порядком атрибут это z-order. Левая часть дерева имеет отрицательный z-order, в том время как правая — положительный. Можно держать это в голове, чтобы избежать ошибок. Плюс к этому, вы можете добавлять элементы в любом порядке с указанным z-order, тогда элементы будут отсортированы автоматически.
Можно рассматривать Scene как набор объектов (Node). Разобьем сцену, показанную выше, чтобы посмотреть scene graph:
Сцена слева это сложенные вместе узлы (Node) имеющие разный z-order, которые определяют порядок их отрисовки.
Добавить элемент к сцене можно вызвав API:
// Добавляем элемент с z-order −2,
// он будет в левой части дерева
scene->addChild(title_node, −2);
// Если не указать z-order явно, используется значение по умолчанию — 0
scene->addChild(label_node);
// Добавляем элемент с z-order 1,
// он будет в правой части дерева
scene->addChild(sprite_node, 1);
Sprites
Все игры используют спрайты. Это те штуки, которые двигаются по экрану. Вы можете управлять ими. Главный герой игры, скорей всего, является спрайтом. Важно отметить: не каждый графический элемент игры это спрайт. Если элемент не перемещается по экрану, то это просто узел (Node).
Взглянем еще раз на сцену из игры:
Спрайты это ключевые элементы игры. Создать спрайт очень легко:
// Создаем спрайт
auto mySprite = Sprite::create("mysprite.png");
// Устанавливаем позицию на экране
mySprite->setPosition(Vec2(500, 0));
// Угол поворота
mySprite->setRotation(40);
mySprite->setScale(2.0); // Масштабирование
addChild(mySprite); // добавляем спрайт к сцене.
Actions
Создание сцены с добавление спрайтов на экран это только часть задачи. Ведь нам еще надо, чтобы все это двигалось. Для этого и существуют экшены (Actions). Движение, повороты, вращение — это все экшены. Экшены очень похожи на ValueAnimator в Android API.
auto mySprite = Sprite::create("Blue_Front1.png«);
// Смещаем на 50 точек наверх и на 10 вправо, за две секунды:
auto moveBy = MoveBy::create(2, Vec2(50,10));
mySprite->runAction(moveBy);
// Перемещаем с конкретную точку:
auto moveTo = MoveTo::create(2, Vec2(50,10));
mySprite->runAction(moveTo);
Система координат
Надо заметить, что Cocos2d использует декартову систему координат. То есть точка (0, 0) находится слева внизу. Это отличает Cocos2d, скажем, от того же Android API.
7. Идея игры
Дальше мы создадим некий прототип игры, чтобы проиллюстрировать работу с Cocos2d. Идея игры очень проста: по экрану хаотично передвигаются объекты, допустим, смайлики (а почему нет?), и есть кто-то, кто их должен есть. А от грустных смайлов наоборот, стараться скрыться. Причем управление пожирателем происходит за счет отклонения телефона в разные стороны. Таким образом, мы задействуем спрайты, экшены, смену сцен, работу с акселерометром.
Понятно, что идея очень проста и скучна, но мы не ставим задачу заработать миллион (но только пока что, да?), а нам нужно разобраться что к чему в Cocos2d, и эта идея вполне подходит. Что ж, начнем!
8. Добавление спрайтов
За основу возьмем приложение MyGame, которое мы создавали в начале статьи. Нам пригодятся смайлы, которые без труда можно найти на просторах интернета, или же их можно заменить чем-то другим. Шарами какими-нибудь.
Все ресурсы надо положить в каталог Resources.
Добавим в метод init() следующий код:
for (int i = 0; i < SMILES_COUNT; i++) {
std::string result = «emoji-» + itos(i);
Sprite *sprite = Sprite::create(result);
if (sprite != NULL) {
this->addChild(sprite);
listSprites.push_front(sprite);
}
}
listSprites это список всех смайлов, нам он пригодится чтобы добавить перемещение по экрану. Сделаем еще заодно и так, чтобы они немного вращались, в процессе движения:
Point *sizeStart = randomEndPoint(); // точка старта
Point *sizeStop = randomEndPoint(); // точка финиша
auto rotateAction = RotateTo::create(2, 90); // на 90 градусов за две секунды.
auto moveAction = MoveTo::create(2, *sizeStop);
sprite->setPosition(*sizeStart);
auto mySpawn = Spawn::createWithTwoActions(rotateAction, moveAction);
auto seq = Sequence::create(mySpawn,
CallFunc::create(callback),
nullptr);
sprite->runAction(seq);
Здесь мы создаем Spawn и Sequence, чтобы выполнить два экшена одновременно, и плюс к этому, в Sequence мы передаем callback — ссылку на функцию, которая вызовется, когда экшен выполнится. Это позволит нам перезапускать экшен каждый раз, чтобы спрайты двигались бесконечно. Надо создать такой экшен для каждого спрайта.
Метод randomEndPoint возвращает точку на границе экрана, выбранную случайным образом.
Теперь у нас есть много двигающихся спрайтов:
Еще нам надо добавить пожирателя, но это делается аналогично, не будем на этом останавливаться.
9. Разрешение коллизий
Теперь нам надо написать немного кода для разрешения коллизий. То есть, обнаружить момент когда мелкие смайлы будут касаться пожирателя.
Для этого, переопределим метод onUpdate:
void HelloWorld::update(float dt)
{
...
}
Чтобы это метод вызывался при обновлении экрана вызываем scheduleUpdate():
void HelloWorld::init(float dt)
{
this->scheduleUpdate();
...
}
Теперь при каждом обновлении экрана (и изменении позиций спрайтов, соответственно) будет вызываться метод update. Для простоты можно написать что-то вроде такого:
void HelloWorld::init(float dt)
{
if(spriteSmile->getPosition().distance(eaterSprite->getPosition()) <
spriteSmile->getBoundingBox().size.width/2 +
eaterSprite->getBoundingBox().size.width/2){}
}
Когда коллизия найдена, можно посчитать очки и перезапустить Action:
void HelloWorld::init(float dt)
{
if(spriteSmile->getPosition().distance(eaterSprite->getPosition()) <
spriteSmile->getBoundingBox().size.width/2 +
eaterSprite->getBoundingBox().size.width/2){
score—;
spriteSmile->stopAllActions();
spriteSmile->setPosition(0, 0);
runActionForSmile(spriteSmile);
}
}
10. Акселерометр
Работать с акселерометром также просто, как разрешать коллизии, даже проще.
Для начала, подпишемся на нужное событие:
void HelloWorld::init(float dt)
{
...
this->setAccelerometerEnabled(true);
...
}
Чтобы получать значения акселерометра переопределим метод onAcceleration:
void HelloWorld::init(float dt)
{
void GameScene::onAcceleration(cocos2d::Acceleration *acc, cocos2d::Event *unused_event) {
Director *director = Director::getInstance();
auto viewSize = director->getWinSize();
float minSize = MIN(viewSize.width, viewSize.height);
double targetSpeedY = acc->y * minSize*2;
double targetSpeedX = acc->x * minSize*2;
eaterPointsPerSecY = (eaterPointsPerSecY * .8f) + (targetSpeedY * .2f);
eaterPointsPerSecX = (eaterPointsPerSecX * .8f) + (targetSpeedX * .2f);
}
}
Вычислив eaterPointsPerSecX и eaterPointsPerSecY в методе update вызываем setPosition():
float diffY = (eaterPointsPerSecY * dt)*1.3f;
float diffX = (eaterPointsPerSecX * dt)*1.3f;
float newY = eaterSprite->getPosition().y + diffY;
float newX = eaterSprite->getPosition().x + diffX;
eaterSprite->setPosition(newX, newY);
Конечно, в реальной игре надо еще следить чтобы наш пожиратель не вышел за границы экрана. Но пока что остановимся на этом.
Итак, мы создали летающие смайлы, добавили поедателя, управляем им с помощью акселерометра… осталось, добавить экран «Game Over!».
11. Game Over!
Вот тут нам понадобится новая сцена. Можно подсмотреть в HelloWorld и создать ее по образцу. Так мы и поступим:
// GameOverScene.h
#include «cocos2d.h»
class GameOverScene : public cocos2d::Layer {
public:
virtual bool init();
static cocos2d::Scene *createScene();
void startAgainCallback(cocos2d::Ref *pSender);
CREATE_FUNC(GameOverScene);
};
// GameOverScene.cpp
#include <ui/UIDeprecated.h>
#include «GameOverScene.h»
#include «GameScene/GameScene.h»
using namespace std;
using namespace cocos2d;
using namespace cocos2d::ui;
Scene *GameOverScene::createScene() {
auto scene = Scene::create();
GameOverScene* layer = GameOverScene::create();
scene->addChild(layer);
return scene;
}
bool GameOverScene::init() {
//////////////////////////////
// 1. super init first
if (!Layer::init()) {
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
auto gameOVerLabel = Label::createWithTTF("Game over!«, «fonts/Comfortaa-Bold.ttf», 24);
gameOVerLabel->setTextColor(Color4B::WHITE);
auto startAgainLabel = Label::createWithTTF("Play again«, «fonts/Comfortaa-Bold.ttf», 30);
startAgainLabel->setTextColor(Color4B::ORANGE);
auto playAgainItem = MenuItemLabel::create(startAgainLabel,
CC_CALLBACK_1(GameOverScene::startAgaingCallback, this));
auto menu = Menu::create(playAgainItem, NULL);
gameOVerLabel->setPosition(visibleSize.width/2, visibleSize.height/2 + gameOVerLabel->getContentSize().height * 3);
menu->setPosition(visibleSize.width/2, gameOVerLabel->getPosition().y — gameOVerLabel->getContentSize().height);
this->addChild(gameOVerLabel);
this->addChild(menu);
}
void GameOverScene::startAgaingCallback(Ref *pSender) {
Director::getInstance()->replaceScene(
TransitionFade::create(0.5, HelloWorld::createScene(), Color3B(255, 255, 255)));
}
#endif //MYGAME_GAMEOVERSCENE_H
Почти все, что здесь написано, нам уже известно.
Здесь, по аналогии с HelloWorld мы создаем лейбл который является элементом меню, то есть мы можем обработать событие onClick. Создавая MenuItemLabel мы передаем одним из параметров ссылку на функцию startAgaingCallback, в которой меняем сцену. Заметим, что меняем сцену еще и с применением эффекта TransitionFade.
Точно также мы можем поменять сцену игры на GameOverScene:
void HelloWorld::update(float dt) {
if(score <= 0){
Director::getInstance()->replaceScene(TransitionFade::create(0.5, GameOverScene::createScene(score), Color3B(255,0,0)));
}}
GameOver!
Ссылки
www.cocos2d-x.org
www.jetbrains.com/clion
habrahabr.ru/post/126582
www.cocos2d-x.org/programmersguide
www.raywenderlich.com/33752/cocos2d-x-tutorial-for-ios-and-android-space-game
Комментарии (2)
demonit
17.11.2015 12:26+1молодец… хорошая статья для начинающих в кокосе, но не мешало бы добавить что то типа CocosBuilder (SpriteBuilder, CocoStudio) ибо ваять сцены на голом кокосе — это мазохизм.
Ну и в догонку некоторые баги кокоса — у меня регулярно пропадает звук, на некоторых девайсах спрайты превращаются тупо в черные квадраты ну и тп. В общем, по уровню вылизанности кокосу еще расти и расти, хотя в целом очень неплохо и для старта в самый раз.
hellman
Э, а где Контра :(