zoukankan      html  css  js  c++  java
  • Cocos动画系统

    动画系统也是Cocos的UI中一个重要的模块,今天对它的运作进行解析。

    Action类的介绍

    一个动画的基类是Action,其声明如下:

    class CC_DLL Action : public Ref, public Clonable
    {
    public:
        virtual std::string description() const;
        virtual bool isDone() const;
        virtual void startWithTarget(Node *target);
        virtual void stop();
        virtual void step(float dt);
        virtual void update(float time);
        Node* getTarget() const { return _target; }
        void setTarget(Node *target) { _target = target; }
        Node* getOriginalTarget() const { return _originalTarget; }
        void setOriginalTarget(Node *originalTarget) { _originalTarget = originalTarget; }
        int getTag() const { return _tag; }
        void setTag(int tag) { _tag = tag; }
        unsigned int getFlags() const { return _flags; }
        void setFlags(unsigned int flags) { _flags = flags; }
    
    CC_CONSTRUCTOR_ACCESS:
        Action();
        virtual ~Action();
    
    protected:
        Node    *_originalTarget;
        Node    *_target;
        int     _tag;
        unsigned int _flags;
    };
    

    基类中主要包含播放动画的节点_target,用于记录动画信息的_tag,以及step、update等虚函数。
    Action又继承出ActionInstant和ActionInterval,其中ActionInstant指的是那些立即执行的操作,例如visible,flip等操作;ActionInterval指的是那些要持续一段时间的操作,在Cocos的UI工程中设置的Scale、Rotate、变色、透明度等大部分都是这种。
    它们的类图关系如下:

    动画的执行逻辑

    接下来我们来看一看Cocos在运行时是如何播放动画的。
    Cocos有一个统一的ActionManager来记录当前所有的动画,并执行它们的播放操作。具体的逻辑是,Director中存储了actionManager对象,在初始化的时候将它的update方法注册到了scheduler中,这样就保证了每一帧都会由scheduler调用actionManager的update方法:

    bool Director::init(void)
    {    
        // some code....
        // action manager
        _actionManager = new (std::nothrow) ActionManager();
        _scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false);
        // some code....
    }
    

    然后我们看看ActionManager的update函数:

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

    它会遍历当前所有附加了Action的节点,调用它们的step函数,并传入这一帧的delta time。
    我们以一个简单的动画效果Scale为例,来看看step里到底都做了什么,先看看ScaleTo类的声明:

    class CC_DLL ScaleTo : public ActionInterval
    {
    public:
        static ScaleTo* create(float duration, float s);
        static ScaleTo* create(float duration, float sx, float sy);
        static ScaleTo* create(float duration, float sx, float sy, float sz);
        virtual void update(float time) override;
        
    CC_CONSTRUCTOR_ACCESS:
        ScaleTo() {}
        virtual ~ScaleTo() {}
        bool initWithDuration(float duration, float s);
        bool initWithDuration(float duration, float sx, float sy);
        bool initWithDuration(float duration, float sx, float sy, float sz);
    
    protected:
        float _startScaleX;
        float _startScaleY;
        float _startScaleZ;
        float _endScaleX;
        float _endScaleY;
        float _endScaleZ;
        float _deltaX;
        float _deltaY;
        float _deltaZ;
    
    private:
        CC_DISALLOW_COPY_AND_ASSIGN(ScaleTo);
    };
    

    这个类里记录了目标scale的XYZ、初始scale的XYZ以及中间的变化ScaleXYZ。
    它没有step函数的定义,其实这个函数是在它的父类里:

    void ActionInterval::step(float dt)
    {
        if (_firstTick)
        {
            _firstTick = false;
            _elapsed = 0;
        }
        else
        {
            _elapsed += dt;
        }
        
        
        float updateDt = std::max(0.0f,                                  // needed for rewind. elapsed could be negative
        std::min(1.0f, _elapsed / _duration));
    
        if (sendUpdateEventToScript(updateDt, this)) return;
        
        this->update(updateDt);
    
        _done = _elapsed >= _duration;
    }
    

    可以看到,step函数会把dt化为[0,1]之间的比例,最后传入Action自己定义的update函数中,ScaleTo函数的update还是比较简单的:

    void ScaleTo::update(float time)
    {
        if (_target)
        {
            _target->setScaleX(_startScaleX + _deltaX * time);
            _target->setScaleY(_startScaleY + _deltaY * time);
            _target->setScaleZ(_startScaleZ + _deltaZ * time);
        }
    }
    

    其余的Action函数原理也类似。
    总而言之,Cocos在scheduler中注册actionManager的方法,在每一帧开头调用actionManager的update函数。该函数会计算所有绑定了action的UI节点,将传入的dt转换为[0,1]之间的值,最后由具体的Action来执行相应的插值操作,计算出UI节点的属性值并在之后进行渲染。这就是CocosUI动画系统的执行逻辑。

  • 相关阅读:
    wss的webpart的3种开发方式(转载)
    C# 2.0学习之集合2
    对C#中的TreeView添加背景图转载
    ASP.NET 2.0: 页面中链入的CSS、js文件带中文时需注意
    C# 2.0学习之泛型
    C# 2.0学习之数组
    连接 ACCESS 2007
    C# 2.0学习之事件2
    一个P2P+搜索音乐网站的策划书(转载)
    关于MOSS的应用和开发的一些联接
  • 原文地址:https://www.cnblogs.com/wickedpriest/p/12242421.html
Copyright © 2011-2022 走看看