剖析cocos2d-x之Action实现

稍微了解cocos2d-x的朋友应该都知道cocos2d-x里面的动作都是通过CCAction的各种派生类来实现的。

比如我要将一个Sprite1秒内从(0,0)移动到(400,400

pSprite->setPosition(ccp(0,0));

CCMoveTo* moveTo = CCMoveTo::create(1.0f,,ccp(400,400));

pSprite->runAction(moveTo);

那么,让我们来看看Actioncocos2d-x到底是如何实现的。

首先必须要了解的几个类

1.CCActionCCActionInterval

CCAction是所有动作的基类,从代码里可以看出它本身并不执行动作

void CCAction::update(float time)

{

    CC_UNUSED_PARAM(time);

    CCLOG("[Action update]. override me");

}

他的作用只是提供一部分接口,如stopupdate等等,其本身的实际作用并不是太大

CCActionInterval反而相对重要些,这个是大部分常用动作的基类,如常用的CCMoveToCCRotateBy等都是继承自CCActionInterval,CCActionInterval还有很重要的一点就是他实现了step方法,这个方法是大部分其派生类更新动作的基础(后文会有讨论)

2.CCActionManager

从名字可以看出,这个类负责管理着所有的动作,

前面代码里的pSprite->runAction(moveTo);其实就是将moveTo这个ActionpSprite这个Node加入到了CCActionManager内部维护的m_pTargets的这个hash表中

看看代码:

  CCAction * CCNode::runAction(CCAction* action)
  {
      CCAssert( action != NULL, "Argument must be non-nil");
      m_pActionManager->addAction(action, this, !m_bIsRunning);
      return action;
  }

看看,内部是否是调用CCActionManageraddAction

接着我们再看看addAction的实现

  void CCActionManager::addAction(CCAction *pAction, CCNode *pTarget, bool paused)
  {
      CCAssert(pAction != NULL, "");
      CCAssert(pTarget != NULL, "");
  
      tHashElement *pElement = NULL;
      // we should convert it to CCObject*, because we save it as CCObject*
      CCObject *tmp = pTarget;
      HASH_FIND_INT(m_pTargets, &tmp, pElement);
      if (! pElement)
      {
          pElement = (tHashElement*)calloc(sizeof(*pElement), 1);
          pElement->paused = paused;
          pTarget->retain();
          pElement->target = pTarget;
          HASH_ADD_INT(m_pTargets, target, pElement);
      }
  
  
       actionAllocWithHashElement(pElement);
   
       CCAssert(! ccArrayContainsObject(pElement->actions, pAction), "");
       ccArrayAppendObject(pElement->actions, pAction);
   
       pAction->startWithTarget(pTarget);
  }

略去见面的Assert检查和后面的初始化设定不看,上面代码的大概意思就是将pAction(上文的moveTo)pTarget(上文的pSprite)加入到内部维护的m_Targets中,至于m_Targets到底是个什么东西我们可以不必深究,就理解为一个数组或者hash表就够了

贴出这个结构的源码,有兴趣的朋友可以看看

  typedef struct _hashElement
  {
      struct _ccArray             *actions;
      CCObject                  *target;
      unsigned int                actionIndex;
      CCAction                  *currentAction;
      bool                      currentActionSalvaged;
      bool                      paused;
      UT_hash_handle            hh;
  } tHashElement;

这里要注意的一点,一个target是可以附带多个Action的,从上面actions的类型是一个数组我们可以加以确认。

OK,一切都设置妥当,我们开始讲解最核心的update方法

首先要说明一点,这个CCActionManagerupdate是在CCDisplayLinkDirector的mainLoop->drawScene里调用m_pScheduler->update(m_fDeltaTime);中被调用的

m_pScheduler是CCScheduler类型,他负责系统全部子系统的更新,比如TouchAction,脚本,定时器等等都在这里updateAction是在优先级 < 0的那个list里面的

好了,继续讨论update,还是先看看源码

  void CCActionManager::update(float dt)
  {
      for (tHashElement *elt = m_pTargets; elt != NULL; )
      {
          m_pCurrentTarget = elt;
          m_bCurrentTargetSalvaged = false;
  
          if (! m_pCurrentTarget->paused)
          {
              // The 'actions' CCMutableArray may change while inside this loop.
              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];
                  if (m_pCurrentTarget->currentAction == NULL)
                  {
                      continue;
                  }
  
                  m_pCurrentTarget->currentActionSalvaged = false;
  
                  m_pCurrentTarget->currentAction->step(dt);
  
                  if (m_pCurrentTarget->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.
                      m_pCurrentTarget->currentAction->release();
                  } else
                  if (m_pCurrentTarget->currentAction->isDone())
                  {
                      m_pCurrentTarget->currentAction->stop();
  
                      CCAction *pAction = m_pCurrentTarget->currentAction;
                      // Make currentAction nil to prevent removeAction from salvaging it.
                      m_pCurrentTarget->currentAction = NULL;
                      removeAction(pAction);
                  }
  
                  m_pCurrentTarget->currentAction = NULL;
              }
          }
  
          // 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 (m_bCurrentTargetSalvaged && m_pCurrentTarget->actions->num == 0)
          {
              deleteHashElement(m_pCurrentTarget);
          }
      }
  
      // issue #635
      m_pCurrentTarget = NULL;
  }

其实update的代码很简单,就是遍历m_pTargets数组(或者链表,hash,随你高兴,不过他实际是hash),然后逐个更新每个tHashElement 里面的动作,当然他会有一些判断,比如动作是否暂停,是否结束之类的,并做相应处理,不如终止了自然要remove掉。如果一切正常,那就调用动作的step方法(这里必须知道多态的概念哦,因为step和后面的update方法都是virtual的)。

前面讲过CCActionInterval有实现step方法,好的,现在我们再来看看他做了什么事情

  void CCActionInterval::step(float dt)
  {
      if (m_bFirstTick)
      {
          m_bFirstTick = false;
          m_elapsed = 0;
      }
      else
      {
          m_elapsed += dt;
      }
      
      this->update(MAX (0,                                  // needed for rewind. elapsed could be negative
                        MIN(1, m_elapsed /
                            MAX(m_fDuration, FLT_EPSILON)   // division by 0
                            )
                        )
                   );
  }

简而言之,就是根据传入的时间间隔(dt),增加m_elapsed(目前动作的执行到的位置),并将其处理在一个合法范围内(通过MAXMIN宏),然后传给update方法,最后在update方法里面会进行实际的动作更新,接下来,我们以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));
      }
  }

看到了吧,他在这里就会根据他目前的elapse设置他所负责的Node的位置了。

大家有兴趣也可以看看其他动作,如RotateJump之类,看看他们是如何来设置位置的。

OK,到此结束

由于本人才疏学浅,以上内容如有错误,欢迎大家多多斧正


你可能感兴趣的:(C++学习,cocos2dx学习笔记)