Cocos2d-x — Обработка действий

в 16:32, , рубрики: c++, cocos2d-x, разработка игр

От автора перевода

Как вы уже поняли, эта статья — простой перевод официальной документации к движку Cocos2d-x. Если вы здесь впервые, можете глянуть предыдущую статью!

ВНИМАНИЕ: Самое сложное при переводе технических документаций — это не сам перевод, а адаптация текста для русскоязычных читателей. Многие термины могут вовсе не переводиться на русский. Я выкручивался из таких ситуаций как мог. На суть и основные мысли данной статьи это никак не повлияло, но возможны некоторые неточности. Я заранее извиняюсь и прошу вас сообщать мне о подобных ошибках, при их обнаружении. Буду очень благодарен!

Действия

Объекты Action — это то, чем они называются. Действия позволяют изменять свойства объектов Node во времени. Любые объекты базового класса Node могут иметь действия, выполняющиеся в них. В качестве примера, вы можете двигать спрайт из одной позиции в другую, и делать это постепенно в течении промежутка времени.

Примеры действий:

// Двигает спрайт в позицию (50,10) за 2 секунды.
auto moveTo = MoveTo::create(2, Vec2(50, 10));
mySprite1->runAction(moveTo);

// Двигает спрайт на 20 точек вправо за 2 секунды
auto moveBy = MoveBy::create(2, Vec2(20,0));
mySprite2->runAction(moveBy);

«By» и «To», в чем различия?

Вы заметите, что каждое действие имеет By и To версии. Зачем это нужно? Дело в том, что они отличаются по принципу своей работы. By выполняются относительно текущего состояния узла. Действия To не зависят от текущих параметров узла. Давайте взглянем на конкретный пример:

auto mySprite = Sprite::create("mysprite.png");
mySprite->setPosition(Vec2(200, 256));

// MoveBy - позволяет переместить спрайт на 500 пикселей по оси x за 2 секунды.
// Если x = 200, то после перемещения x = 200 + 500 = 700
auto moveBy = MoveBy::create(2, Vec2(500, 0));

// MoveTo - позволяет переместить спрайт в точку (300, 256)
// Спрайт получает новую позицию (300, 256) не зависимо от того,
// где он находился до этого.
auto moveTo = MoveTo::create(2, Vec2(300, mySprite->getPositionY()));

// Delay - создает небольшую задержку
auto delay = DelayTime::create(1);

auto seq = Sequence::create(moveBy, delay, moveTo, nullptr);

mySprite->runAction(seq);

image

Основные действия и как их запустить

Базовые действия, как правило, являются единичными, то есть выполняют единственную задачу.

Давайте разберем несколько примеров:

Move

Перемещает узел за заданный период времени.

auto mySprite = Sprite::create("mysprite.png");

// Двигает спрайт в заданную локацию за 2 секунды.
auto moveTo = MoveTo::create(2, Vec2(50, 0));

mySprite->runAction(moveTo);

// Двигает спрайт на 50 пикселей вправо и 0 пикселей вверх за 2 секунды.
auto moveBy = MoveBy::create(2, Vec2(50, 0));

mySprite->runAction(moveBy);

image

Rotate

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

auto mySprite = Sprite::create("mysprite.png");

// Поворачивает узел до заданного угла за 2 секунды
auto rotateTo = RotateTo::create(2.0f, 40.0f);
mySprite->runAction(rotateTo);

// Поворачивает узел на 40 градусов, за 2 секунды
auto rotateBy = RotateBy::create(2.0f, 40.0f);
mySprite->runAction(rotateBy);

image

Scale

Масштабирует узел, с течением времени.

auto mySprite = Sprite::create("mysprite.png");

// Увеличивает спрайт в 3 раза относительно текущего размера
// за 2 секунды
auto scaleBy = ScaleBy::create(2.0f, 3.0f);
mySprite->runAction(scaleBy);

// Тоже, что предыдущий вызов
auto scaleBy = ScaleBy::create(2.0f, 3.0f, 3.0f);
mySprite->runAction(scaleBy);

// Увеличивает масштаб в 3 раза
auto scaleTo = ScaleTo::create(2.0f, 3.0f);
mySprite->runAction(scaleTo);

// Тоже, что предыдущий вызов
auto scaleTo = ScaleTo::create(2.0f, 3.0f, 3.0f);
mySprite->runAction(scaleTo);

image

Fade In/Out

Постепенно делает узел видимым/невидимым.

FadeIn изменяет непрозрачность от 0 до 255. Обратное действие — FadeOut

auto mySprite = Sprite::create("mysprite.png");

// Делает спрайт видимым за 1 секунду
auto fadeIn = FadeIn::create(1.0f);
mySprite->runAction(fadeIn);

// Делает спрайт невидимым за 2 секунды
auto fadeOut = FadeOut::create(2.0f);
mySprite->runAction(fadeOut);

image

Tint

Изменяет цветовой тон узла, с текущего на заданный.

auto mySprite = Sprite::create("mysprite.png");

// окрашивает узел в заданный RGB цвет
auto tintTo = TintTo::create(2.0f, 120.0f, 232.0f, 254.0f);
mySprite->runAction(tintTo);

// складывает текущий цвет с заданным
auto tintBy = TintBy::create(2.0f, 120.0f, 232.0f, 254.0f);
mySprite->runAction(tintBy);

image

Animate

Animate позволяет реализовать покадровую анимацию для вашего спрайта. Это просто замена отображаемого кадра с установленными интервалами продолжительности анимации. Давайте рассмотрим такой пример:

auto mySprite = Sprite::create("mysprite.png");

// теперь давайте анимируем спрайт
Vector<SpriteFrame*> animFrames;
animFrames.reserve(12);
animFrames.pushBack(SpriteFrame::create("Blue_Front1.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Front2.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Front3.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Left1.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Left2.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Left3.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Back1.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Back2.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Back3.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Right1.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Right2.png", Rect(0,0,65,81)));
animFrames.pushBack(SpriteFrame::create("Blue_Right3.png", Rect(0,0,65,81)));

// создает анимацию из кадров
Animation* animation = Animation::createWithSpriteFrames(animFrames, 0.1f);
Animate* animate = Animate::create(animation);

// запускаем анимацию и повторяем ее бесконечно
mySprite->runAction(RepeatForever::create(animate));

Трудно продемонстрировать эффект анимации в тексте, поэтому попробуйте сами протестировать ее, чтобы увидеть как она работает!

Easing

Сглаживает анимацию, чтобы сделать ее плавной. Помните, что сглаженные действия всегда начинаются одинаково и заканчиваются в одно время, независимо от типа сглаживания. Плавные действия — это хороший способ подделать физику в вашей игре!

Здесь представлены общие функции сглаживания отображаемые на графиках:

image

Cocos2d-x поддерживает большинство функций сглаживания из вышеуказанных. Они также просты для реализации. Давайте посмотрим на конкретный случай использования. Давайте сбросим спрайт с верхней части экрана и симулируем его отскок.

// создание спрайта
auto mySprite = Sprite::create("mysprite.png");

auto dir = Director::getInstance();
mySprite->setPosition(Vec2(dir->getVisibleSize().width / 2, mySprite->getContentSize().height / 2));

// создание действия MoveBy в то место, от куда мы сбросим спрайт
auto move = MoveBy::create(2, Vec2(0, dir->getVisibleSize().height - newSprite2 >getContentSize().height));
auto move_back = move->reverse();

// создаем действие - падение
auto move_ease_back = EaseBounceOut::create(move_back->clone());

// создает задержку, которая запускается между действиями
auto delay = DelayTime::create(0.25f);

// создаем последовательность действий в том порядке,
// в котором мы хотим их запустить
auto seq1 = Sequence::create(move, delay, move_ease_back, nullptr);

// создает последовательность и повторяет бесконечно
mySprite->runAction(RepeatForever::create(seq1));

Протестируйте этот код, чтобы увидеть его в действии!

Последовательности и как их запустить

Последовательности — это серии действий, которые выполняются последовательно. Это может быть любое количество действий, функций или даже других последовательностей. Функций? Да! Cocos2d-x содержит объект CallFunc, который позволяет вам создавать функции и выполнять их в вашей последовательности. Это позволяет вам добавить ваши собственные функции в последовательности, помимо простых действий, предоставленных Cocos2d-x. Процесс выполнения последовательности выглядит примерно так:

image

Пример последовательности

auto mySprite = Sprite::create("mysprite.png");

// создаем несколько действий
auto jump = JumpBy::create(0.5, Vec2(0, 0), 100, 1);

auto rotate = RotateTo::create(2.0f, 10);

// создаем несколько callbacks
auto callbackJump = CallFunc::create([](){
    log("Jumped!");
});

auto callbackRotate = CallFunc::create([](){
    log("Rotated!");
});

// создаем последовательность с действиями и callbacks
auto seq = Sequence::create(jump, callbackJump, rotate, callbackRotate, nullptr);

// запускаем их
mySprite->runAction(seq);

Итак, что же делает действие Sequence?

Оно будет исполнять следующие действия последовательно:

Jump -> callbackJump() -> Rotate -> callbackRotate()

Попробуйте сами протестировать эту последовательность!

Spawn

Spawn очень похож на Sequence, единственное различие в том, что он выполняет все действия одновременно. У вас может быть любое количество действий или даже другой объект Spawn!

image

Spawn дает тот же результат, что множество последовательных вызовов runAction(). Однако, польза от использования Spawn в том, что вы можете включить его в последовательность, что бы достичь особых эффектов, которые невозможно получить иным способом. Комбинация Spawn и Sequence — очень мощный инструмент.

Пример:

// создание двух действий и запуск в Spawn
auto mySprite = Sprite::create("mysprite.png");

auto moveBy = MoveBy::create(10, Vec2(400,100));
auto fadeTo = FadeTo::create(2.0f, 120.0f);

Использование Spawn:

// Запуск вышеописанных действий в Spawn.
auto mySpawn = Spawn::createWithTwoActions(moveBy, fadeTo);
mySprite->runAction(mySpawn);

и последовательный вызов runAction():

// запуск обоих действий последовательно
mySprite->runAction(moveBy);
mySprite->runAction(fadeTo);

Оба эти способа дают один результат. Однако, Cocos2d-x позволяет также использовать Spawn в Sequence. Эта блок-схема демонстрирует, как это может выглядеть:

image

// создаем спрайт
auto mySprite = Sprite::create("mysprite.png");

// создаем несколько действий
auto moveBy = MoveBy::create(10, Vec2(400,100));
auto fadeTo = FadeTo::create(2.0f, 120.0f);
auto scaleBy = ScaleBy::create(2.0f, 3.0f);

// создаем Spawn
auto mySpawn = Spawn::createWithTwoActions(scaleBy, fadeTo);

// объединяем все в последовательность
auto seq = Sequence::create(moveBy, mySpawn, moveBy, nullptr);

// запускаем
mySprite->runAction(seq);

Запустите этот код, чтобы увидеть его в действии!

Clone

Clone в точности соответствует своему названию. Если у вас есть действие, вы можете применить его ко множеству узлов, используя clone(). Зачем вам клонировать? Хороший вопрос. Action объекты имеют внутренние состояние. Когда они запускаются, то фактически изменяют свойства узла. Без использования clone(), вы не будете иметь уникального действия, примененного к узлу. Это приведет к непредвиденным результатам, так как вы точно не знаете какие значения параметров действия в настоящие время установлены.

Давайте посмотрим пример, скажем у вас есть heroSprite и он имеет позицию (0, 0). Если вы запустите действие:

MoveBy::create(10, Vec2(400,100));

Это заставит двигаться спрайт из точки (0, 0) в точку (400, 100), и он преодолеет это расстояние за 10 секунд. Теперь heroSprite находится в позиции (400, 100) и, что более важно, действие держит эту позицию во внутреннем состоянии. Теперь, допустим у вас есть enemySprite в позиции (200, 200). Если вы примените к нему тоже самое:

MoveBy::create(10, Vec2(400,100));

… к вашему enemySprite, действие закончится в положении (800, 200), а не там где вы задумывали. Понимаете почему? Это связано с тем, что действие больше не имеет начального состояния, после первого запуска MoveBy. Клонирование действий предотвращает это. Оно гарантирует вам получение уникальных действий, применяемых к узлам.

Давайте также посмотрим пример некорректного кода:

// создаем наши спрайты
auto heroSprite = Sprite::create("herosprite.png");
auto enemySprite = Sprite::create("enemysprite.png");

// создаем действие
auto moveBy = MoveBy::create(10, Vec2(400,100));

// применяем его к нашему heroSprite
heroSprite->runAction(moveBy);

// также применяем его к enemySprite
enemySprite->runAction(moveBy); // Ой, действие уже не работает!
// действия используют свое состояние как начальную позицию.

Корректный код с использование clone():

// создаем наши спрайты
auto heroSprite = Sprite::create("herosprite.png");
auto enemySprite = Sprite::create("enemysprite.png");

// создаем действие
auto moveBy = MoveBy::create(10, Vec2(400,100));

// применяем его к нашему heroSprite
heroSprite->runAction(moveBy);

// также применяем его к enemySprite
enemySprite->runAction(moveBy->clone()); // Верно! Это действие уникально

Reverse

Разворачивает действия в обратную сторону. Если вы запускаете серию действий, вы можете вызвать для них reverse(), что запустит их в обратном порядке. Однако это не просто запуск действий в обратную сторону. Вызов reverse() — это фактически управление параметрами оригинального объекта Sequence или Spawn, в обратном порядке.

Пример вызова Spawn в обратном порядке:

// разворачиваем действие в обратную сторону
mySprite->runAction(mySpawn->reverse());

Большинство действий или последовательностей могут быть запущены в обратном порядке!

Это легко использовать, но давайте посмотрим, что происходит. Дано:

// создаем спрайт
auto mySprite = Sprite::create("mysprite.png");
mySprite->setPosition(50, 56);

// создаем несколько действий
auto moveBy = MoveBy::create(2.0f, Vec2(500,0));
auto scaleBy = ScaleBy::create(2.0f, 2.0f);
auto delay = DelayTime::create(2.0f);

// создаем последовательность
auto delaySequence = Sequence::create(delay, delay->clone(), delay->clone(),
delay->clone(), nullptr);

auto sequence = Sequence::create(moveBy, delay, scaleBy, delaySequence, nullptr);

// запускаем
newSprite2->runAction(sequence);

// запускаем обратно
newSprite2->runAction(sequence->reverse());

Что действительно происходит? Если мы выложим шаги в виде списка, это должно помочь:

  • mySprite создается
  • mySprite получает позицию (50, 56)
  • последовательность запускается
  • mySprite перемещается на 500 пикселей, его новая позиция — (550, 56)
  • последовательность задерживается на 2 секунды
  • mySprite увеличивается в 2 раза
  • последовательность задерживается еще на 6 секунд
  • Мы запускаем обратную последовательность, таким образом, каждое действие выполняется в обратную сторону
  • mySprite создается
  • последовательность задерживается на 6 секунд
  • mySprite уменьшается в 2 раза
  • последовательность задерживается на 2 секунды
  • mySprite перемещается на -500 пикселей, его позиция снова (50, 56)

Вы можете видеть, что метод reverse() прост для использования, но не слишком прост в своей внутренней логике. К счастью, Cocos2d-x сделает всю тяжелую работу для вас!

Автор: MrMysterious

Источник

Поделиться

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