cocos2dx-action的实现逻辑

前言

action是我们在cocos当中用到的相当多的一个功能,常见的调用方式通过node的runAction去执行一系列对应的动作,本篇文章介绍一下和action的设计方式和实现原理


相关流程图

  • 子类继承关系流程图

速度控制
跟随action
有限次动作执行类
瞬时动作基类
延时动作基类
Ref
action
Clonable
Speed
Follow
FinitetimeAction
ActionInstant
ActionInterval
Show
Hide
Callback
Move
Scale
Fade
  • 调用执行关系流程图

update
status
runAction
ActionManager
hashElement存action的结构体
action
target
Node

基类action

  • 要点

    • action继承自Ref和clonable,基本类型可重写clone复制
    • action是所有动作的基类, 所有可被执行的动作,都是继承自action
    • action重点接口step:每一帧都会被调用,参数是时间跨度
    • action重点接口update:每次step都会被调用,但是参数不是时间是完成度(0-1),0表示还没开始,1表示动作已经完成了
    • 每帧刷新方式是actionManager在director类初始化的时候,执行的一个定时器,更新ActionManager的update方法
  • 总结

    • 所有的action都是通过在step和update的刷新更新状态,例如scale,在step里计算当前消耗时间和总时间的比例,然后告诉update执行到哪了,update将target按进度变化
      在这里插入图片描述
// 每帧被调用,dt是每帧调用的时间 不要轻易重写
virtual void step(float dt);
// 每帧被调用,time是0-1的一个数,表示完成度,0是没开始,1是完成了
virtual void update(float time);

action子类之Speed

  • 简介

Speed是直接继承自Action的一个拓展动作类,功能是用来调整节点的执行action的速度,普通action、sequence或者repeat都可以用,个人觉得有点鸡肋,不如直接调整action的执行时间更方便

  • 实现原理

在action中我们介绍了,step是每帧都会被调用的,参数就是时间维度,Speed的实现原理如下所示,在init的时候传了一个参数speed,在重写的step方法内,并不是直接传入了时间dt,而是传入了dt*speed,时间变长了,执行进度自然变快了

bool Speed::initWithAction(ActionInterval *action, float speed)
{
    CCASSERT(action != nullptr, "action must not be NULL");
    if (action == nullptr)
    {
        log("Speed::initWithAction error: action is nullptr!");
        return false;
    }
    
    action->retain();
    _innerAction = action;
    _speed = speed;
    return true;
}

void Speed::step(float dt)
{
    _innerAction->step(dt * _speed);
}
  • 实例演示

# ScaleTo快了四倍
local action1 = cc.ScaleTo:create(10,5)
local action2 = cc.Speed:create(action1,4)
sp:runAction(action2)

action子类之Follow

  • 简介

Follow是直接继承自Action的一个拓展动作类,功能是实现一个节点对领一个节点的跟随运动,可以限制跟随的偏移和跟随的范围,一般用在镜头跟随之类的,博主基本没用过

  • 实现原理

和Speed基本类似啊,在init的时候传入了跟随的目标,跟随的偏移和范围,然后再step里面去计算应该到得位置

bool Follow::initWithTargetAndOffset(Node *followedNode, float xOffset,float yOffset,const Rect& rect)
{
  .......
}

void Follow::step(float /*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(clampf(tempPos.x, _leftBoundary, _rightBoundary),
                                   clampf(tempPos.y, _bottomBoundary, _topBoundary));
    }
    else
    {
        _target->setPosition(_halfScreenSize - _followedNode->getPosition());
    }
}
  • 实例演示

local action1 = cc.Follow:create(sp1,cc.rect(0,0,0,0))
sp2:runAction(action1)
sp1:runAction(cc.MoveTo:create(2,cc.p(360,300)))

action子类之FinitetimeAction

  • 简介

有限次动作执行类,就是按时间顺序执行一系列动作,执行完后动作结束,下分了两个子类ActionInstant和ActionInterval,我们常用action都是继承自这俩

  • 接口

这个中间类比较简单,多了一个和执行时间相关的变量

/** Get duration in seconds of the action. 
     *
     * @return The duration in seconds of the action.
     */
    float getDuration() const { return _duration; }
    /** Set duration in seconds of the action. 
     *
     * @param duration In seconds of the action.
     */
    void setDuration(float duration) { _duration = duration; }

    //
    // Overrides
    //
    virtual FiniteTimeAction* reverse() const override
    {
        CC_ASSERT(0);
        return nullptr;
    }
    virtual FiniteTimeAction* clone() const override
    {
        CC_ASSERT(0);
        return 
    }

瞬时动作基类ActionInstant

  • 简介

瞬时动作基类ActionInstant,顾名思义,继承自该类实现的动作类型都是在一帧内就执行完成,例如:show、hide、callback等

  • 原理

step调用后传给update直接是1,进度直接搞满

void ActionInstant::step(float /*dt*/)
{
    float updateDt = 1;
#if CC_ENABLE_SCRIPT_BINDING
    if (_scriptType == kScriptTypeJavascript)
    {
        if (ScriptEngineManager::sendActionEventToJS(this, kActionUpdate, (void *)&updateDt))
            return;
    }
#endif
    update(updateDt);
}

void ActionInstant::update(float /*time*/)
{
    _done = true;
}
  • 演示实例 (show)

Show* Show::create() 
{
    Show* ret = new (std::nothrow) Show();

    if (ret)
    {
        ret->autorelease();
    }

    return ret;
}

void Show::update(float time)
{
    ActionInstant::update(time);
    _target->setVisible(true);
}

ActionInstant* Show::reverse() const
{
    return Hide::create();
}

Show* Show::clone() const
{
    // no copy constructor
    return Show::create();
}

延时动作基类ActionInterval

  • 简介

延时动作基类所有继承自该类写的action子类,都是需要一定的时间去完成,例如:scale、move、fade等

  • 原理

初始化的时候存了两个变量_duration记录总时间,_elapsed记录执行时间,比值计算当前完成进度,传给update去刷新进度

bool ActionInterval::initWithDuration(float d)
{
    _duration = d;
    _elapsed = 0;
    _firstTick = true;
    _done = false;
    return true;
}
void ActionInterval::step(float dt)
{
    if (_firstTick)
    {
        _firstTick = false;
        _elapsed = 0;
    }
    else
    {
        _elapsed += dt;
    }
    float updateDt = MAX (0,                                 
                           MIN(1, _elapsed / _duration)
                           );
    if (sendUpdateEventToScript(updateDt, this)) return;
    this->update(updateDt);
    _done = _elapsed >= _duration;
}
  • 演示示例 (moveBy)

MoveBy* MoveBy::create(float duration, const Vec2& deltaPosition)
{
    return MoveBy::create(duration, Vec3(deltaPosition.x, deltaPosition.y, 0));
}
MoveBy* MoveBy::create(float duration, const Vec3 &deltaPosition)
{
    MoveBy *ret = new (std::nothrow) MoveBy();
    if (ret && ret->initWithDuration(duration, deltaPosition))
    {
        ret->autorelease();
        return ret;
    }
    delete ret;
    return nullptr;
}
bool MoveBy::initWithDuration(float duration, const Vec2& deltaPosition)
{
    return MoveBy::initWithDuration(duration, Vec3(deltaPosition.x, deltaPosition.y, 0));
}
bool MoveBy::initWithDuration(float duration, const Vec3& deltaPosition)
{
    bool ret = false;
    if (ActionInterval::initWithDuration(duration))
    {
        _positionDelta = deltaPosition;
        _is3D = true;
        ret = true;
    }
    return ret;
}
MoveBy* MoveBy::clone() const
{
    // no copy constructor
    return MoveBy::create(_duration, _positionDelta);
}
void MoveBy::startWithTarget(Node *target)
{
    ActionInterval::startWithTarget(target);
    _previousPosition = _startPosition = target->getPosition3D();
}
MoveBy* MoveBy::reverse() const
{
    return MoveBy::create(_duration, -_positionDelta);
}
void MoveBy::update(float t)
{
    if (_target)
    {
#if CC_ENABLE_STACKABLE_ACTIONS
        Vec3 currentPos = _target->getPosition3D();
        Vec3 diff = currentPos - _previousPosition;
        _startPosition = _startPosition + diff;
        Vec3 newPos =  _startPosition + (_positionDelta * t);
        _target->setPosition3D(newPos);
        _previousPosition = newPos;
#else
        _target->setPosition3D(_startPosition + _positionDelta * t);
#endif // CC_ENABLE_STACKABLE_ACTIONS
    }
}


action管理类ActionManager

  • 简介

ActionManager管理了所有节点执行action相关的内容

  • 原理

执行的action和target都会被存入到一个结构体内,结构体内还存了一些状态,暂停啥的, 每run一个action,增加到当前链表内

typedef struct _hashElement
{
    struct _ccArray     *actions;
    Node                *target;
    int                 actionIndex;
    Action              *currentAction;
    bool                currentActionSalvaged;
    bool                paused;
    UT_hash_handle      hh;
} tHashElement;
    

在ActionManager的update内会每帧都在遍历链表内所有结构体能够执行的action,然后执行他们的step方法,结束了就执行对应的结束方法,暂停和恢复都是对结构体内状态的调整

// main loop
void ActionManager::update(float dt)
{
    for (tHashElement *elt = _targets; elt != nullptr; )
    {
        _currentTarget = elt;
        _currentTargetSalvaged = false;

        if (! _currentTarget->paused)
        {
            // The 'actions' MutableArray may change while inside this loop.
            for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num;
                _currentTarget->actionIndex++)
            {
                _currentTarget->currentAction = static_cast<Action*>(_currentTarget->actions->arr[_currentTarget->actionIndex]);
                if (_currentTarget->currentAction == nullptr)
                {
                    continue;
                }

                _currentTarget->currentActionSalvaged = false;

                _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);
        }
        //if some node reference 'target', it's reference count >= 2 (issues #14050)
        else if (_currentTarget->target->getReferenceCount() == 1)
        {
            deleteHashElement(_currentTarget);
        }
    }

    // issue #635
    _currentTarget = nullptr;
}

推送

Github:https://github.com/KingSun5


结语

希望看到最后的同学有所收获,若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。
QQ交流群:806091680(Chinar)
该群为CSDN博主Chinar所创,推荐一下!我也在群里!
本文属于原创文章,转载请著名作者出处并置顶!!!!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值