首页 > 代码库 > Cocos2d-x学习笔记(七)CCAction原理分析

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

原创文章,转载请注明出处:http://blog.csdn.net/sfh366958228/article/details/38824987


前言

上一讲学习笔记,我们学习了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 “三板斧” 之一:http://blog.csdn.net/honghaier/article/details/8197892


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