游戏设计中,动作是不可缺少的,Cocos2d-x中所有的动作都继承自Action类,而Action类继承自Ref和Clonable类,整个动作类继承体系如图:
FiniteTimeAction是所有瞬时动作和延时动作的父类,Follow跟随一个节点的动作,Speed改变一个动作的时间。,其中FiniteTimeAction的两个子类以及这两个子类的子类是重点。
瞬时性动作类
<ActionInstant.h>中的类是瞬时动作类,它的子类有:
//... 显示一个节点 setVisible(true) class Show : public ActionInstant { }; //... 隐藏一个节点 setVisible(false) class Hide : public ActionInstant { }; //... 切换节点的可视属性 setVisible(!_target->isVisible()) class ToggleVisibility : public ActionInstant { }; //... 移除自己 class RemoveSelf : public ActionInstant { }; // 水平翻转精灵 class FlipX : public ActionInstant { }; // 垂直翻转精灵 class FlipY : public ActionInstant { }; // 将节点放置到某个位置 class Place : public ActionInstant { }; // 设置动作的回调函数为 std::function<void()> class CallFunc : public ActionInstant { public : static CallFunc * create(const std::function<void()>& func); }; // 设置动作的回调函数为 std::function<void(Node*)> class CallFuncN : public CallFunc { public : static CallFuncN * create(const std::function<void(Node*)>& func); };
延时性动作类
<ActionInterval.h>中的类是瞬时动作类,它的子类有:
// 创建序列动画 class Sequence : public ActionInterval { public : // 这种方式创建序列动画最后需要加nullptr // 比如: Sequence::create(action1, action2, nullptr); static Sequence* create(FiniteTimeAction *action1, ...); // 根据一个动作vector来创建 static Sequence* create(const Vector<FiniteTimeAction*>& arrayOfActions); // 创建两个动作 static Sequence* createWithTwoActions(FiniteTimeAction *actionOne, FiniteTimeAction *actionTwo); // 根据变长动作数组创建序列动作 static Sequence* createWithVariableList(FiniteTimeAction *action1, va_list args); }; // 重复一个动作(一次) class Repeat : public ActionInterval { public : // 创建一个FiniteTimeAction动作 static Repeat* create(FiniteTimeAction *action, unsigned int times); }; // 创建不断重复的动作 class RepeatForever : public ActionInterval { public : // 由一个延时动作ActionInterval而创建 static RepeatForever* create(ActionInterval *action); }; // 创建同时执行的动作 class Spawn : public ActionInterval { public : // 同序列式动作, 最后需要添加nullptr static Spawn* create(FiniteTimeAction *action1, ...); static Spawn* createWithVariableList(FiniteTimeAction *action1, va_list args); static Spawn* create(const Vector<FiniteTimeAction*>& arrayOfActions); static Spawn* createWithTwoActions(FiniteTimeAction *action1, FiniteTimeAction *action2); }; // 旋转动作 旋转到某个角度 class RotateTo : public ActionInterval { }; // 旋转动作 旋转一定角度 class CC_DLL RotateBy : public ActionInterval { }; // 移动一定距离 class MoveBy : public ActionInterval { }; // 移动到某个点 class MoveTo : public MoveBy { }; /*---------- 这个动作By版本继承自To版本 ----------*/ // 使某个倾斜到某个角度 class SkewTo : public ActionInterval { }; // 倾斜一定角度 class SkewBy : public SkewTo { }; // 跳跃一定距离 class JumpBy : public ActionInterval { }; // 跳跃到某个点 class JumpTo : public JumpBy { }; // 贝塞尔曲线 class BezierBy : public ActionInterval { }; // class BezierTo : public BezierBy { }; // 缩放 class ScaleTo : public ActionInterval { }; class ScaleBy : public ScaleTo { }; // 闪烁 class Blink : public ActionInterval { }; // 设置透明度 class FadeTo : public ActionInterval // 淡入 class FadeIn : public FadeTo { }; // 淡出 class FadeOut : public FadeTo { }; // 延时动作 class DelayTime : public ActionInterval { }; // 动画 class Animate : public ActionInterval { };
在所有延时动作里:
- SkewBy继承自SkewTo, ScaleBy继承自ScaleTo
- RotateBy和RotateTo分别继承自ActionInterval
TinkTo和TinkBy分别继承自ActionInterval
- BesizerTo继承自BezierBy
MoveTo继承自MoveBy
JumpTo继承自JumpBy
动作管理
所有的动作执行都由动作管理类ActionManager对象_actionManager来管理动作,_actionManager将所有的动作添加到执行序列中,然后该对象定时刷新自己的update函数,然后调用动作序列中每个动作的step函数,这些step再根据自身的update或结束动作:
假设先添加一个延时性动作:
tihs->runAction(action);
然后由ActionManager来添加到动作队列中:
Action * Node::runAction(Action* action) { CCASSERT( action != nullptr, "Argument must be non-nil"); _actionManager->addAction(action, this, !_running); return action; }
看看addAction发生了什么:
void ActionManager::addAction(Action *action, Node *target, bool paused) { CCASSERT(action != nullptr, ""); CCASSERT(target != nullptr, ""); 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), ""); ccArrayAppendObject(element->actions, action); // 此处将action添加到动作列表里 action->startWithTarget(target); // 然后绑定该动作的执行节点 }
添加了新的动作之后, 在帧刷新时会调用ActionManager的update函数,然后会遍历每个动作并执行相应的step函数:
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 = (Action*)_currentTarget->actions->arr[_currentTarget->actionIndex]; if (_currentTarget->currentAction == nullptr) { continue; } _currentTarget->currentActionSalvaged = false; _currentTarget->currentAction->step(dt); // 对于延时性动作而言,执行ActionInterval::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()) // 动作执行结束后,isDone函数返回true,然后将动作停止并移除 { _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); } } // issue #635 _currentTarget = nullptr; }
对于延时性动作ActionInterval来说, 只有RepeatForever重写了基类ActionInterval的step函数,其他默认使用基类的版本:
void ActionInterval::step(float dt) { if (_firstTick) { _firstTick = false; _elapsed = 0; } else { _elapsed += dt; } // 该调用会遍历每个ActionInterval的子类的update函数,从而执行相应的动作 this->update(MAX (0, // needed for rewind. elapsed could be negative MIN(1, _elapsed / MAX(_duration, FLT_EPSILON) // division by 0 ) ) ); }
在每一帧结束后, ActionMagener的update会根据函数isDone()来检测动作是否完成,如果完成,就执行stop函数将动作停止,并且执行 removeAction函数将动作移除。