Cocos2Dx之动作Action-欧阳左至

来源:互联网 发布:王坚阿里云纷争马云 编辑:程序博客网 时间:2024/05/16 19:43

Cocos2Dx提供了调度器。结合调度器,我们可以不断地修改节点的属性,从而实现丰富的动态效果。但是这样做过于麻烦。举个例子,我们需要把一个精灵从一个位置移动到另外一个位置。从我们前面提到的内容来思考一个可行的方案:在父节点的update中,将它的位置移动镇间隔时间移动的距离。假定目标位置到初始距离为d,移动时间指定为t秒,帧间隔时间1/60秒,那么每个update我们需要往目标位置移动d/60t。这还是匀速地移动,如果要求加速移动的效果,或者减速移动的效果,情况就变得更加复杂一些了。

为了简化节点的动作执行,一般是CCSprite,Cocos2Dx提供了CAction来支持动作。动作可以简单地分为两类,一是瞬时动作,即立即完成的动作;二是持续性动作,即动作的执行需要持续一段时间。另外,我们还需要有能够组合各种动作的能力,称之为复合动作。下面我们,先看看动作的分类。

瞬时动作。瞬时动作就是下一帧就要完成的动作,比如定位、缩放。其实这样的动作不需要定时机制,修改节点的属性即可,但还是封装为一个动作,目的是方便进行做动作的组合。瞬时动作包括:

  • CCPlace:将节点放置到某个指定位置,其作用与修改节点的Position属性相同。

  • CCFlipX和CCFlipY:用于将精灵沿X和Y轴反向显示,其作用与设置精灵的FlipX和FlipY属性相同。

  • CCShow和CCHide:用于显示和隐藏节点,其作用与设置节点的Visible属性的作用一样。

  • CCCallFunc:CCCallFunc系列动作包括CCCallFunc、CCCallFuncN、CCCallFuncND,以及CCCall- FuncO四个动作,用来在动作中进行类的实例方法的调用。

持续性动作。持续性动作,以为着动作的执行需要持续一段时间。所以,这些都做都需要指定动作执行的时间:duration。持续性动作还可以根据产生的效果不同进一步细分。

  • 位置变化动作:针对位置(position)属性。

    • CCMoveTo和CCMoveBy:用于使节点做直线运动。

    • CCJumpTo和CCJumpBy:使节点以一定的轨迹跳跃到指定位置。

    • CCBezierTo和CCBezierBy:使节点进行曲线运动,运动的轨迹由贝塞尔曲线描述。

by是相对于节点的位置;to是绝对位置。

  • 属性变化动作:通过属性值的逐渐变化来实现动画效果。

    • CCScaleTo和CCScaleBy:产生缩放效果,使节点的缩放系数随时间线性变化。

    • CCRotateTo和CCRotateBy:产生旋转效果。

    • CCFadeIn和CCFadeOut:产生淡入淡出效果,其中前者实现了淡入效果,后者实现了淡出效果。

    • CCFadeTo:用于设置一段时间内透明度的变化效果。

    • CCTintTo和CCTintBy:设置色调变化。可以理解为RGB颜色中的某一种颜色值变化了。

  • 视觉特效动作:一些特殊的视觉效果。

    • CCBlink:使目标节点闪烁。

    • CCAnimation:播放帧动画,用帧动画的形式实现动画效果。

  • 控制动作:对一些列动作进行精细控制。

    • CCDelayTime:将动作延时一定的时间才执行。

    • CCRepeat:把现有的动作重复一定次数。

    • CCRepeatForever:现有动作一直执行下去。

复合动作。复合动作是由一些基本动作一起组合成的新动作。可以满足游戏需要的复杂动作需求。

  • CCSpawn:允许一批动作同时执行。

  • CCSequence:顺序执行一系列动作。

  • CCSpeed:改变动作执行的速度。

  • CCActionEase:是一类动作,不同于CCSpeed的匀速执行,CCActionEase允许多样化地控制速度。

    • CCEaseRateAction

      • CCEaseIn

      • CCEaseOut

      • CCEaseInOut

    • CCEaseExponentialIn

    • CCEaseExponentialIn

    • CCEaseExponentialInOut

    • CCEaseSineIn

    • CCEaseSineOut

    • CCEaseSineInOut

    • CCEaseElastic

      • CCEaseElasticIn

      • CCEaseElasticOut

      • CCEaseElasticInOut

    • CCEaseBounce

      • CCEaseBounceIn

      • CCEaseBounceOut

      • CCEaseBounceInOut

    • CCEaseBackIn

    • CCEaseBackOut

    • CCEaseBackInOut

JQuery有个比较详尽的Easing效果示例。可以访问http://jqueryui.com/easing/查看。并不是所有的Easing效果Cocos2Dx都支持。

知道大概有哪些Action后,我们看看Action是怎么得到调度执行的。

我们执行一个Action的方法,一般是是调用CCSripte::runAction,因为动作一般都是作用于某个精灵的。CCSripte继承自CCNodeRGBA,后者继承自CCNode。CCNode提供了runAction来执行动作。CCLayer也继承自CCNode,因此它也是可以执行动作的。后面都假定执行动作的对象是一个CCSprite。

CCNode有一个成员m_pActionManager,它默认初始化为CCDirector内部的CCActionManager。CCDirector在它的init初始化函数中会创建一个CCActionManager对象。这类似于调度器CCScheduler。CCNode拥有指向CCActionManager对象的指针,因此在CCNode::runAction里面,通过CCActionManager的addAction将自己添加到CCActionManager当中。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void CCActionManager::addAction(CCAction *pAction, CCNode *pTarget, bool paused)
{
    tHashElement *pElement = NULL;
    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);
     ccArrayAppendObject(pElement->actions, pAction);
     pAction->startWithTarget(pTarget);
}

CCActionManager同样用一个HASH表来存储动作CCAction,HASH表的键还是CCObject类型的指针。每个CCSprite可能同时在执行多个动作。CCActionManager将这些动作放在一个数组当中(tHashElement->actions)。CCActionManager::addAction先根据传入的pTarget作为键来查看HASH表中是否已经存在对应的Bucket。如果没有找到,就现在HASH表中创建一个。然后,将现在的CCAction添加到数组的末尾。

到目前为止,我们只是将需要执行的动作放在一个HASH表中。什么时候执行呢?这就要看CCDirector::init。CCDirector初始化的时候,先创建一个CCScheduler,然后创建一个CCActionManager,随后马上调用CCScheduler::scheduleUpdateForTarget将自己注册为一个update调度器。并且update调度器的优先级设为了最高优先级kCCPrioritySystem(INT_MIN)。

CCScheduler::scheduleUpdateForTarget的实现我们前面已经讨论过了。它将注册的update调度器根据优先级放到不同的列表当中。update调度器在每个帧间隔时间到期之后就会被调用。CCActionManager把自己注册为一个update调度器,那么在每个帧间隔时间到期之后,就会调用CCActionManager的update函数。由于CCActionManager管理所有的动作,因此CCActionManager::update相当于管理动作的一个主循环。

注:到这里,我们已经讨论过不少Cocos2Dx的主循环:应用程序主循环、CCDirector的主循环、CCScheduler的主循环、CCScene的主循环、负责内存维护的栈循环。现在我们讨论到了负责管理动作的CCActionManager主循环。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void CCActionManager::update(float dt)
{
    for (tHashElement *elt = m_pTargets; elt != NULL; )
    {
        m_pCurrentTarget = elt;
        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];
                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;
                    m_pCurrentTarget->currentAction = NULL;
                    removeAction(pAction);
                }
                m_pCurrentTarget->currentAction = NULL;
            }
        }
        elt = (tHashElement*)(elt->hh.next);
        if (m_bCurrentTargetSalvaged && m_pCurrentTarget->actions->num == 0)
        {
            deleteHashElement(m_pCurrentTarget);
        }
    }
    m_pCurrentTarget = NULL;
}

CCActionManager::update的外循环遍历所有的HASH表的Bucket,内循环遍历CCSprite上的所有动作。执行动作是通过调用CCAction的step函数(多态调用)。不同类型的动作,可能实现了不同的step。我们后面再看不同的step实现。currentActionSalvaged用来标记动作是否被要求删除。CCAction的isDone虚函数用来帮助CCActionManager判断当前的动作是否已经完成。CCAction的stop虚函数是在清理动作之前执行的回调函数。如果动作执行完step之后,已经完成了,那么动作就会从HASH表中Bucket的数组上删除。最后,如果某个CCSprite上已经没有附加任何动作,就删除HASH表中这个Bucket。

我们可以看出,实现一个动作的关键是在继承CCAction的基础上实现这些虚函数:

  • void step(float dt)

  • void stop(void)

  • bool isDone(void)

  • void update(float time) 真正实现动作的地方,step相当于一个包装器,它内部会调用update。

动作相关的类很多。它们放在\cocos2dx\actions下面。我们不讨论所有的动作,挑选一两个看下怎么在CCAction的基础上实现动作即可。点到为止。

CCActionInterval和CCJumpBy

CCActionInterval是持续性动作。意味着动作的完成可能会持续大于一帧的时间。CCActionInterval和CCActionInstant都是继承自CCFiniteTimeAction。对计算机来说,时间还是一个有限量。CCFiniteTimeAction本身只是提供了对动作持续时间duration的封装。CCActionInstant的duration为0而已。CCActionInterval的duration是构造函数传入的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
void CCActionInterval::step(float dt)
{
    if (m_bFirstTick)
    {
        m_bFirstTick = false;
        m_elapsed = 0;
    }
    else
    {
        m_elapsed += dt;
    }
    this->update(MAX(0, MIN(1, m_elapsed / MAX(m_fDuration, FLT_EPSILON))));
}

CCActionInterval的step累积已经流逝的时间,存放在m_elapsed当中。如果流逝的时间m_elapsed大于我们指定的动作执行时间duration,那么动作就该完成了,这就是CCActionInterval::isDone做的事情。

需要注意的是,传给动作update的时间是一个0-1的浮点数。它表示整个动作完成的程度。由于每帧都会调用step,那么动作的update函数的执行次数就是按duration和帧间隔时间除的结果。step是用来控制动作执行过程的。

因此CCJumpBy需要做的就是在每帧调用update时,将CCSprite移动到合适的位置。

?
1
2
3
4
5
6
7
8
9
10
11
void CCJumpBy::update(float t)
{
    if (m_pTarget)
    {
        float frac = fmodf( t * m_nJumps, 1.0f );
        float y = m_height * 4 * frac * (1 - frac);
        y += m_delta.y * t;
        float x = m_delta.x * t;
        m_pTarget->setPosition(ccpAdd( m_startPosition, ccp(x,y)));
    }
}

CCSpeed

CCSpeed本身并不是一个独立的动作。它作用于其他的继续性动作(CCActionInterval),可以加快或者降低其他动作的执行速度。具体实现的办法是在step的流逝时间参数上乘以了一个速度因子,实现非常简单。

他的isDone和stop都是调用作用动作的对应实现。

CCActionInstant和CCPlace

CCActionInstant是在下一帧就会完成的动作。CCActionInstant::isDone的直接返回ture,因此CCActionInstant::step只会执行一次,对应的动作的update函数也就只会执行一次。

CCPlace继承自CCActionInstant,它的update函数也就非常简单了,直接将自己的位置设置为目标位置即可。

由于动作的执行只是改变了CCSprite的属性,但是还需要在当前帧上反应出来,因此调度器的调用应该在渲染场景之前,详见CCDirector::drawScene。

还有一个特殊动作是CCAnimate,它和CCAnimation一起完成动画效果。我们后面再讨论。

CCCallFunc、CCCallFuncN、CCCallFuncND、CCCallFuncO

这类动作比较特殊,它们用来帮助执行自定义的回调函数。后缀代表的是不同的参数。

0 0
原创粉丝点击