Skip to content

cocos2d x 3.3 013 动作Action原理

cheyiliu edited this page Dec 29, 2014 · 6 revisions

相关类

  • Action及子类, 提供如何改变Node属性的算法
  • Node,被Action改变的对象
  • ActionManager,Action的驱动器

Action类图

coco 3.3 action class

由一个例子说开

例子

// Move sprite 20 points to right in 2 seconds
auto moveBy = MoveBy::create(2, Vec2(20,0));
mySprite2->runAction(moveTo);

背后的故事

  • MoveBy::create(2, Vec2(20,0));
//两段构造
MoveBy* MoveBy::create(float duration, const Vec2& deltaPosition)
{
    MoveBy *ret = new (std::nothrow) MoveBy();
    ret->initWithDuration(duration, deltaPosition);
    ret->autorelease();

    return ret;
}

bool MoveBy::initWithDuration(float duration, const Vec2& deltaPosition)
{
//调用父类函数将duration设置
    if (ActionInterval::initWithDuration(duration))
    {
//设置deltaPosition
        _positionDelta = deltaPosition;
        return true;
    }

    return false;
}
  • mySprite2->runAction(moveTo);
Action * Node::runAction(Action* action)
{
    CCASSERT( action != nullptr, "Argument must be non-nil");
    _actionManager->addAction(action, this, !_running);
    return action;
}
  • ActionManager::addAction
void ActionManager::addAction(Action *action, Node *target, bool paused)
{
    CCASSERT(action != nullptr, "");
    CCASSERT(target != nullptr, "");

    tHashElement *element = nullptr;
    // we should convert it to Ref*, because we save it as Ref*
    Ref *tmp = target;
//在hash表中查找target
//(这里的hash结构由uthash实现,在schedule 中也用过 https://github.com/cheyiliu/All-in-One/wiki/cocos2d-x-3.3-011-%E8%B0%83%E5%BA%A6%E5%99%A8 )
    HASH_FIND_PTR(_targets, &tmp, element);
    if (! element)//没找到
    {
        element = (tHashElement*)calloc(sizeof(*element), 1);
        element->paused = paused;
        target->retain();
        element->target = target;
        HASH_ADD_PTR(_targets, target, element);
    }

//确保element的actions数组有容量存储
     actionAllocWithHashElement(element);
 
     CCASSERT(! ccArrayContainsObject(element->actions, action), "");
//将action放入element->actions
     ccArrayAppendObject(element->actions, action);
 
//这一步很关键,建立Node和action的关系
     action->startWithTarget(target);
}
  • 小结下前面的代码: 1.构造出了action; 2.将action交个action manager管理起来了; 3. action manager将action和Node建立了关联。 前面的runAction并没有真的'run', 那谁让action真的run起来了?答案在ActionManager

  • ActionManager的创建

1. ActionManager的创建
bool Director::init(void)
{
//其他略
    // action manager
    _actionManager = new (std::nothrow) ActionManager();
    _scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false);
}

2. scheduleUpdate, 用到了Scheduler的知识, https://github.com/cheyiliu/All-in-One/wiki/cocos2d-x-3.3-011-%E8%B0%83%E5%BA%A6%E5%99%A8
    template <class T>
    void scheduleUpdate(T *target, int priority, bool paused)
    {
        this->schedulePerFrame([target](float dt){
            target->update(dt);
        }, target, priority, paused);
    }

//这里的流程:导演在创建的时候, 创建了ActionManager,并注册到Scheduler中。 这使得之后每一帧里ActionManager的update方法都会被Scheduler的update方法调用到(```mainLoop->drawScene->scheduler::update->callback到ActionManager的update```)。
  • ActionManager的update如何驱动action 'run'起来
void ActionManager::update(float dt)
{
//一重循环,遍历Node
    for (tHashElement *elt = _targets; elt != nullptr; )
    {
        _currentTarget = elt;
        _currentTargetSalvaged = false;
//未被暂停
        if (! _currentTarget->paused)
        {
//二重循环,遍历Node对应的action
            // The 'actions' MutableArray may change while inside this loop.
            for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num;
                _currentTarget->actionIndex++)
            {
                _currentTarget->currentAction = (Action*)_currentTarget->actions->arr[_currentTarget->actionIndex];
                if (_currentTarget->currentAction == nullptr)
                {
                    continue;
                }

                _currentTarget->currentActionSalvaged = false;

//!!!
//关键点, action::step, 将action驱动起来
                _currentTarget->currentAction->step(dt);

//其他声明周期相关的管理
                if (_currentTarget->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.
                    _currentTarget->currentAction->release();
                } else
                if (_currentTarget->currentAction->isDone())
                {
//动作结束了
                    _currentTarget->currentAction->stop();

                    Action *action = _currentTarget->currentAction;
                    // Make currentAction nil to prevent removeAction from salvaging it.
                    _currentTarget->currentAction = nullptr;
                    removeAction(action);
                }

                _currentTarget->currentAction = nullptr;
            }
        }

        // 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 (_currentTargetSalvaged && _currentTarget->actions->num == 0)
        {
            deleteHashElement(_currentTarget);
        }
    }

    // issue #635
    _currentTarget = nullptr;
}
  • 最后看MoveBy的step方法
1. MoveBy的step是直接继承的基类的
void ActionInterval::step(float dt)
{
    if (_firstTick)
    {
//第一次调用则初始化
        _firstTick = false;
        _elapsed = 0;
    }
    else
    {
//非第一次调用,开始累计逝去的美好时光
        _elapsed += dt;
    }
    
//最终调用到虚函数update,注意这个参数的意义
    this->update(MAX (0,                                  // needed for rewind. elapsed could be negative
                      MIN(1, _elapsed /
                          MAX(_duration, FLT_EPSILON)   // division by 0
                          )
                      )
                 );
}
  • MoveBy::update
void MoveBy::update(float t)
{

//具体算法我不关注了, 我们现在关注的流程是action最中如何改变Node属性的。
//下面的关键代码 _target->setPosition(xxx);
//到此, Action真正起作用了, 'run'起来了。

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

其他action

  • 执行流程都类似, 只不过不同的action改变的属性不同和改变属性的算法不同而已, 一般体现在Action的step或者update的实现不同。

小结

  • 使用action的标准步骤是create action+ xxNode runAction
  • 但runAction并非真正的run,这步仅调用了action manager的add action, 并在action manager的撮合下action结识了xxNode
  • 驱动action真正run的是action manager, 他是导演的一个成员变量, 在初始化后就被加入到了调度器导致游戏每一帧里都会调用action manager的update方法。 action manger的update方法最终调用action的step方法进行相应的算法处理并最终调用到action的update方法来改变xxNode的相关属性。

扩展阅读

Clone this wiki locally