От автора перевода
Как вы уже поняли, эта статья — простой перевод официальной документации к движку 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);
Основные действия и как их запустить
Базовые действия, как правило, являются единичными, то есть выполняют единственную задачу.
Давайте разберем несколько примеров:
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);
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);
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);
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);
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);
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
Сглаживает анимацию, чтобы сделать ее плавной. Помните, что сглаженные действия всегда начинаются одинаково и заканчиваются в одно время, независимо от типа сглаживания. Плавные действия — это хороший способ подделать физику в вашей игре!
Здесь представлены общие функции сглаживания отображаемые на графиках:
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 - MySprite->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. Процесс выполнения последовательности выглядит примерно так:
Пример последовательности
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!
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. Эта блок-схема демонстрирует, как это может выглядеть:
// создаем спрайт
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);
// запускаем
mySprite->runAction(sequence);
// запускаем обратно
mySprite->runAction(sequence->reverse());
Что действительно происходит? Если мы выложим шаги в виде списка, это должно помочь:
- mySprite создается
- mySprite получает позицию (50, 56)
- последовательность запускается
- mySprite перемещается на 500 пикселей, его новая позиция — (550, 56)
- последовательность задерживается на 2 секунды
- mySprite увеличивается в 2 раза
- последовательность задерживается еще на 6 секунд
- Мы запускаем обратную последовательность, таким образом, каждое действие выполняется в обратную сторону
- последовательность задерживается на 6 секунд
- mySprite уменьшается в 2 раза
- последовательность задерживается на 2 секунды
- mySprite перемещается на -500 пикселей, его позиция снова (50, 56)
Вы можете видеть, что метод reverse() прост для использования, но не слишком прост в своей внутренней логике. К счастью, Cocos2d-x сделает всю тяжелую работу для вас!
Комментарии (9)
1nt3g3r
16.10.2017 09:55В libGDX тоже есть система Actions, пишутся очень похоже. Например, для перемещения обьекта на 50 пикселей влево за 1 секунду:
actor.addAction(moveBy(50, 0, 1f));
Прочитал статью — практически все Action-ы есть в libGDX. Судя по тому, что поддержка Action в libGDX появилась где-то три года назад, разработчик libGDX вдохновлялся Cocos-2d в этом плане :)
domix32
16.10.2017 15:18Обратите внимание на то, что обе секвенции запускаются на одном и том же спрайте. Любые N секвенций запущеных на одной и той же ноде будут работать ОДНОВРЕМЕННО, поэтому если сделать что-то вроде
sprite->runAction(MoveTo::create(Point(0, 100))); sprite->runAction(MoveTo::create(Point(100, 0)));
Будут работать параллельно и ноду (в данном случае условный спрайт) будет колбасить между этими двумя позициями. Поэтому для ясности все же лучше использовать Spawn.
А clone позволяет дублировать секвенции, чтобы не писать две одинаковые, но иметь независимое влияние/поведение.
CaptainCrocus
16.10.2017 16:42Извините, а если вы об этих Move'a уложите в Spawn, Node (Sprite) колбасить не будет между этими двумя позициями?
domix32
16.10.2017 18:19Будет. Если положить два
MoveTo
, то каждый будет пытаться высчитать новую дельту позиции основываясь на текущем положении. Поэтому финальная позиция вполне может не совпасть ни с одной из указанных точек.
Различие в том что Spawn запускает эти события явно из одного места. Если очень хочется запустить Action на ноде, которая потенциально уже имеет действующий Action, существует проверка на наличиеisRunning
и методы для остановкиstopAction
иstopAllAction
.
Ну и небольшое эмпирическое правило: Не запускать два одинаковых трансформирующих Action'а параллельно (Move, Scale, Skew, Rotate, Fade).CaptainCrocus
16.10.2017 19:21Нет, мы как-то отклонились в сторону, вы писали
Будут работать параллельно и ноду (в данном случае условный спрайт) будет колбасить между этими двумя позициями. Поэтому для ясности все же лучше использовать Spawn.
У вас по тексту получилось, что запускание этого через Spawn имеет преимущества перед отдельным параллельным запуском. Мне кажется, никакого в этом случае, кроме понятной организации кода.
Различие в том что Spawn запускает эти события явно из одного места.
Я думаю, что он их запускает также, как и если бы мы их запускали один за другим, просто все это упаковано в один аккуратный вызов Spawn. Надо глянуть в реализацию.
helg1978
Вопрос по примеру Animate: Cocos2d не поддерживает атласы, или это просто пример такой?
CaptainCrocus
Cocos2d-x поддерживает атласы, при этом предоставляет и другие средства для создания анимации. Подробная инструкция создания атласа для Cocos2d-x в TexturePacker:
domix32
Позволяет. Имеется нескольо различных адаптеров — под Cocosbuilder (deprecated), CocoStudio и еще скелетные анимации Spine