Action
(一)Action
Action
是所有动作的基类。继承关系如下所示:
Action
常用成员函数:class CC_DLL Action : public Ref, public Clonable { public: virtual Action* clone() const { CC_ASSERT(0); return nullptr; } /** Returns a new action that performs the exact reverse of the action. * * @return A new action that performs the exact reverse of the action. * @js NA */ virtual Action* reverse() const { CC_ASSERT(0); return nullptr; } /** Return true if the action has finished. * * @return Is true if the action has finished. */ virtual bool isDone() const; /** Called before the action start. It will also set the target. * * @param target A certain target. */ virtual void startWithTarget(Node *target); /** * Called after the action has finished. It will set the 'target' to nil. * IMPORTANT: You should never call "Action::stop()" manually. Instead, use: "target->stopAction(action);". */ virtual void stop(); /** Called every frame with it's delta time, dt in seconds. DON'T override unless you know what you are doing. * * @param dt In seconds. */ virtual void step(float dt); /** * Called once per frame. time a value between 0 and 1. * For example: * - 0 Means that the action just started. * - 0.5 Means that the action is in the middle. * - 1 Means that the action is over. * * @param time A value between 0 and 1. */ virtual void update(float time); /** Changes the tag that is used to identify the action easily. * * @param tag Used to identify the action easily. */ void setTag(int tag) { _tag = tag; } }
Node
与动作相关的成员函数:/** * Sets the ActionManager object that is used by all actions. * * @warning If you set a new ActionManager, then previously created actions will be removed. * * @param actionManager A ActionManager object that is used by all actions. */ virtual void setActionManager(ActionManager* actionManager); /** * Gets the ActionManager object that is used by all actions. * @see setActionManager(ActionManager*) * @return A ActionManager object. */ virtual ActionManager* getActionManager() { return _actionManager; } virtual const ActionManager* getActionManager() const { return _actionManager; } /** * Executes an action, and returns the action that is executed. * * This node becomes the action's target. Refer to Action::getTarget(). * @warning Actions don't retain their target. * * @param action An Action pointer. */ virtual Action* runAction(Action* action); /** * Stops and removes all actions from the running action list . */ void stopAllActions(); /** * Stops and removes an action from the running action list. * * @param action The action object to be removed. */ void stopAction(Action* action); /** * Removes an action from the running action list by its tag. * * @param tag A tag that indicates the action to be removed. */ void stopActionByTag(int tag); /** * Removes all actions from the running action list by its tag. * * @param tag A tag that indicates the action to be removed. */ void stopAllActionsByTag(int tag);
(二)时间动作
FiniteTimeAction
是以时间相关的动作类,会记录动作执行的持续时间。
class CC_DLL FiniteTimeAction : public Action
{
public:
float getDuration() const { return _duration; }
void setDuration(float duration) { _duration = duration; }
protected:
//! Duration in seconds.
float _duration;
FiniteTimeAction
的子类包括:
-
ActionInstant
:即时动作类。常用子类:
- 水平与垂直翻转:
FlipX
、FlipY
。注:只有e.g.mySprite->runAction(FlipX::create(true)); // 参数表示是否翻转
mySprite->runAction(FlipY::create(true));
- 放置:
Place
,相当于setPosition
。e.g.mySprite->runAction(Place::create(Vec2(100, 200)));
- 隐藏和显示:
Hide
、Show
,相当于setVisible
。e.g.mySprite->runAction(Hide::create());
mySprite->runAction(Show::create());
- 可见切换:
ToggleVisibility
,可见切换为不可见,不可见切换为可见。e.g.mySprite->runAction(ToggleVisibility::create());
- 函数调用动作:
CallFunc
。e.g.auto callFunc = CallFunc::create([](){ log("hello"); }); mySprite->runAction(callFunc);
- 水平与垂直翻转:
-
ActionInterval
:持续动作类。常用子类(带有
To
的方法参数表示动作结果大小(dst),带有By
的方法参数表示动作变化大小(delta)):-
旋转:
RotateTo
、RotateBy
。static RotateTo* create(float duration, float dstAngleX, float dstAngleY); static RotateTo* create(float duration, float dstAngle); static RotateTo* create(float duration, const Vec3& dstAngle3D);
-
移动:
MoveTo
、MoveBy
。static MoveTo* create(float duration, const Vec2& position); static MoveTo* create(float duration, const Vec3& position);
-
跳跃:
JumpTo
、JumpBy
。static JumpTo* create(float duration, const Vec2& position, float height, int jumps); // 第3个参数表示跳跃高度,第4个参数表示跳跃次数
-
贝塞尔曲线运动:
BezierTo
、BezierBy
。贝塞尔曲线的轨迹如下图所示。
static BezierTo* create(float t, const ccBezierConfig& c); typedef struct _ccBezierConfig { //! end position of the bezier Vec2 endPosition; //! Bezier control point 1 Vec2 controlPoint_1; //! Bezier control point 2 Vec2 controlPoint_2; } ccBezierConfig;
-
缩放:
ScaleTo
、RotateBy
。static ScaleTo* create(float duration, float s); static ScaleTo* create(float duration, float sx, float sy); static ScaleTo* create(float duration, float sx, float sy, float sz);
-
倾斜:
SkewTo
、SkewBy
。static SkewTo* create(float t, float sx, float sy);
-
调整大小:
ResizeTo
、ResizeBy
。static ResizeTo* create(float duration, const cocos2d::Size& final_size);
-
淡入淡出:
FadeIn
(从完全透明到不透明)、FadeOut
(从完全不透明到透明)、FadeTo
(转化至指定透明度)。e.g.static FadeIn* create(float d); static FadeOut* create(float d); static FadeTo* create(float duration, uint8_t opacity); // 第二个参数的范围在0~255之间
-
色彩混合:
TintTo
、TintBy
。e.g.static TintTo* create(float duration, uint8_t red, uint8_t green, uint8_t blue); static TintTo* create(float duration, const Color3B& color);
-
闪烁:
Blink
,其实就是来回变更可见性。static Blink* create(float duration, int blinks); // 第2个参数表示闪烁次数
-
延时:
DelayTime
。static DelayTime* create(float d);
-
倒转:
ReverseTime
。static ReverseTime* create(FiniteTimeAction *action);
-
帧动画:
Animate
。e.g.// now lets animate the sprite we moved Vector<SpriteFrame*> animFrames; animFrames.reserve(3); animFrames.pushBack(SpriteFrame::create("frame1.png", Rect(0,0,65,81))); animFrames.pushBack(SpriteFrame::create("frame2.png", Rect(0,0,65,81))); animFrames.pushBack(SpriteFrame::create("frame3.png", Rect(0,0,65,81))); // create the animation out of the frames Animation* animation = Animation::createWithSpriteFrames(animFrames, 0.1f); Animate* animate = Animate::create(animation); // run it and repeat it forever mySprite->runAction(RepeatForever::create(animate));
SpriteFrame::create
方法的第二个参数Rect
记录了图片的原点坐标和宽高。// CCSpriteFrame.cpp SpriteFrame* SpriteFrame::create(const std::string& filename, const Rect& rect) { SpriteFrame *spriteFrame = new (std::nothrow) SpriteFrame(); spriteFrame->initWithTextureFilename(filename, rect); spriteFrame->autorelease(); return spriteFrame; } bool SpriteFrame::initWithTextureFilename(const std::string& filename, const Rect& rect) { Rect rectInPixels = CC_RECT_POINTS_TO_PIXELS( rect ); return initWithTextureFilename(filename, rectInPixels, false, Vec2::ZERO, rectInPixels.size); } // ccMacros.h /** @def CC_RECT_POINTS_TO_PIXELS Converts a rect in points to pixels */ #define CC_RECT_POINTS_TO_PIXELS(__rect_in_points_points__) \ cocos2d::Rect( (__rect_in_points_points__).origin.x * CC_CONTENT_SCALE_FACTOR(), (__rect_in_points_points__).origin.y * CC_CONTENT_SCALE_FACTOR(), \ (__rect_in_points_points__).size.width * CC_CONTENT_SCALE_FACTOR(), (__rect_in_points_points__).size.height * CC_CONTENT_SCALE_FACTOR() )
Animation::createWithSpriteFrames
方法的第二个参数表示延迟,第三个参数表示循环次数。Animation* Animation::createWithSpriteFrames(const Vector<SpriteFrame*>& frames, float delay/* = 0.0f*/, unsigned int loops/* = 1*/) { Animation *animation = new (std::nothrow) Animation(); animation->initWithSpriteFrames(frames, delay, loops); animation->autorelease(); return animation; }
-
Sequence
:序列动作类(串行执行)。static Sequence* create(FiniteTimeAction *action1, ...) CC_REQUIRES_NULL_TERMINATION; static Sequence* create(const Vector<FiniteTimeAction*>& arrayOfActions); static Sequence* createWithVariableList(FiniteTimeAction *action1, va_list args); static Sequence* createWithTwoActions(FiniteTimeAction *actionOne, FiniteTimeAction *actionTwo);
Sequence* Sequence::create(FiniteTimeAction *action1, ...)
方法的参数个数不限,不过要求最后一个参数必须为nullptr
或NULL
。e.g.// create a few actions. auto jump = JumpBy::create(0.5, Vec2(0, 0), 100, 1); auto rotate = RotateTo::create(2.0f, 10); // create a few callbacks auto callbackJump = CallFunc::create([](){ log("Jumped!"); }); auto callbackRotate = CallFunc::create([](){ log("Rotated!"); }); // create a sequence with the actions and callbacks auto seq = Sequence::create(jump, callbackJump, rotate, callbackRotate, nullptr); // run it mySprite->runAction(seq);
-
Spawn
:同步动作类(并行执行)。static Spawn* create(FiniteTimeAction *action1, ...) CC_REQUIRES_NULL_TERMINATION; static Spawn* createWithVariableList(FiniteTimeAction *action1, va_list args); static Spawn* create(const Vector<FiniteTimeAction*>& arrayOfActions); static Spawn* createWithTwoActions(FiniteTimeAction *action1, FiniteTimeAction *action2);
e.g.
auto moveBy = MoveBy::create(10, Vec2(400,100)); auto fadeTo = FadeTo::create(2.0f, 120.0f); // running the above Actions with Spawn. auto mySpawn = Spawn::createWithTwoActions(moveBy, fadeTo); mySprite->runAction(mySpawn);
虽然
mySprite->runAction(mySpawn);
的效果等价于mySprite->runAction(moveBy); mySprite->runAction(fadeTo);
,但Spawn
有一个好处是它可以添加到Sequence
。e.g.// create a few Actions auto moveBy = MoveBy::create(10, Vec2(400,100)); auto fadeTo = FadeTo::create(2.0f, 120.0f); auto scaleBy = ScaleBy::create(2.0f, 3.0f); // create a Spawn to use auto mySpawn = Spawn::createWithTwoActions(scaleBy, fadeTo); // tie everything together in a sequence auto seq = Sequence::create(moveBy, mySpawn, moveBy, nullptr); // run it mySprite->runAction(seq);
-
Repeat
:重复动作。static Repeat* create(FiniteTimeAction *action, unsigned int times);
-
RepeatForever
:无限重复动作。static RepeatForever* create(ActionInterval *action);
-
可变速度类:
ActionEase
为抽象基类。EaseRateAction
:直接根据rate
调节动作速度。static EaseRateAction* create(ActionInterval* action, float rate); // rate=1.0为动作原始速度。数值越小,速度越快;数值越大,速度越慢
EaseIn
:由慢变快;EaseOut
:由快变慢;EaseInOut
:先有慢变快,再由快变慢。命名带有该前后缀的类型都是通过宏定义来声明的:#define EASE_TEMPLATE_DECL_CLASS(CLASSNAME) \ class CC_DLL CLASSNAME : public ActionEase \ { \ CC_CONSTRUCTOR_ACCESS: \ virtual ~CLASSNAME() { } \ CLASSNAME() { } \ public: \ static CLASSNAME* create(ActionInterval* action); \ virtual CLASSNAME* clone() const override; \ virtual void update(float time) override; \ virtual ActionEase* reverse() const override; \ private: \ CC_DISALLOW_COPY_AND_ASSIGN(CLASSNAME); \ }; EASERATE_TEMPLATE_DECL_CLASS(EaseIn); EASERATE_TEMPLATE_DECL_CLASS(EaseOut); EASERATE_TEMPLATE_DECL_CLASS(EaseInOut);
- 至于带有特定变速方程(e.g.
EaseSineIn
,EaseExponentIn
)的动作也不用去记,有需要再查就好。
-
(三)动作克隆 & 序列倒转
在前面提到,一个动作不能多次执行,除非使用动作的克隆。e.g.
// create our Sprites
auto heroSprite = Sprite::create("herosprite.png");
auto enemySprite = Sprite::create("enemysprite.png");
// create an Action
auto moveBy = MoveBy::create(10, Vec2(400,100));
// run it on our hero
heroSprite->runAction(moveBy);
// run it on our enemy
enemySprite->runAction(moveBy->clone());
Sequence
可以通过调用reverse
方法获取倒转的序列。
// create a few Actions
auto moveBy = MoveBy::create(2.0f, Vec2(500,0));
auto scaleBy = ScaleBy::create(2.0f, 2.0f);
auto delay = DelayTime::create(2.0f);
// create a sequence
auto delaySequence = Sequence::create(delay, delay->clone(), delay->clone(),
delay->clone(), nullptr);
auto sequence = Sequence::create(moveBy, delay, scaleBy, delaySequence, nullptr);
// run it
mySprite->runAction(sequence);
// reverse it
mySprite->runAction(sequence->reverse());
sequence
:移动2s – 暂停2s – 伸缩2s – 暂停2s – 暂停2s – 暂停2s – 暂停2s;sequence->reverse()
:暂停2s – 暂停2s – 暂停2s – 暂停2s – 伸缩2s – 暂停2s – 移动2s。
(四)速度类 & 跟随动作类
Speed
:用于设置动作的速度。class CC_DLL Speed : public Action { public: static Speed* create(ActionInterval* action, float speed); // rate=1.0表示正常速度;rate=2.0表示速度加倍;rate=0.5表示速度减半 float getSpeed() const { return _speed; } void setSpeed(float speed) { _speed = speed; } }
Follow
:用于跟随另一个节点做同步运动。class CC_DLL Follow : public Action { public: /** * Creates the action with a set boundary or with no boundary. * * @param followedNode The node to be followed. * @param rect The boundary. If \p rect is equal to Rect::ZERO, it'll work * with no boundary. */ static Follow* create(Node *followedNode, const Rect& rect = Rect::ZERO); /** * Creates the action with a set boundary or with no boundary with offsets. * * @param followedNode The node to be followed. * @param rect The boundary. If \p rect is equal to Rect::ZERO, it'll work * with no boundary. * @param xOffset The horizontal offset from the center of the screen from which the * node is to be followed.It can be positive,negative or zero.If * set to zero the node will be horizontally centered followed. * @param yOffset The vertical offset from the center of the screen from which the * node is to be followed.It can be positive,negative or zero. * If set to zero the node will be vertically centered followed. * If both xOffset and yOffset are set to zero,then the node will be horizontally and vertically centered followed. */ static Follow* createWithOffset(Node* followedNode,float xOffset,float yOffset,const Rect& rect = Rect::ZERO); /** Return boundarySet. * * @return Return boundarySet. */ bool isBoundarySet() const { return _boundarySet; } /** Alter behavior - turn on/off boundary. * * @param value Turn on/off boundary. */ void setBoundarySet(bool value) { _boundarySet = value; } }
(五)CocosStudio编辑动画
可以参考博客:使用cocos studio制作动画 | Metoor
e.g. 增加了两个动画animation1
和animation2
。animation1
对Button
节点进行旋转和淡入淡出;animation2
对Button
节点进行伸缩变换。
保存过后的csd为:
<GameFile>
<PropertyGroup Name="MainScene" Type="Scene" ID="a2ee0952-26b5-49ae-8bf9-4f1d6279b798" Version="3.10.0.0" />
<Content ctype="GameProjectContent">
<Content>
<Animation Duration="30" Speed="1.0000">
<Timeline ActionTag="-509134901" Property="RotationSkew">
<ScaleFrame FrameIndex="0" X="0.0000" Y="0.0000">
<EasingData Type="0" />
</ScaleFrame>
<ScaleFrame FrameIndex="5" X="90.0000" Y="90.0000">
<EasingData Type="0" />
</ScaleFrame>
<ScaleFrame FrameIndex="10" X="180.0000" Y="180.0000">
<EasingData Type="0" />
</ScaleFrame>
<ScaleFrame FrameIndex="15" X="270.0000" Y="270.0000">
<EasingData Type="0" />
</ScaleFrame>
<ScaleFrame FrameIndex="20" X="360.0000" Y="360.0000">
<EasingData Type="0" />
</ScaleFrame>
</Timeline>
<Timeline ActionTag="-509134901" Property="Scale">
<ScaleFrame FrameIndex="20" X="4.0000" Y="3.0000">
<EasingData Type="0" />
</ScaleFrame>
<ScaleFrame FrameIndex="25" X="2.0000" Y="1.5000">
<EasingData Type="0" />
</ScaleFrame>
<ScaleFrame FrameIndex="30" X="4.0000" Y="3.0000">
<EasingData Type="0" />
</ScaleFrame>
</Timeline>
<Timeline ActionTag="-509134901" Property="Alpha">
<IntFrame FrameIndex="0" Value="255">
<EasingData Type="0" />
</IntFrame>
<IntFrame FrameIndex="5" Value="127">
<EasingData Type="0" />
</IntFrame>
<IntFrame FrameIndex="10" Value="0">
<EasingData Type="0" />
</IntFrame>
<IntFrame FrameIndex="15" Value="127">
<EasingData Type="0" />
</IntFrame>
<IntFrame FrameIndex="20" Value="255">
<EasingData Type="0" />
</IntFrame>
</Timeline>
</Animation>
<AnimationList>
<AnimationInfo Name="animation0" StartIndex="0" EndIndex="20">
<RenderColor A="150" R="220" G="20" B="60" />
</AnimationInfo>
<AnimationInfo Name="animation1" StartIndex="20" EndIndex="30">
<RenderColor A="150" R="65" G="105" B="225" />
</AnimationInfo>
</AnimationList>
<ObjectData Name="Scene" ctype="GameNodeObjectData">
<Children>
<AbstractNodeData Name="Default" ActionTag="953446860">
</AbstractNodeData>
<AbstractNodeData Name="Button_1" ActionTag="-509134901">
</AbstractNodeData>
</Children>
</ObjectData>
</Content>
</Content>
</GameFile>
AnimationList
节点记录了动画的起始索引StartIndex
、终止索引EndIndex
和渲染颜色RenderColor
(即图中的红色和蓝色)。Animation
节点记录了各个动作的时间线TimeLine
(对应cocos2d-x中的ActionTimeLine
类型),而TimeLine
记录了动作标记ActionTag
(和执行动作的节点相关联)、动作属性Property
(动作修改了节点的哪些属性)以及关键帧IntFrame
。
通过csb文件加载并执行动作:
#include "cocostudio/CocoStudio.h"
using namespace cocostudio::timeline;
auto rootNode = CSLoader::createNode("MainScene.csb");
addChild(rootNode);
ActionTimeline* action = CSLoader::createTimeline("MainScene.csb");
if (action)
{
rootNode->runAction(action);
action->gotoFrameAndPlay(0, false); // 第2个参数表示是否循环,默认值为true。如果动作只需执行一次,则需要传递参数false
}
ActionTimeLine
重写了isDone
方法永远返回false
(virtual bool isDone() const override { return false; }
),即动画执行结束后并不会被release
。
(六)动作执行流程
- 节点添加动画
// CCNode.cpp Action * Node::runAction(Action* action) { CCASSERT( action != nullptr, "Argument must be non-nil"); _actionManager->addAction(action, this, !_running); return action; } // CCActionManager.cpp void ActionManager::addAction(Action *action, Node *target, bool paused) { if(action == nullptr || target == nullptr) return; tHashElement *element = nullptr; // we should convert it to Ref*, because we save it as Ref* Ref *tmp = target; HASH_FIND_PTR(_targets, &tmp, element); if (! element) { element = (tHashElement*)calloc(sizeof(*element), 1); element->paused = paused; target->retain(); element->target = target; HASH_ADD_PTR(_targets, target, element); } actionAllocWithHashElement(element); CCASSERT(! ccArrayContainsObject(element->actions, action), "action already be added!"); ccArrayAppendObject(element->actions, action); action->startWithTarget(target); // 动作绑定目标节点 }
- director定时访问
_actionMananger
// CCDirector.cpp bool Director::init() { // action manager _actionManager = new (std::nothrow) ActionManager(); _scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false); } // CCScheduler.h template <class T> void scheduleUpdate(T *target, int priority, bool paused) { this->schedulePerFrame([target](float dt){ target->update(dt); }, target, priority, paused); }
- 遍历所有动作并执行其
step
方法,step
方法中一般会调用update
方法来执行动画的实际逻辑;如果动画执行结束(isDone()
)则调用stop
方法,最终动作会被release
掉(ActionTimeLine
除外)。// main loop void ActionManager::update(float dt) { for (tHashElement *elt = _targets; elt != nullptr; ) { _currentTarget = elt; _currentTargetSalvaged = false; if (! _currentTarget->paused) { // The 'actions' MutableArray may change while inside this loop. for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num; _currentTarget->actionIndex++) { _currentTarget->currentAction = static_cast<Action*>(_currentTarget->actions->arr[_currentTarget->actionIndex]); if (_currentTarget->currentAction == nullptr) { continue; } _currentTarget->currentActionSalvaged = false; _currentTarget->currentAction->step(dt); if (_currentTarget->currentActionSalvaged) { // The currentAction told the node to remove it. To prevent the action from // accidentally deallocating itself before finishing its step, we retained // it. Now that step is done, it's safe to release it. _currentTarget->currentAction->release(); } else if (_currentTarget->currentAction->isDone()) { _currentTarget->currentAction->stop(); Action *action = _currentTarget->currentAction; // Make currentAction nil to prevent removeAction from salvaging it. _currentTarget->currentAction = nullptr; removeAction(action); } _currentTarget->currentAction = nullptr; } } // elt, at this moment, is still valid // so it is safe to ask this here (issue #490) elt = (tHashElement*)(elt->hh.next); // only delete currentTarget if no actions were scheduled during the cycle (issue #481) if (_currentTargetSalvaged && _currentTarget->actions->num == 0) { deleteHashElement(_currentTarget); } //if some node reference 'target', it's reference count >= 2 (issues #14050) else if (_currentTarget->target->getReferenceCount() == 1) { deleteHashElement(_currentTarget); } } // issue #635 _currentTarget = nullptr; }
- 基类
Action
的step
方法和update
方法需要子类来重写。// CCAction.cpp void Action::step(float /*dt*/) { CCLOG("[Action step]. override me"); } void Action::update(float /*time*/) { CCLOG("[Action update]. override me"); } // CCActionInstant.cpp void ActionInstant::step(float /*dt*/) { float updateDt = 1; update(updateDt); } void ActionInstant::update(float /*time*/) { _done = true; } // CCActionInterval.cpp void ActionInterval::step(float dt) { if (_firstTick) { _firstTick = false; _elapsed = 0; } else { _elapsed += dt; } float updateDt = std::max(0.0f, // needed for rewind. elapsed could be negative std::min(1.0f, _elapsed / _duration) ); if (sendUpdateEventToScript(updateDt, this)) return; this->update(updateDt); // updateDt是一个比例,范围在0~1 _done = _elapsed >= _duration; }
Place
派生类重写的update
方法:void Place::update(float time) { ActionInstant::update(time); _target->setPosition(_position); }
MoveBy
派生类重写的update
方法:void MoveBy::update(float t) { if (_target) { #if CC_ENABLE_STACKABLE_ACTIONS // 如果支持动作叠加,那么_startPosition在其他动作的影响下可能会发生变化,所以需要对当前位置和MoveBy记录的_previousPosition做diff,然后校正MoveBy的_startPosition Vec3 currentPos = _target->getPosition3D(); Vec3 diff = currentPos - _previousPosition; _startPosition = _startPosition + diff; Vec3 newPos = _startPosition + (_positionDelta * t); _target->setPosition3D(newPos); _previousPosition = newPos; #else _target->setPosition3D(_startPosition + _positionDelta * t); #endif // CC_ENABLE_STACKABLE_ACTIONS } }
【参考资料】
[1] Cocos2D-X游戏开发技术精解 - 第4章 动作功能
[2] 动作(Action) · GitBook
[3] 使用cocos studio制作动画 | Metoor
[4] Cocos2dx源码赏析(4)之Action动作