ActionManager通过hash表的方式管理所有需要执行的动作,每一个hash元素存储动作的相关信息。这里有必要看一下hash元素的结构体:
typedef struct _hashElement
{
struct _ccArray *actions; // 精灵需要执行的动作集合。这里可以看出,可以为一个精灵分配多个动作。
Node *target; // 需要执行动作的精灵。
int actionIndex; // 动作的索引。这个是为了removeActionAtIndex()与update()动作的过程中配合使用的,下面ActionManager::update()的关键点总结中细说。
Action *currentAction; // 精灵当前正在执行的动作。
bool currentActionSalvaged; // 精灵当前正在执行的动作是否可以被干掉。
bool paused; // 精灵当前正在执行的动作是否处于暂停状态。
UT_hash_handle hh; // 引擎使用uthash的hash表实现,在使用时每个hash元素中必须包含此项。
} tHashElement;
可以看出,ActionManager是以精灵为单位管理动作的。一个精灵可以执行多个动作,多个执行动作的精灵串联在一起形成hash表,这样ActionManager就可以轻松的管理所有需要执行的动作了。
ActionManager的继承关系如下:

1、成员变量:
protected:
struct _hashElement *_targets; // 所有需要执行动作的精灵的hash集合(我觉得这里就可以理解为一个hash表的表头)。
struct _hashElement *_currentTarget; // 当前正在执行动作的精灵。
bool _currentTargetSalvaged; // 当前正在执行动作的精灵是否可以被干掉。
2、成员方法:
(1) void addAction(Action *action, Node *target, bool paused)
该方法用于将动作加入到精灵的动作数组中,即将动作与精灵绑定。
action:需要执行的动作。
target:需要执行动作的精灵。
paused:动作与精灵绑定后是处于暂停状态还是运行状态。
实例:
mySprite->runAction(MoveBy::create(1, 100));
上面的语句内部调用的是:_actionManager->addAction(action, this, !_running);。action是MoveBy;this是mySprite;!_running是以暂停的模式。
实现源码:
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); // 在所需要执行动作的精灵的hash集合中寻找当前精灵是否在其中,如果找到了通过element返回。
if (! element) // 如果不在其中,下面就需要新创建一个对应于该精灵的hash元素。
{
element = (tHashElement*)calloc(sizeof(*element), 1); // 为hash元素分配空间。
element->paused = paused; // 赋值运行状态。
target->retain(); // 当前精灵被ActionManager使用,需要“保留下来”。这个在分析引擎的引用计数机制的时候再深究。
element->target = target; // 赋值动作的执行者。
HASH_ADD_PTR(_targets, target, element); // 将新创建的hash元素加入到大家庭中。
}
actionAllocWithHashElement(element); // 查看element中的actions数组是否有足够的空间,如果没有则为其分配空间。
CCASSERT(! ccArrayContainsObject(element->actions, action), ""); // 不能重复增加相同的动作。
ccArrayAppendObject(element->actions, action); // 前面都是辅助性工作,这里真正的将动作加入到精灵的动作数组中。
action->startWithTarget(target); // 调用具体动作的startWithTarget(),多态。
}
void ActionManager::actionAllocWithHashElement(tHashElement *element)
{
// 4 actions per Node by default
if (element->actions == nullptr) // 如果未分配过空间。
{
element->actions = ccArrayNew(4); // 则默认为其分配4个单位的空间,可用于存储4个动作。
}else
if (element->actions->num == element->actions->max) // 如果空间不足。
{
ccArrayDoubleCapacity(element->actions); // 则重新分配一个为当前空间2倍大小的空间。
}
}
关键点总结:
♂ 执行动作的精灵下挂着需要执行的动作,ActionManager通过管理执行动作的精灵来管理所有动作。
♂
这个actionAllocWithHashElement(),空间不够就重新分配原先2倍大小的空间,分配空间的上限没有限制?
(2) void ActionManager::removeActionAtIndex(ssize_t index, tHashElement *element)
该方法用于移除精灵需要执行的第index个动作。
index:待移除的动作索引。
element:执行动作的精灵所对应的hash元素。
实例:
很多ActionManager中的移除动作方法底层均是调用的该方法,例如ActionManager::removeAction()、ActionManager::removeActionByTag()、ActionManager::removeAllActionsByTag()。
实现源码:
void ActionManager::removeActionAtIndex(ssize_t index, tHashElement *element)
{
Action *action = (Action*)element->actions->arr; // 找到该精灵需要删除的动作。
if (action == element->currentAction && (! element->currentActionSalvaged)) // 如果待移除的动作为当前精灵正在执行的动作,并且该动作不能被干掉。
{
element->currentAction->retain(); // 则“保留”这个动作。
element->currentActionSalvaged = true; // 并将其标记为(在动作执行结束后)可以被干掉。
}
ccArrayRemoveObjectAtIndex(element->actions, index, true); // 将待移除动作从精灵的动作数组中移除。
// update actionIndex in case we are in tick. looping over the actions
/* 这里是为了和ActionManager::update()配合,保证更新精灵下的每个动作,不遗漏。
* ActionManager::update()更新动作的流程是:
* 从ActionManager::_target中获取第一个需要执行动作的精灵,之后依次遍历出该精灵下挂着的所有待执行动作,每找到一个动作就调用其step()以更新该动作。
* 当该精灵下挂着的所有动作均更新完成后,继续从ActionManager::_target中获取第二个需要执行动作的精灵,更新其下挂着的所有动作,
* 这样直至更新了ActionManager::_target中所有精灵的所有动作。这个遍历每个精灵的过程,就是使用element->actionIndex作为索引的。
-
element->actionIndex是当前正在ActionManager::update()中更新着的动作的索引;index是已经被删除的动作的索引。
* 例如当前element->actionIndex为3,即在ActionManager::update()中正在更新该精灵的第四个动作。此时如果index为2,即想移除该精灵的第三个动作,
* 那么在上面ccArrayRemoveObjectAtIndex()移除动作后,就要在这里对element->actionIndex–。
* 因为在ccArrayRemoveObjectAtIndex()中移除一个动作后,将该动作之后的动作使用memmove()整体向前移动,那么原先第五个动作现在变成了第四个动作,
* 而element->actionIndex如果在ActionManager::update()继续++的话,下一个会更新现在的第五个动作,那么现在的第四个动作就被跳过了,没更新。
* 所以在这种情况下,就要对element->actionIndex–,让索引回退一个,下一个继续更新现在的第四个动作。
* 而如果删除的动作索引在当前更新的动作索引之后,就没有任何影响了,element->actionIndex依次++,直到找不到动作就好了。
* 此处还有个附加的内容,见“关键点总结”中的第二条。
*/
if (element->actionIndex >= index)
{
element->actionIndex–;
}if (element->actions->num == 0) // 如果删除的是该精灵仅有的最后一个动作,那么该精灵也就无需再被管理了,可以被干掉了。 { if (_currentTarget == element) // 如果该精灵还在ActionManager::update()中被更新着动作。 { _currentTargetSalvaged = true; // 则将其标记为(在动作执行结束后)可以被干掉。 } else { deleteHashElement(element); // 从hash大家庭中移除该精灵,没有挂着动作的精灵无需再管理。 } }}
void ActionManager::deleteHashElement(tHashElement *element)
{
ccArrayFree(element->actions); // 将hash元素actions数组所占用空间释放。
HASH_DEL(_targets, element); // 从hash大家族中删除该hash元素。
element->target->release(); // 将该hash元素绑定的精灵释放。
free(element); // 释放该hash所占用的空间。
}
关键点总结:
♂ 还记得上面说到的ActionManager::actionAllocWithHashElement()?ActionManager通过数组的方式管理动作,所以index可以直接当作动作的索引使用。同时,ccArrayRemoveObjectAtIndex()中也可以看到当减少一个动作后,该动作之后的动作使用memmove()整体向前移动,这也是由于ActionManager通过数组的方式管理动作。
♂ 上面实现源码中着重分析的if语句块部分并没有判断当前操纵的element是否是_currentTarget。举个例子,当前正在更新精灵1的动作,此时删除精灵2的某一个动作,在if语句块部分是在判断精灵2的actionIndex是否大于等于精灵2待删除动作的index,此时的判断并没有什么意义。不过这对ActionManager::update()并没有影响,当轮到精灵2开始更新动作时,精灵2的actionIndex会从0开始。
(3) void ActionManager::removeAction(Action *action)
void ActionManager::removeActionByTag(int tag, Node *target)
void ActionManager::removeAllActionsByTag(int tag, Node *target)
此3个方法分别用于移除指定的动作、移除指定的精灵上第一个tag为指定的tag的动作、移除指定的精灵上所有tag为指定的tag的动作。
action:指定的待移除的动作。
tag:指定的待移除的动作的tag。
target:指定的待被移除动作的精灵。
实现源码:
void ActionManager::removeAction(Action *action)
{
// explicit null handling
if (action == nullptr)
{
return;
}
tHashElement *element = nullptr;
Ref *target = action->getOriginalTarget(); // 通过动作获取到绑定的精灵。
HASH_FIND_PTR(_targets, &target, element); // 在hash大家族中找寻该精灵。
if (element)
{
auto i = ccArrayGetIndexOfObject(element->actions, action); // 确定待移除的动作在该精灵的动作数组中的索引。
if (i != CC_INVALID_INDEX) // 如果不是无效索引。其实代表待移除的动作在该精灵的动作数组中找到了。
{
removeActionAtIndex(i, element); // 通过索引移除该精灵的动作。
}
}
else
{
CCLOG("cocos2d: removeAction: Target not found");
}
}
void ActionManager::removeActionByTag(int tag, Node *target)
{
CCASSERT(tag != Action::INVALID_TAG, "");
CCASSERT(target != nullptr, "");
tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element); // 在所需要执行动作的精灵的hash集合中寻找当前精灵是否在其中,如果找到了通过element返回。
if (element) // 如果找到了。
{
auto limit = element->actions->num; // 获取该精灵下一共挂着多少个动作。
for (int i = 0; i < limit; ++i)
{
Action *action = (Action*)element->actions->arr*; // 依次遍历每个动作。
if (action->getTag() == (int)tag && action->getOriginalTarget() == target) // 该动作的tag是否与指定的tag一致,并且该动作所绑定的精灵是否为指定的精灵。
{
removeActionAtIndex(i, element); // 通过索引移除该精灵的动作。
break; // 注意这里跳出了,说明只删除了第一个tag为指定tag的动作。ActionManager::removeAllActionsByTag()中对应的这里会continue。
}
}
}
}
// 大部分与ActionManager::removeActionByTag()相同,只注明不同的部分。
void ActionManager::removeAllActionsByTag(int tag, Node *target)
{
CCASSERT(tag != Action::INVALID_TAG, "");
CCASSERT(target != nullptr, "");
tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element);
if (element)
{
auto limit = element->actions->num;
for (int i = 0; i < limit;)
{
Action *action = (Action*)element->actions->arr*;
if (action->getTag() == (int)tag && action->getOriginalTarget() == target)
{
removeActionAtIndex(i, element);
--limit; // 注意这里将精灵所挂动作的总数减1,之后继续删除tag为指定tag的动作。ActionManager::removeActionByTag()中对应的这里会break。
}
else
{
++i;
}
}
}
}
关键点总结:*
♂ if (action->getTag() == (int)tag && action->getOriginalTarget() == target)这句话感觉第二个限定条件没有什么必要。element是通过target找到的,element中包含target和action,那么action和target是否绑定还需要判定吗?难道在删除的过程中绑定关系有可能被改变?
(4) void removeAllActions()
该方法用于将所有精灵绑定的所有动作都移除。
实现源码:
void ActionManager::removeAllActions()
{
for (tHashElement *element = _targets; element != nullptr; ) // 与下面hh.next配合,循环遍历出ActionManager中记录的所有绑定了动作的精灵。
{
auto target = element->target;
element = (tHashElement*)element->hh.next;
removeAllActionsFromTarget(target); // 将该精灵身上绑定的动作全部移除。
}
}
void ActionManager::removeAllActionsFromTarget(Node *target)
{
// explicit null handling
if (target == nullptr)
{
return;
}
tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element); // 在所需要执行动作的精灵的hash集合中寻找当前精灵是否在其中,如果找到了通过element返回。
if (element) // 如果找到了。
{
// 如果该精灵当前有动作正在运行着,并且当前运行的这个动作不能被干掉。
if (ccArrayContainsObject(element->actions, element->currentAction) && (! element->currentActionSalvaged))
{
element->currentAction->retain(); // 则“保留”这个动作。
element->currentActionSalvaged = true; // 并将其标记为(在动作执行结束后)可以被干掉。
}
ccArrayRemoveAllObjects(element->actions); // 将actions数组中的元素全部释放掉。
if (_currentTarget == element) // 如果当前正在update()着这个精灵的动作。
{
_currentTargetSalvaged = true; // 则将该精灵标记为(在动作执行结束后)可以被干掉。
}
else
{
deleteHashElement(element); // 否则直接从hash表中移除这个精灵。
}
}
else
{
// CCLOG("cocos2d: removeAllActionsFromTarget: Target not found");
}
}
(5) void update(float dt)
该方法用于更新动作。它会调用具体动作的step()方法,当发现动作执行完成后会调用具体动作的stop()方法。
dt:动作的进度。比如10s的动作,当5s的时候调用了update(),此时time应该传入0.5。
实例:
Director::init()中:
_scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false);
Scheduler::scheduleUpdate()的定义:
template <class T>
void scheduleUpdate(T *target, int priority, bool paused)
{
this->schedulePerFrame((float dt){
target->update(dt); // 这里调用的是ActionManager::update()。
}, target, priority, paused);
}
实现源码:
void ActionManager::update(float dt)
{
for (tHashElement *elt = _targets; elt != nullptr; ) // 从hash大家族中逐个遍历出动作执行者。
{
_currentTarget = elt; // 这里不直接使用elt,而使用_currentTarget操作,是为了配合其他移除动作的相关方法,详见它们的分析。
_currentTargetSalvaged = false; // 该动作执行者正在更新着动作,不能被干掉。(其他移除动作的方法在将该动作执行者的所有动作都移除后,会将该标志置为true)
if (! _currentTarget->paused) // 如果当前动作执行者处于非暂停状态。
{
// 在下面的循环中,该动作执行者的actions数组有可能会发生变化(有动作被移除)。
for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num;
_currentTarget->actionIndex++) // 使用actionIndex作为当前更新的动作的索引,为了配合ActionManager::removeActionAtIndex(),详见其分析。
{
_currentTarget->currentAction = (Action*)_currentTarget->actions->arr; // 这个动作即将被更新。
if (_currentTarget->currentAction == nullptr)
{
continue;
}
_currentTarget->currentActionSalvaged = false; // 即将被更新的动作不能被干掉。(当相关移除动作的方法想移除这个动作时,该标志会被置true的)
_currentTarget->currentAction->step(dt); // 这里是更新动作的关键点。调用具体动作的step(),step()再调用具体动作的update()以实现动作的更新。
if (_currentTarget->currentActionSalvaged) // 如果在更新动作的过程中,该动作被某个移除动作方法标记为可以被干掉,则下面己真的把它干掉了 -_-!
{
// 如果动作在更新过程中要被移除,移除方法中会先retain()这个动作。现在step()结束了,可以安全了移除这个动作了。
_currentTarget->currentAction->release();
} else
if (_currentTarget->currentAction->isDone()) // 如果当前动作已经执行完成(不需要再被更新)了。
{
_currentTarget->currentAction->stop(); // Action::_target会被置NULL。
Action *action = _currentTarget->currentAction;
_currentTarget->currentAction = nullptr; // 这里将其置空是为了防止ActionManager::removeActionAtIndex()将该动作再retain()下来。
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; // _currentTarget代表当前正在更新的动作。动作都更新完成了,该成员变量置NULL。
}
关键点总结:
♂ 逐一更新动作的循环中使用_currentTarget->actionIndex作为动作索引是为了配合ActionManager::removeActionAtIndex(),保证删除动作不会导致遗漏需要更新的动作。
♂ fps --> _scheduler->scheduleUpdate() --> ActionManager::update() --> 动作::step() --> 动作::update()。每帧更新动作就是这么个流程,例如每秒24帧,连起来就形成了动画。
(6) Action* getActionByTag(int tag, const Node *target) const
该方法用于在target中找到第一个tag为指定的tag的动作。
tag:待寻找的动作的tag。
target:从该精灵中寻找。
实现源码:
Action* ActionManager::getActionByTag(int tag, const Node *target) const
{
CCASSERT(tag != Action::INVALID_TAG, "");
tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element); // 找到该精灵所对应的hash元素。
if (element)
{
if (element->actions != nullptr)
{
auto limit = element->actions->num;
for (int i = 0; i < limit; ++i)
{
Action *action = (Action*)element->actions->arr*;
if (action->getTag() == (int)tag) // 找到待寻找的动作,返回。
{
return action;
}
}
}
//CCLOG("cocos2d : getActionByTag(tag = %d): Action not found", tag);
}
else
{
// CCLOG("cocos2d : getActionByTag: Target not found");
}
return nullptr;
}
关键点总结:*
♂ 感觉该方法的流程与多数移除动作相关方法中通过tag找动作的流程相似,觉得应该统一起来。
(7) ssize_t getNumberOfRunningActionsInTarget(const Node *target) const
该方法用于获得管理着指定精灵中多少个动作(需要被更新的动作才会被管理,运行完成的动作无需被管理)。
实现源码:
ssize_t ActionManager::getNumberOfRunningActionsInTarget(const Node *target) const
{
tHashElement *element = nullptr;
HASH_FIND_PTR(targets, &target, element); // 从hash大家族中找到精灵所对应的hash元素。
if (element)
{
return element->actions ? element->actions->num : 0; // 如果该精灵的动作数组还存在(这么多并发问题要考虑 --!),返回数组中元素的个数。
}
return 0;
}
关键点总结:
♂ 该方法声明部分上面的注释需要关注:组合起来的动作(例如Sequence)视为1个action。
(8) void pauseTarget(Node *target)
void resumeTarget(Node *target)
这两个方法分别用于暂停和恢复精灵的动作。
target:需要被暂停或者恢复动作的精灵。
实现源码:
void ActionManager::pauseTarget(Node *target)
{
tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element); // 找到该精灵所对应的hash元素。
if (element)
{
element->paused = true; // 置该精灵的暂停标志为true。
}
}
void ActionManager::resumeTarget(Node *target)
{
tHashElement *element = nullptr;
HASH_FIND_PTR(_targets, &target, element); // 找到该精灵所对应的hash元素。
if (element)
{
element->paused = false; // 置该精灵的暂停标志为false。
}
}
(9) Vector<Node*> pauseAllRunningActions()
void resumeTargets(const Vector<Node*>& targetsToResume)
这两个方法分别用于暂停所有精灵的所有运行状态的动作,返回一个容器,容器中存储暂停了哪些动作;这个列表可以传递给resumeTargets()用于恢复这些动作。
实现源码:
Vector<Node*> ActionManager::pauseAllRunningActions()
{
Vector<Node*> idsWithActions; // 存储暂停了哪些动作的容器。
for (tHashElement *element=_targets; element != nullptr; element = (tHashElement *)element->hh.next) // 遍历每个hash元素。
{
if (! element->paused) // 如果处于运行状态。
{
element->paused = true; // 置暂停标志。
idsWithActions.pushBack(element->target); // 将该精灵压入容器中。
}
}
return idsWithActions; // 返回容器。
}
void ActionManager::resumeTargets(const Vector<Node*>& targetsToResume)
{
for(const auto &node : targetsToResume) { // 遍历容器中每个精灵。
this->resumeTarget(node); // 恢复。
}
}