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

cocos2dx中的CCAction

2014-03-20 11:00 369 查看
稍微了解cocos2d-x的朋友应该都知道cocos2d-x里面的动作都是通过CCAction的各种派生类来实现的。

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

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

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

pSprite->runAction(moveTo);

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

首先必须要了解的几个类

1.CCAction和CCActionInterval

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

void CCAction::update(float time)

{

CC_UNUSED_PARAM(time);

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

}

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

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

2.CCActionManager

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

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

看看代码:

[cpp]
view plaincopyprint?

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

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


看看,内部是否是调用CCActionManager的addAction

接着我们再看看addAction的实现

[cpp]
view plaincopyprint?

  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);
  }

  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表就够了

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

[cpp]
view plaincopyprint?

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

  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方法

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

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

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

[cpp]
view plaincopyprint?

  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;
  }

  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方法,好的,现在我们再来看看他做了什么事情

[cpp]
view plaincopyprint?

  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

   )
   )
   );
  }

  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(目前动作的执行到的位置),并将其处理在一个合法范围内(通过MAX,MIN宏),然后传给update方法,最后在update方法里面会进行实际的动作更新,接下来,我们以CCMoveTo为例,看看他的update是如何实现的

[cpp]
view plaincopyprint?

  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));
   }
  }

  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));
      }
  }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: