cocos2d-x 动作实现的浅析

1.  动作类的结构

打开CCAction.h文档,按照注释我们可以找到下面几个比较重要的方法。

 //! return true if the action has finished
    virtual bool isDone(void);
    //! called before the action start. It will also set the target.
    virtual void startWithTarget(CCNode *pTarget);
    /** 
    called after the action has finished. It will set the 'target' to nil.
    IMPORTANT: You should never call "[action stop]" manually. Instead, use: "target->stopAction(action);"
    */
    virtual void stop(void);
    //! called every frame with it's delta time. DON'T override unless you know what you are doing.
    virtual void step(float dt);
    /** 
    called once per frame. time a value between 0 and 1
    For example: 
    - 0 means that the action just started
    - 0.5 means that the action is in the middle
    - 1 means that the action is over
*/
virtual void update(float time);

     isDone判断动作是否完成,startWithTarget设定执行该动作的对象,stop停止当前动作,step累积时间并在指定周期更新动作,update按照百分比更新动作。

接着我们看看它的一个使用最多的一个子类CCFiniteTimeAction,发现它新增了一个用于保存该动作完成时间的变量:ccTimem_fDuration。按照猜想,应该这个变量是用于区分实现瞬时动作和延时动作的。

    进一步去观察源代码,发现有一个CCActionManager的类,顾名思义就是动作管理类。通过查看资料发现,这个类主要功能为把动作对象添加到队列,然后根据一定周期更新动作列表里的动作,根据指示标志来停止动作或者动作完成之后把动作从队列中删除。

搜索代码中CCActionManager的初始化,我们发现初始化代码在CCDirector::init()中,代码如下:

M_pActionManager = new CCActionManager();
M_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false)

    可以知道CCActionManager在创建对象的时候即注册了一个定期更新的服务。也就是说动作调度其实也就是借助CCScheduler的控制,这应该也简化了动作暂停、恢复等功能的实现,只要控制定时器即可。根据我们前面一篇文章对于定时机制的分析,我们可以知道这是一种update类型的定时器,也就是只要重载update函数,并初始化的时候调用scheduleUpdate即可。接着我们去查看CCActionManager::update(float dt)函数。

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函数定时遍历动作列表的节点,然后调用我们前面所说的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
                          )
                      )
                 );
}

    

    这个是延时动作类的step函数,可以看到一个关键的字眼就是update,这个方法的time参数表示逝去的时间与动作完成需要的时间的比值,取值为0-1之间的百分比。究竟这个update实现了什么,我们可以进一步找个具体动作来看看。


//
// RotateTo
//

CCRotateTo* CCRotateTo::create(float fDuration, float fDeltaAngle)
{
    CCRotateTo* pRotateTo = new CCRotateTo();
    pRotateTo->initWithDuration(fDuration, fDeltaAngle);
    pRotateTo->autorelease();

    return pRotateTo;
}

bool CCRotateTo::initWithDuration(float fDuration, float fDeltaAngle)
{
    if (CCActionInterval::initWithDuration(fDuration))
    {
        m_fDstAngleX = m_fDstAngleY = fDeltaAngle;
        return true;
    }

    return false;
}

CCRotateTo* CCRotateTo::create(float fDuration, float fDeltaAngleX, float fDeltaAngleY)
{
    CCRotateTo* pRotateTo = new CCRotateTo();
    pRotateTo->initWithDuration(fDuration, fDeltaAngleX, fDeltaAngleY);
    pRotateTo->autorelease();
    
    return pRotateTo;
}

bool CCRotateTo::initWithDuration(float fDuration, float fDeltaAngleX, float fDeltaAngleY)
{
    if (CCActionInterval::initWithDuration(fDuration))
    {
        m_fDstAngleX = fDeltaAngleX;
        m_fDstAngleY = fDeltaAngleY;
        
        return true;
    }
    
    return false;
}

CCObject* CCRotateTo::copyWithZone(CCZone *pZone)
{
    CCZone* pNewZone = NULL;
    CCRotateTo* pCopy = NULL;
    if(pZone && pZone->m_pCopyObject)
    {
        //in case of being called at sub class
        pCopy = (CCRotateTo*)(pZone->m_pCopyObject);
    }
    else
    {
        pCopy = new CCRotateTo();
        pZone = pNewZone = new CCZone(pCopy);
    }

    CCActionInterval::copyWithZone(pZone);

    pCopy->initWithDuration(m_fDuration, m_fDstAngleX, m_fDstAngleY);

    //Action *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] angle: angle];
    CC_SAFE_DELETE(pNewZone);
    return pCopy;
}

void CCRotateTo::startWithTarget(CCNode *pTarget)
{
    CCActionInterval::startWithTarget(pTarget);
    
    // Calculate X
    m_fStartAngleX = pTarget->getRotationX();
    if (m_fStartAngleX > 0)
    {
        m_fStartAngleX = fmodf(m_fStartAngleX, 360.0f);
    }
    else
    {
        m_fStartAngleX = fmodf(m_fStartAngleX, -360.0f);
    }

    m_fDiffAngleX = m_fDstAngleX - m_fStartAngleX;
    if (m_fDiffAngleX > 180)
    {
        m_fDiffAngleX -= 360;
    }
    if (m_fDiffAngleX < -180)
    {
        m_fDiffAngleX += 360;
    }
    
    //Calculate Y: It's duplicated from calculating X since the rotation wrap should be the same
    m_fStartAngleY = m_pTarget->getRotationY();

    if (m_fStartAngleY > 0)
    {
        m_fStartAngleY = fmodf(m_fStartAngleY, 360.0f);
    }
    else
    {
        m_fStartAngleY = fmodf(m_fStartAngleY, -360.0f);
    }

    m_fDiffAngleY = m_fDstAngleY - m_fStartAngleY;
    if (m_fDiffAngleY > 180)
    {
        m_fDiffAngleY -= 360;
    }

    if (m_fDiffAngleY < -180)
    {
        m_fDiffAngleY += 360;
    }
}

void CCRotateTo::update(float time)
{
    if (m_pTarget)
    {
        m_pTarget->setRotationX(m_fStartAngleX + m_fDiffAngleX * time);
        m_pTarget->setRotationY(m_fStartAngleY + m_fDiffAngleY * time);
    }
}

    这个是CCActionInterval.cpp中提取的一个RotateTo动作的实现,可以看到,它主要是重写CCActionInterval中的一些基本方法,而实现动作变化的关键是update函数,可以看到update通过设置当前节点的属性值Rotation就完成了该工作的效果。

    根据前面的简单分析,也就是动作的处理过程是先受到系统定时器的调度,当周期到达的时候,调用CCActionManager中的update来遍历动作列表,并进行处理。处理的一个重点是调用step函数来记录时长,并再次调用所实现动作的update函数来进行动作的更新。假如我们需要自定义自己的动作的时候,我们可以通过继承CCAction的子类,然后关键的逻辑控制在于重载step和update,借助这两个函数来实现动作的触发和效果。


如有错误和不足,请及时支出,谢谢指导!





你可能感兴趣的:(cocos2d-x 动作实现的浅析)