Предисловие
Начну с небольшой предыстории. Не так давно, мы с другом решили разработать свою двумерную игру, для дипломного проекта. После того как мы определились с ее жанром и получили примерное представление об игровом процессе, перед нами встал вопрос о выборе движка. Unreal или Unity мы отбросили сразу, так как они показались нам слишком «тяжелыми» инструментами для 2d игры. Cocos2d-x был бы идеальным вариантом, если бы не одна деталь — практически полное отсутствие вводных уроков. Те статьи, что нам удалось найти были, либо не закончены, либо недостаточно подробны. И тогда, я решил перевести официальную документацию, практически без знаний английского и заодно поделится своим переводом с остальными (кто же если не я).
Эта статья представляет собой почти дословный перевод официальной документации к Cocos2d-x. Если вы не хотите разбираться во всяких тонкостях устройства движка, а хотите сразу начать писать свою игру, могу посоветовать вам эту статью: Cocos2d-x — разработка простой игры .
Если вы еще не установили Cocos2d-x или не можете создать проект, то тут вы найдете подробную инструкцию: Создание многоплатформенных игр с использованием Cocos2d-x версии 3.0 и выше.
Оригинальная статья: Cocos2d-x programmers guide.
P.S.: Не советую клонировать cocos2d-x с gitHub. У меня после клонирования не доставало нескольких важных файлов. Но может быть мне просто не повезло.
Приступим!
Основные понятия Cocos2d-x
Эта глава предполагает, что вы только начали ознакомление с Cocos2d-x и уже готовы начать работу над игрой вашей мечты. Не волнуйтесь, будет весело!
Cocos2d-x кросс-платформенный игровой движок. Игровой движок — это программное обеспечение, которое обеспечивает базовый функционал, необходимый всем играм. Вы могли слышать, что это называется API или фреймворк, но в этом руководстве мы будем называть его 'игровым движком'.
Игровые движки включают множество компонентов, которые при совместном использовании повышают скорость разработки и часто работают лучше самодельных библиотек. Игровой движок обычно содержит некоторые или все следующие компоненты: визуализатор, 2d/3d графику, обработчик столкновений, физический движок, звук, анимацию и многое другое. Игровые движки обычно поддерживают несколько платформ, таким образом они упрощают портирование вашей игры на различные устройства.
Поскольку Cocos2d-x является игровым движком, он предоствляет API для разработки кросс-платформенных игр. Благодаря инкапсуляции мощности внутри простого для использования API, вы можете сосредоточится на разработке вашей игры и меньше беспокоиться о технической основе. Cocos2d-x возьмет на себя тяжелую работу.
Cocos2d-x предоставляет следующие объекты: Scene, Transition, Sprite, Menu, Sprite3D, Audio и многие другие. Всё необходимое для создания игры включено.
Main Components
Это может показаться очень сложным, но начать использовать Cocos2d-x просто. Перед погружением, мы должны понять некоторые концепции используемые в Cocos2d-x. В сердце Cocos2d-x лежат объекты: Scene, Node, Sprite, Menu и Action. Посмотрите на любую игру и вы увидите все эти компоненты в той или иной форме!
Взгляните сюда. Возможно вы обнаружите схожесть с одной очень популярной игрой:
Давайте посмотрим еще раз, но разделим скриншот на компоненты, используемые для его создания:
Вы можете видеть меню, некоторые спрайты и надписи, все они имеют аналоги в Cocos2d-x. Взгляните на свой собственный концепт игры и посмотрите какие у вас есть компоненты, скорее всего вы обнаружите совпадения.
Director
Cocos2d-x использует концепцию режиссера (Director), как в кино! Объект Director контролирует поток операций и сообщает всем что делать. Основной задачей режиссера является контроль переходов и замен сцен. Director — это базовый singleton (фактически, может существовать только один экземпляр этого класса за раз), который может быть вызван в любом месте вашего кода.
Вот пример типичного игрового потока. Director позаботится о его исполнении, в соответствии критериям вашей игры:
Вы являетесь режиссером вашей игры. Вы решаете что, когда и каким образом происходит.
Scene
В своей игре вы, вероятно, хотите видеть главное меню, несколько уровней и завершающую сцену. Как вы всё это и организуете? Как вы догадались, при помощи сцен (Scene). Вспомните какой-нибудь понравившийся вам фильм. Вы можете заметить, что он четко разбит на сцены или отдельные части истории. Если мы применим тот же самый подход к играм, мы должны придумать как минимум несколько сцен, независимо от того, насколько простая игра.
Снова взглянем на уже знакомое нам изображение:
Это главное меню и оно является отдельной сценой. Эта сцена состоит из нескольких частей, которые в совокупности дают нам финальный результат. Сцены рисуются при рендере. Рендер отвечает за отображение спрайтов и других объектов на сцене. Чтобы лучше это понять нам нужно немного поговорить о графе сцены (scene graph).
Scene Graph
Scene graph это структура данных организующая графическую сцену. Scene graph содержит Node объекты в древовидной (да, это называется графом, но, фактически, представляет собой дерево) структуре.
Я уверен, что вы задаётесь вопросом, почему вы должны думать об этих технических деталях, если Cocos2d-x делает тяжёлую работу за вас? Это действительно важно для понимания того, как сцены отрисовываются в ходе рендера.
Как только вы начинаете добавлять узлы, спрайты и анимации в свою игру, вы хотите быть уверены в том, что на экране отобразится именно то, чего вы ожидали. Но что если этого не произошло? Что если ваши спрайты оказались скрыты за фоном, а вы хотите, чтобы они были спереди? Ничего страшного, просто сделайте шаг назад и пробегитесь по графу сцены на листе бумаги, и я уверен, вы легко найдете свою ошибку.
Поскольку Scene Graph является деревом; вы можете его обойти. Cocos2d-x использует in-order алгоритм обхода. Вышеупомянутый алгоритм in-order начинает обход с левой стороны дерева, затем проходит через корень и завершается в правой части дерева. Поскольку правая сторона дерева рендерится последней, на экране она отображается первой.
Граф сцены легко продемонстрировать, давайте взглянем на нашу игровую сцену:
Она будет отображаться на экране согласно дереву, приведенному ниже:
Еще один момент о котором нужно рассказать — элементы с отрицательным порядком Z (z-order) находятся с левой стороны дерева, в то время как элементы с положительным порядком Z находятся с правой стороны. Держите это во внимании при упорядочивании ваших элементов! Конечно, вы можете добавлять элементы в любом порядке, и они автоматически отсортируются исходя из настраиваемого параметра z-order.
Основываясь на этой концепции, мы можем подумать о сцене как о коллекции узлов (Node). Давайте разобьем сцену на части сверху вниз, чтобы увидеть как scene graph использует z-order для компоновки сцены:
Фактически, сцена собрана из множества узлов, с различными параметрами z-order, сложенных в «стек» друг над другом.
В Cocos2d-x вы строите scene graph используя вызов addChild():
// Добавление дочернего элемента с z-order равным -2, таким образом
// он идет в левую сторону дерева (потому что z-order отрицательный)
scene->addChild(title_node, -2);
// Если вы не указали z-order, будет использован 0
scene->addChild(label_node);
// Добавление дочернего элемента с z-order равным -2, таким образом
// он пойдет в правую часть дерева (потому что z-order положительный)
scene->addChild(sprite_node, 1);
Sprites
Все игры содержат спрайты, и вы, наверняка, уже имеете представление о них. Спрайты представляют собой графические объекты, которые вы перемещаете по экрану. Главный персонаж вашей игры, вероятнее всего, тоже спрайт. Я знаю, вы можете спросить — не каждый графический объект является спрайтом? Нет! Почему? Спрайт является спрайтом, только если вы его перемещаете. Если вы не перемещаете его, то это обычный узел (Node).
Ещё раз взглянем на знакомое изображение, отметим где спрайты а где узлы:
Спрайты важны в каждой игре. При написании платформера, у вас, вероятно, есть главный персонаж, который был создан с использованием какого-либо изображения. Это и есть спрайт.
Спрайты просто создать и они имеют следующие настраиваемые параметры: position, rotation, scale, opacity, color и другие.
// Вот так создается спрайт
auto mySprite = Sprite::create("mysprite.png");
// А так изменяются свойства спрайта
mySprite->setPosition(Vec2(500, 0));
mySprite->setRotation(40);
mySprite->setScale(2.0); // равномерно задает масштаб обеим осям X и Y
mySprite->setAnchorPoint(Vec2(0, 0));
Давайте продемонстрируем каждое свойство из данного кода, рассмотрим следующий скриншот:
Если мы зададим позицию используя mySprite->setPositon(Vec2(500, 0));:
Узел-спрайт изменил свою позицию с оригинальной на заданную нами.
Если мы сейчас зададим вращение, используя mySprite->setRotation(40);:
… вы можете видеть, что спрайт был повернут на величину, которая была указана.
Если мы сейчас зададим новый масштаб используя mySprite->setScale(2. 0);:
Снова, мы можем видеть, что спрайт изменился в соответствии с кодом.
Наконец, все узлы (поскольку Sprite является подклассом Node) имеют опорную точку (anchor point). Мы еще не говорили об этом, так что сейчас самое время. Вы можете думать о опорной точке как о способе определения, какая часть спрайта будет использоваться как базовая координата, при задании позиции.
Зададим опорную точку для персонажа нашей игры, используя следующий вызов:
mySprite->setAnchorPoint(Vec2(0, 0));
Это приведет к тому, что нижний левый угол нашего спрайта станет использоваться как базис для каждого вызова setPosition(). Давайте взглянем на несколько таких примеров в действии:
Обратите внимание на красную точку в каждом изображении. Эта красная метка иллюстрирует, где находится опорная точка!
Как вы могли заметить, опорная точка очень полезна при позиционировании узлов. Вы даже можете регулировать опорную точку динамически, для симуляции эффектов в вашей игре.
Actions
Создание сцены и добавление спрайтов на экран — это только часть того, что нам необходимо сделать. Чтобы сделать игру, нам необходимо заставить вещи двигаться! Action объекты являются основной частью каждой игры. Действия (Action) позволяют трансформировать Node объекты во времени. Хотите двигать спрайт из одной точки в другую? Нет проблем! Вы даже можете создать последовательность (Sequence) действий, которые будут выполнены на узле. Вы можете менять параметры узла такие как position, rotation и scale. Примеры действий: MoveBy, Rotate, Scale.
Сейчас мы продемонстрируем работу действий:
… и через 5 секунд спрайт переместится в новое положение:
Action объекты создавать легко:
auto mySprite = Sprite::create("Blue_Front1.png");
// Двигаем спрайт на 50 пикселей вправо и 10 пикселей вверх за 2 секунды
auto moveBy = MoveBy::create(2, Vec2(50,10));
mySprite->runAction(moveBy);
// Двигаем спрайт в заданную локацию в течении двух секунд.
auto moveTo = MoveTo::create(2, Vec2(50,10));
mySprite->runAction(moveTo);
Sequence и Spawn
С движущимися по экрану спрайтами мы имеем все необходимое, для создания нашей игры, верно? Не вполне. Как же запустить несколько действий? Cocos2d-x реализует это, несколькими путями.
Sequence — это множество действий, запускаемых в определенном порядке. Нужно запустить последовательность в обратном порядке? Нет проблем, Cocos2d-x реализует это без дополнительной работы.
Взглянем на следующий пример последовательности для постепенного движения спрайта:
Эту последовательность легко создать:
auto mySprite = Node::create();
// двигается в точку 50, 10 за 2 секунды
auto moveTo1 = MoveTo::create(2, Vec2(50,10));
// сдвигается относительно текущей позиции на 100, 10 за 2 секунды
auto moveBy1 = MoveBy::create(2, Vec2(100,10));
// двигается в точку 150, 10 за 2 секунды
auto moveTo2 = MoveTo::create(2, Vec2(150,10));
// создаем задержку
auto delay = DelayTime::create(1);
mySprite->runAction(Sequence::create(moveTo1, delay, moveBy1, delay.clone(),
moveTo2, nullptr));
Этот пример запускает последовательность, по порядку, но как запустить все указанные действия одновременно? Такая возможность тоже есть в Cocos2d-x, это называется Spawn. Spawn будет принимать все указанные действия и запускать их одновременно. Некоторые могут быть длиннее других, в таком случае все они не будут закончены в одно время.
auto myNode = Node::create();
auto moveTo1 = MoveTo::create(2, Vec2(50,10));
auto moveBy1 = MoveBy::create(2, Vec2(100,10));
auto moveTo2 = MoveTo::create(2, Vec2(150,10));
myNode->runAction(Spawn::create(moveTo1, moveBy1, moveTo2, nullptr));
Родители, дети и их связь
Cocos2d-x использует принцип наследования свойств родителей и детей. Это означает, что изменения родительского узла применяются и к дочерним элементам. Рассмотрим один спрайт, который имеет наследников:
Изменение вращения родителя так же изменяет вращение всех детей:
auto myNode = Node::create();
// задаем вращение
myNode->setRotation(50);
По аналогии с вращением, если вы измените масштаб родителя, наследникам так же будет задан новый масштаб:
auto myNode = Node::create();
// масштабирование
myNode->setScale(2.0); // увеличиваем в 2 раза
Не все изменения родителя отражаются на детях. Изменения родительской опорной точки влияет только на операции трансформации (scale, position, rotate, skew и так далее ...) и не влияет на позиционирование наследников. Фактически, дети всегда будут добавляться в нижний-левый (0,0) угол родителя.
Logining как способ вывода сообщений
Иногда, когда ваше приложение запущено, вы можете захотеть увидеть сообщение, выведенное в консоль, для информации или отладочных целей. Реализуется движком, с помощью использования log().
// простая строка
log("This would be outputted to the console");
// переменная string
string s = "My variable";
log("string is %s", s);
// double
double dd = 42;
log("double is %f", dd);
// integer
int i = 6;
log("integer is %d", i);
// float
float f = 2.0f;
log("float is %f", f);
// bool
bool b = true;
if (b == true)
log("bool is true");
else
log("bool is false");
Если вы предпочитаете std::cout, то можете использовать его вместо log(), однако log() мог бы предоставить упрощенное форматирование сложного вывода.
Вывод
Мы разобрали множество понятий Cocos2d-x. Можете глубоко вздохнуть. Не волнуйтесь. Просто погрузитесь в свои идеи и реализуйте их, шаг за шагом. Cocos2d-x и программирование не являются навыками, которые изучаются за одну ночь. Они требуют практики и понимания. Помните, что форумы так же помогут ответить на ваши вопросы.
От переводчика
Возможно, многое из этой статьи вам уже было известно. Но это была лишь вводная часть из серии уроков по cocos2d-x. В дальнейшем, я буду продолжать переводить новые статьи и выкладывать их сюда, если это будет кому-то интересно.
Продолжение: Cocos2d-x — Работа со спрайтами
Комментарии (13)
Kellon
07.10.2017 20:54У меня есть вопрос. Сам пишу на этом движке (правда на JavaScript версии, так как там есть возможность изменять исходники в рантайме).
Как выключать из билда ненужные библиотеки?
Например, пустой проект тянет за собой bullet, chipmunk, библиотеки для отображения 3D моделей и тд. Хотя я не использую ни одну из них, при компиляции они добавляется в билд. Для Android у меня сейчас билд 15 мб, и я наю, что отключив все что мне не надо, его можно уменьшить вдвое.MrMysterious Автор
07.10.2017 21:00Как было сказано в начале статьи, я сам только недавно начал осваивать cocos и этого вопроса еще не успел коснутся.
scar_nik
08.10.2017 08:48Нет не отпилить никак, не изменив исходного кода и структура проекта.
Вроде как у них в планах это на версию 4.0, которая кажется длится и просится уже бесконечно.
sova
08.10.2017 02:47если бы я уже не писал на cocos2dx, я бы не стал инвестировать своё время в движок который развивает и поддерживает 1 человек. возможно я не прав)
Velet
08.10.2017 07:25Как-то испытывал Cocos2d-x, очень разочаровал, не знаю, что они там на С++ написали, но очень медленно работает, 2к спрайтов и тормозит насмерть, а тот же haxe 200к спрайтов выдает без тормозов, но haxe, конечно не игровой движок.
scar_nik
08.10.2017 08:53+1По поводу клонирования репозитория — его можно склонировать, только надо выполнить:
git submodule update --init --recursive python download-deps.py
goldferum1991
08.10.2017 09:00спасибо за перевод. Жду продолжения:) Кстати, почему выбрали c++, почему не js?
MrMysterious Автор
08.10.2017 09:05Просто с++ мне ближе, я уже долгое время пишу на нем. Также в с++ есть больше возможностей для оптимизации, но для маленьких проектов это не критично. Рад, что вам понравилась статья!
sandyre
08.10.2017 17:13Спасибо за статью. Понимаю, что это лишь цикл переводов, однако есть пожелание — упомяните, пожалуйста, в одной из будущих статей про такой метод, как
performInCocosThread
(в простейших играх он не нужен, однако если появляется асинхронная работа с сетью, или signal/slots — его использование просто необходимо, если в хендлере сигнала будет изменяться любая из cocos-Node). Если что — по ряду вопросов могу проконсультировать (порядка года пинаю этот движок).
NeronSparda
Спасибо за статью! Ещё было бы классно составить что-то вроде справочника по движку.
С описанием всех функций и объектов. А то документация по кокосу очень скудная, хотя движок интересный.
MrMysterious Автор
Официальная документация к движку весьма информативна, там описываются все основные функции и объекты. Я уже работаю над переводом второй части, в ней будут более подробно рассмотрены функции спрайтов и объектов связанных с ними.