【源码分析】cocos2dx的Action

  第一次去学习Action,总会找到一篇入门的帖子(官网:http://cn.cocos2d-x.org/article/index?type=cocos2d-x&url=/doc/cocos-docs-master/manual/framework/native/v3/action/zh.md)。也会初步了解Action是怎么用的?

  *创建动作对象,eg:移动、旋转等

  *调用Node的runAction函数,这个动作就表现出来了

  auto actionTo = MoveTo::create(2, Vec2(s.width-40, s.height-40));
    auto actionBy = MoveBy::create(2, Vec2(80,80));
    auto actionByBack = actionBy->reverse();

    _tamara->runAction( actionTo);
    _grossini->runAction( Sequence::create(actionBy, actionByBack, nullptr));
    _kathia->runAction(MoveTo::create(1, Vec2(40,40)));

  So Easy!但是,但是,看完之后你会有一溜的问题!

(1)类结构中即时动作和持续动作在原理上有什么不同?

(2)FiniteTimeAction、Follow和Speed这两个是干嘛的?尤其是后面两个。

(3)Action工作的一个原理是什么?

  然而,还有一个一开始容易被忽视的问题,但是你去看了之后才发现有很多技术或者知识细节需要去弄明白,再加一个问题:

(4)不管是即时动作(ActionInstant)还是ActionInterval(持续动作)里面包含了许多动作类,他们表现上的差异在原理上有什么不同?如何实现的?

*友情提示*上面的问题记得看一篇入门文章,后面的内容并不一定按照上面问题的顺序和分节去讲,只是按照自己学习的思路记录一下,增强自己的记忆和梳理一些遗漏的知识点,如果能帮助一些和我一样的菜鸟朋友,权当无心插柳!^_^

----------------------------------------------------------------------------------------------------------------------------------------------------------

--下面进入高能阶段--

----------------------------------------------------------------------------------------------------------------------------------------------------------

一、Action的原理

  看cocos提供的例子,很容易理解一些基本的动作,比如move、rotate、jump等,但是当你看源码的时候,你是想知道,这个整体的逻辑是怎么设计和实现的,先上Action的流程图,如下所示:

  

1.流程图解析

(1)没有方框的名字是类名,eg:Director, Scheduler,ActionManager,Action,Timer等

(2)没有方框但是带有‘*’开头的是旁白,为了帮助自己记忆和更容易看明白

(3)有颜色的的都是自己觉得比较重要的关键点,当然其他细节也需要弄明白,一些表示状态的标记等

* 两条蓝色的箭头是Scheduler(看调度器时候重要)调用update函数的地方,所以不管是动作的ActionManager还是定时器的Timer,主要逻辑都还是在update中完成的

* 紫色的step函数是Action的逻辑主要完成地方,但是由于Action是基类。最后各个动作都是继承的Action,但是没有重载step函数,所以基类Action中的step是调用了update函数的,因此你可以看到多数动作的逻辑是自己重载update函数完成的,而为什么要有step呢?那是因为有些类是需要重载step的,他不需要update,可以看Follow和Speed的源码。

void Follow::step(float dt)
{
    CC_UNUSED_PARAM(dt);

    if(_boundarySet)
    {
        // whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
        if(_boundaryFullyCovered)
            return;

        Vec2 tempPos = _halfScreenSize - _followedNode->getPosition();

        _target->setPosition(Vec2(clampf(tempPos.x, _leftBoundary, _rightBoundary),
                                   clampf(tempPos.y, _bottomBoundary, _topBoundary)));
    }
    else
    {
        _target->setPosition(_halfScreenSize - _followedNode->getPosition());
    }
}

  当然,也可以这么理解:由于连续性动作需要时间上有个计时器的概念,所以他需要每隔一段时间update,而其他类不需要,只要把逻辑写在step里面就可以了,所以需要一个step和update的层次概念,这样是不是很好理解了呢?(clever!^_^)

* 各个真正使用的动作类(如Move、Jump等)都是重载了update函数的,那么update干嘛了呢?只有update函数这里有差异,所以update是区分各个动作的关键所在!来看一个简单的动作代码(MoveBy):

void MoveBy::update(float t)
{
    if (_target)
    {
#if CC_ENABLE_STACKABLE_ACTIONS
        Vec2 currentPos = _target->getPosition();
        Vec2 diff = currentPos - _previousPosition;
        _startPosition = _startPosition + diff;
        Vec2 newPos =  _startPosition + (_positionDelta * t);
        _target->setPosition(newPos);
        _previousPosition = newPos;
#else
        _target->setPosition(_startPosition + _positionDelta * t);
#endif // CC_ENABLE_STACKABLE_ACTIONS
    }
}

  稍微看一下就明白了,他只做了一件事情,就是设置节点(_target是node对象,例如一个精灵Sprite)的位置。有点乱~,往前看,这里看下连续性动作ActionInternal的代码,再联系上这里的update

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

  看到了没?每一帧都会调用update函数,而update函数的参数是一个比例值_elapsed/MAX(_duration, FLT_EPSILOW)。去掉一些MIN、MAX等宏(防止除数为0等的情况),很容易看明白这个比例表示了当前时间点在整个持续动作过程的某个位置(注:这里的update参数和scheduler中的update参数意义不一样,step的参数和scheduler中的update参数是一个意义)

  那么一个宏观的逻辑就算是比较清晰了,那么发散思维一下:如果是Jump呢?他的update做了什么呢?同样的也是setPosition,只不过不近要处理水平坐标,还要处理纵坐标;Scale呢?就是直接设置Node(eg:精灵Sprite)的scale,(eg:setScaleX,setScaleY)...

(4)Timer中红色方框边缘的节点表示的是关键函数,这里面执行的就是定时器传进来的函数参数。

(5)深绿色方框边缘的判断语句“时间到”只是表示他们都是一样的:_elapsed >= _interval,看源代码也很好理解。命名很赞!

2. cocos文件

  cocos中的文件很好找,在工程里面libcocos2d/2d目录下面一开始就可以看到一槽排的CCActionXXX.h和CCActionXXX.cpp文件,具体自己去看吧。(个人版本:cocos2dx-3.2)

3. 问题

4. 总结

(1)整体理解一下,Action是一系列不同的对象,绑定Node。ActionManager维护一个hash表,并且每帧处理!

(2)跳出来看,每一个Action就是让Node做某件事情,也就是设定Node的某项属性。如果是即时动作,这立马执行。如果是持续性动作,就是每帧去处理节点的属性,因为牵涉到时间概念,所以有一个简单的计算过程(比例值的计算)。

---先到这里---后面继续记录>>>

----------------------------------------------------------------------------------------------------------------------------------------------------------

二、各种动作详细解读

原文地址:https://www.cnblogs.com/pk-run/p/4185559.html