您的位置:首页 > 移动开发 > Cocos引擎

Cocos2d-x学习笔记(七)CCAction原理分析

2014-08-26 10:26 459 查看
原创文章,转载请注明出处:/article/8316764.html

前言

上一讲学习笔记,我们学习了CCAction,了解了各种CCAction的子类,心跳的效果虽然简单,但是却能让人好奇,它具体是怎么实现,这一讲我们将对此进行揭秘,方法很简单,从runAction方法调用开始。

CCNode::runAction

CCAction * CCNode::runAction(CCAction* action)
{
CCAssert( action != NULL, "Argument must be non-nil");
m_pActionManager->addAction(action, this, !m_bRunning);
return action;
}
runAction的结构很简单,一个断言,确保动作(action)不为空,紧接着调用了ActionManager的addAction方法,传递action、target(执行动作节点)以及m_bRunning(节点当前是否running)

CCActionManager::addAction

void CCActionManager::addAction(CCAction *pAction, CCNode *pTarget, bool paused)
{
CCAssert(pAction != NULL, ""); // 确保动作和节点不为空
CCAssert(pTarget != NULL, "");

tHashElement *pElement = NULL; // 创建一个Hash列表元素,并置为空
CCObject *tmp = pTarget;
HASH_FIND_INT(m_pTargets, &tmp, pElement); // 通过tmp找到对应的哈希表项返回给pElement
if (! pElement) // 如果没有找到该哈希表项
{
pElement = (tHashElement*)calloc(sizeof(*pElement), 1); // 为pElement分配空间
pElement->paused = paused; // 将pTarget的运行状态赋给pElement的paused
pTarget->retain(); // 增加pTarget引用计数
pElement->target = pTarget; // 将pTarget赋给pElement的target
HASH_ADD_INT(m_pTargets, target, pElement); // 添加pElement至哈希表中
}

actionAllocWithHashElement(pElement); // 分配动作空间

CCAssert(! ccArrayContainsObject(pElement->actions, pAction), ""); //判断action是否在element的动作集中。确保只放入一次。
ccArrayAppendObject(pElement->actions, pAction); // 将action添加到pElement的动作集中

pAction->startWithTarget(pTarget); // 给动作设置执行的节点
}
addAction比runAction稍显复杂但是逐行代码来看,也并不复杂,主要是将动作添加到节点对应的哈希表项中去,如果不存在对应哈希表项,则新建一个。actionAllocWithHashElement函数确保了action有存储空间,随后将pAction存入哈希表项中。一切ok之后再调用pAction的startWithTarget方法,设置执行动作的节点

CCActionManager::actionAllocWithHashElement

void CCActionManager::actionAllocWithHashElement(tHashElement *pElement)
{
if (pElement->actions == NULL)
{// 判断pElement的actions是否为空白,如果为空则分配4个空间
pElement->actions = ccArrayNew(4);
}
else if (pElement->actions->num == pElement->actions->max)
{// 如果pElement的容量已满,则将pElement的容量翻倍
ccArrayDoubleCapacity(pElement->actions);
}
}


CCActionManager::update

讲到这,一切已经妥当,我们开始讲最为核心的update方法,这个方法才是真正使动作“动”起来的原因。首先,这个update方法隶属于CCActionManager。
据有关资料介绍,它是在CCDisplayLinkDirector的mainLoop->drawScene里调用m_pScheduler->update(m_fDeltaTime)中被调用的。
m_pScheduler是CCSchedule类型,他负责将系统全部子系统更新,包括Touch、Action、脚本、定时器等等,Action是在优先级<0的那个list里面的。日后有机会将仔细研究,我们继续回到CCActionManager的update方法。
void CCActionManager::update(float dt)
{
for (tHashElement *elt = m_pTargets; elt != NULL; ) // 遍历存储节点的哈希表
{
m_pCurrentTarget = elt; // 将当前哈希表项保存至m_pCurrentTarget变量中
m_bCurrentTargetSalvaged = false; // 将当前节点的回收标记置为否

if (! m_pCurrentTarget->paused) // 如果该项处于暂停状态
{
for (m_pCurrentTarget->actionIndex = 0; m_pCurrentTarget->actionIndex < m_pCurrentTarget->actions->num; m_pCurrentTarget->actionIndex++)
{ // 遍历当前节点所有动作
m_pCurrentTarget->currentAction = (CCAction*)m_pCurrentTarget->actions->arr[m_pCurrentTarget->actionIndex]; // 将遍历到的动作保存到m_pCurrentTarget->currentAction中
if (m_pCurrentTarget->currentAction == NULL)
{ // 如果遍历项动作为空则跳过
continue;
}
m_pCurrentTarget->currentActionSalvaged = false; //设置回收标记为否
m_pCurrentTarget->currentAction->step(dt); // 更新当前动作的播放
if (m_pCurrentTarget->currentActionSalvaged) // 如果当前动作的回收标记为是,则进行回收处理
{
m_pCurrentTarget->currentAction->release();
}
else if (m_pCurrentTarget->currentAction->isDone()) // 否则判断当前节点的当前动作是否播放结束
{
m_pCurrentTarget->currentAction->stop(); // 停止动作

CCAction *pAction = m_pCurrentTarget->currentAction; // 为了在removeAction中正确释放动作,这里先创建一个临时变量pAction记录一下要释放的动作
m_pCurrentTarget->currentAction = NULL; // 在removeAction之前将当前哈希表项中的当前动作设为NULL,否则不能释放
removeAction(pAction); // 释放临时存储的动作
}
m_pCurrentTarget->currentAction = NULL;
}
}

// 将elt指向下一个
elt = (tHashElement*)(elt->hh.next);

// 如果当前哈希表项处于回收状态且其动作集为空,删除此哈希表项
if (m_bCurrentTargetSalvaged && m_pCurrentTarget->actions->num == 0)
{
deleteHashElement(m_pCurrentTarget);
}
}

// 将变量m_pCurrentTarge置空
m_pCurrentTarget = NULL;
}
其实update的代码很简单,就是遍历m_pTargets哈希列表,然后逐个更新每个tHashElement 里面的动作,当然他会有一些判断,比如动作是否暂停,是否结束之类的,并做相应处理,不如终止了自然要remove掉。如果一切正常,那就调用动作的step方法(这里必须知道多态的概念,因为step和后面的update方法都是virtual的)。

CCActionInterval::step

void CCActionInterval::step(float dt)
{
if (m_bFirstTick)
{ // 如果是播放开始第一次进行时间流逝处理,将时间累和值设为0。
m_bFirstTick = false;
m_elapsed = 0;
}
else
{
m_elapsed += dt; // 动作时间累和
}
// 调用update函数更新动作。参数的结果是动作的时间插值结果,它代表了动作的进度,之前讲过它取0~1之间的值。这里MIN和MAX用来将计算结果限定在0~1间
this->update(MAX (0,
MIN(1, m_elapsed /
MAX(m_fDuration, FLT_EPSILON)
)
)
);
}


CCMoveTo::update

void CCMoveTo::update(float time)
  {
      if (m_pTarget) // 如果节点不为空
      {
          m_pTarget->setPosition(ccp(m_startPosition.x + m_delta.x * time,
              m_startPosition.y + m_delta.y * time)); // 设置节点坐标
      }
  }


结语

一个动作的实现原理就这么讲完了,如果有兴趣的话,大家也可以看看Jump、Scale的实现方法,基础的动画实现方法都是大同小异。

参考资料

1)剖析cocos2d-x之Action实现:http://www.linuxidc.com/Linux/2013-04/82436.htm

2)Cocos2d-x 2.0 之Actions (三):http://bbs.9ria.com/thread-198822-1-1.html

3)Cocos2d-x 2.0 之 Actions “三板斧” 之一:/article/1667890.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: