zoukankan      html  css  js  c++  java
  • Cocos2d-X3.0 刨根问底(三)----- Director类源码分析

    上一章我们完整的跟了一遍HelloWorld的源码,了解了Cocos2d-x的启动流程。其中Director这个类贯穿了整个Application程序,这章随小鱼一起把这个类分析透彻。

    小鱼的阅读源码的习惯是,一层层地分析代码,在阅读Director这个类的时候,碰到了很多其它的Cocos2d-x类,我的方式是先大概了解一下类的作用,完整的去了解Director类,之后再去按照重要程度去分析碰到的其它类。

    一点一点分析 CCDirector.h

    #ifndef __CCDIRECTOR_H__
    #define __CCDIRECTOR_H__
    
    #include "CCPlatformMacros.h"
    
    #include "CCRef.h"
    #include "ccTypes.h"
    #include "CCGeometry.h"
    #include "CCVector.h"
    #include "CCGL.h"
    #include "CCLabelAtlas.h"
    #include "kazmath/mat4.h"
    
    
    NS_CC_BEGIN
    
    /**
     * @addtogroup base_nodes
     * @{
     */
    
    /* Forward declarations. */
    class LabelAtlas;
    class Scene;
    class GLView;
    class DirectorDelegate;
    class Node;
    class Scheduler;
    class ActionManager;
    class EventDispatcher;
    class EventCustom;
    class EventListenerCustom;
    class TextureCache;
    class Renderer;
    
    #if  (CC_TARGET_PLATFORM != CC_PLATFORM_WINRT) && (CC_TARGET_PLATFORM != CC_PLATFORM_WP8)
    class Console;
    #endif

    从ccdirector.h的包含文件和引用的类来看,我们可以看到Director类都管些什么,做个初步了解。

    管理的有 Label(标签) 、Scene(场景)、 GLView(OpenGL渲染) 、Node(结点?不知道是什么玩意后面我们再仔细分析)、Scheduler(程序调度)、ActionManager(动画管理)、EventDispatcher(事件管理)、EventCuston(也和事件有关)、EventListenerCuston(事件侦听有关系)、TextureCache(纹理缓存)、Renderer(渲染器)、Console(控制台)

    这个大管家管了这么多东西,后面的章节我来逐个分析这些东西是什么,现在只要不阻碍分析Director这个大管家类,可以暂时不用理会其它类的实现的具体内容。

    继续往下看Director类的具体定义

    class CC_DLL Director : public Ref

    Director类继承了 Ref类,参看Ref类的定义可以大体了解到是一个用来做引用记数的类,相关还有PoolManager ,AutoreleasePool, 等,从命名可以了解cocos2d-x有自己的内存管理机制,用到了引用记数来确定对象是否应该释放,相应的有管理类来控制。后面我们单独去分析coocs2d-x的内存管理。在这里只知道Director类也是由统一的内存管理器来控制的。

    下面看一下Director类的公有函数

    static const char *EVENT_PROJECTION_CHANGED;
        
    static const char* EVENT_AFTER_UPDATE;
        
    static const char* EVENT_AFTER_VISIT;
        
    static const char* EVENT_AFTER_DRAW;
    const char *Director::EVENT_PROJECTION_CHANGED = "director_projection_changed";
    const char *Director::EVENT_AFTER_DRAW = "director_after_draw";
    const char *Director::EVENT_AFTER_VISIT = "director_after_visit";
    const char *Director::EVENT_AFTER_UPDATE = "director_after_update";

    最开始定义了几个事件Director类的事件类型, 依次是 工程类型改变,draw(渲染) visit(访问) update(更新)之后的事件 可猜测 当 draw visit update之后director可抛出相应事件外部捕获后进后自己的处理。

    enum class Projection
        {
            /// sets a 2D projection (orthogonal projection)
            _2D,
            
            /// sets a 3D projection with a fovy=60, znear=0.5f and zfar=1500.
            _3D,
            
            /// it calls "updateProjection" on the projection delegate.
            CUSTOM,
            
            /// Default projection is 3D projection
            DEFAULT = _3D,
        };

    这个枚举定义了工程类型 有 2D 3D 和自定义,默认为3D游戏类型。

    /** returns a shared instance of the director */
        static Director* getInstance();
    
        /** @deprecated Use getInstance() instead */
        CC_DEPRECATED_ATTRIBUTE static Director* sharedDirector() { return Director::getInstance(); }
        /**
         * @js ctor
         */
        Director(void);
        /**
         * @js NA
         * @lua NA
         */
        virtual ~Director();

    这段代码可以知道 Director也是单例创建型。并且提供了外部得到实例的接口sharedDirector;

    virtual bool init();

    init整个director对象的初始化工作都在这里面。这个函数很重要,我们稍后单独分析它

    后面代码里很多方法注释里面已经描述的很详细了,这里我们简单过一遍,大多是些Get Set的方法。

    /** 得到director当前正在运行的场景,director同一时间只能有一个场景在运行*/
        inline Scene* getRunningScene() { return _runningScene; }
    
        /** 得到动画的帧速率*/
        inline double getAnimationInterval() { return _animationInterval; }
        /** 设置动画的帧频,这里看到这是一个纯虚函数,所以Director是一个抽象类,不能被实例化,使用的时候必须继承这个类开实现自己的Director. */
        virtual void setAnimationInterval(double interval) = 0;
    
        /** 询问是否在左下角显示帧频,我们看helloworld里面有一个fps显示,这里应该就是控制显示fps的地方 */
        inline bool isDisplayStats() { return _displayStats; }
        /** 设置是否要在左下角显示帧频*/
        inline void setDisplayStats(bool displayStats) { _displayStats = displayStats; }
        
        /** 得到每一帧消耗时间多少秒 如每秒60的帧频那么这个返回值就是 1/60秒*/
        inline float getSecondsPerFrame() { return _secondsPerFrame; }
    /** 得到封装OpenGl操作的对象GLView的接口 
        * @js NA
        * @lua NA
        */
        inline GLView* getOpenGLView() { return _openGLView; }
        void setOpenGLView(GLView *openGLView);

    纹理缓存的对象

    TextureCache* getTextureCache() const;

    下面几个函数是用来控制游戏循环中帧与帧之间的时间间隔的,其中涉及到两个成员变量

    /* 标记是否下次帧逻辑时是否清除(忽略)_deltaTime */
        bool _nextDeltaTimeZero;    
    /* 上一次逻辑帧运行到当前的时间间隔,用来判断是否应该进行下次逻辑帧,上一次帧执行的时间记录在 _lastUpdate 变量里面*/
        float _deltaTime;

    下面两个函数是用来操作下一次的 _deltaTime是否有效的,当整个游戏暂停的时候,这时_deltaTime会不断累计,就会用到了_nextDeltaTimeZero这个变量,标记着下次的_deltaTime为0这样就会不出现恢复暂停后跳帧,而是继续当前帧顺序开始。

    inline bool isNextDeltaTimeZero() { return _nextDeltaTimeZero; }
        void setNextDeltaTimeZero(bool nextDeltaTimeZero);

    计算_deltaTime的函数,会在每个逻辑循环里面都调用。

    /** 计算 deltaTime 上次逻辑帧调用的时间和当前时间的时间间隔。如果 nextDeltaTimeZero为true则deltaTime为0*/    
        void calculateDeltaTime();    
    /*上次主循环帧执行到当前的时间间隔 _deltaTime*/
        float getDeltaTime() const;

    继续看代码

    /** 询问当前是否是暂停状态 游戏暂停用 _paused这个变量记录 */
        inline bool isPaused() { return _paused; }
    
        /** director运行后一共执行了多少帧*/
        inline unsigned int getTotalFrames() { return _totalFrames; }
        
        /** 设置/读取 _projection变量,标记工程类型 2d?3d?
         @since v0.8.2
         * @js NA
         * @lua NA
         */
        inline Projection getProjection() { return _projection; }
        void setProjection(Projection projection);
        
        /** 设置opengl的viewport*/
        void setViewport();

    下面是一些坐标的操作方法

    /** 可以得到通知消息的node结点,具体后面分析Node再讨论,现在大概了解一下*/
        Node* getNotificationNode() const { return _notificationNode; }
        void setNotificationNode(Node *node);
        
        // 下面是设置和获得窗口尺寸的一些函数 注释已经很详细了,这里就不翻译了
        /** returns the size of the OpenGL view in points.
        */
        const Size& getWinSize() const;
    
        /** returns the size of the OpenGL view in pixels.
        */
        Size getWinSizeInPixels() const;
        
        /** returns visible size of the OpenGL view in points.
         *  the value is equal to getWinSize if don't invoke
         *  GLView::setDesignResolutionSize()
         */
        Size getVisibleSize() const;
        
        /** returns visible origin of the OpenGL view in points.
         */
        Point getVisibleOrigin() const;
    
        /** converts a UIKit coordinate to an OpenGL coordinate
         Useful to convert (multi) touch coordinates to the current layout (portrait or landscape)
         */
        Point convertToGL(const Point& point);
    
        /** converts an OpenGL coordinate to a UIKit coordinate 坐标转换
         Useful to convert node points to window points for calls such as glScissor
         */
        Point convertToUI(const Point& point);
    
        /// XXX: missing description 
        float getZEye() const;

    下面是场景管理的一些方法 这部分挺重要的,我们深入分析

    先看一下关于Scene场景的一些属性

    /* 当前正在执行的场景,由这个变量可以知道,Cocos2d-x同一时间只能执行一个场景。*/
        Scene *_runningScene;
        
        /* 下一个要执行的场景,这块肯定是在场景切换的时候要用到的 */
        Scene *_nextScene;
        
        /* 是否清除场景的标记,当为真时,旧的场景就收到清除消息 */
        bool _sendCleanupToScene;
    
        /* 场景的堆栈 */
        Vector<Scene*> _scenesStack;

    通过这几个关于场景的属性可以大体了解到,Cocos2d-x同时只能执行一个场景,场景切换的时候有一个 _nextScene。清除场景时有一个标记 _scendCleanupToScene,等待执行的场景都存在 一个栈里面 _scenesStack

    /** 设置要执行的场景     */
        void runWithScene(Scene *scene);
    
        /** 将新的场景加入到执行堆栈里面,新加入的场景将会被立即执行,使用的时候避免这个堆栈里的场景太多,防止设备内存不足,当已经有场景在执行的时候可以调用此方法来切换场景     */
        void pushScene(Scene *scene);
    
        /** 从堆栈中弹出最后加入的场景,在使用这个函数的时候要确保已经有一个场景在执行且在堆栈里面。弹出的场景会被清除,如果栈空了,那么Director就会停止     */
        void popScene();
    
        /** 通过调用 `popToSceneStackLevel(1)` 这个方法来实现清理栈里的场景只留下根场景,就是剩下第一个入栈的场景     */
        void popToRootScene();
    
        /** 按栈的层次来清理栈里的场景,level=0全清除 =1时 为 popToRootScene() 如果值超出了栈里的场景数量则不处理     */
         void popToSceneStackLevel(int level);
    
        /** 当有场景在执行的时候,替换当前运行的场景     */
        void replaceScene(Scene *scene);
    
        /** 停止当前场景     */
        void end();
    
        /** 暂停场景     */
        void pause();
    
        /** 暂停后恢复场景
         */
        void resume();
    /** 停止动画及所有逻辑     */
        virtual void stopAnimation() = 0;
    
        /**开始动画循环
         */
        virtual void startAnimation() = 0;
    
        /** 渲染场景
        */
        void drawScene();

    下面是一些内存控制的

    /** 清除Direct的内存缓存,看下源码可以大概了解都有字体,纹理,文件等内存资源     */
        void purgeCachedData();
    
        /** 设置默认值,具体有哪些看下代码就知道了,很清楚写的*/
        void setDefaultValues();

    OpenGl的一些操作

    /** 设置OpenGl的默认值*/
        void setGLDefaultValues();
    
        /** 设置是否开启透明*/
        void setAlphaBlending(bool on);
    
        /** 设置是否开启深度测试*/
        void setDepthTest(bool on);

    Director主循环 所有Director场景逻辑都会在这里触发

    virtual void mainLoop() = 0;

    还有一些方法,简单过一遍,从命名上就可以知道大概的含义了,有些后面我们分章节来详细分析

    /** 设置/获得缩放比例    */
        void setContentScaleFactor(float scaleFactor);
        float getContentScaleFactor() const { return _contentScaleFactor; }
    
        /** 得到调度控制对象 ,这个Scheduler应该是类似一个定时期和一堆回调方法的东西,后面我们专门分析这玩意     */
        Scheduler* getScheduler() const { return _scheduler; }
        
        /** 设置定时器     */
        void setScheduler(Scheduler* scheduler);
    
        /** 获得、设置动作管理器对象    后面单独分析这个类 */
        ActionManager* getActionManager() const { return _actionManager; }
        void setActionManager(ActionManager* actionManager);
        
        /**事件分发器的 get set操作     后面单独分析这个类*/
        EventDispatcher* getEventDispatcher() const { return _eventDispatcher; }
        void setEventDispatcher(EventDispatcher* dispatcher);
    
        /** 渲染器 后面单独分析这个类     */
        Renderer* getRenderer() const { return _renderer; }

    上面解剖了Director类,有几个方法我们着重看一下

    先看返回单例对象的方法

    Director* Director::getInstance()
    {
        if (!s_SharedDirector)
        {
            s_SharedDirector = new DisplayLinkDirector();
            s_SharedDirector->init();
        }
    
        return s_SharedDirector;
    }

    值得注意的是,返回的是DisplayLinkDirector这个类,并且在创建完 DisplayLinkDirector对象后调用了init方法,

    咱们先不管DisplayLinkDirector类是什么,肯定是一个Director的一个子类,因为Director是一个抽象类

    先看一下init方法 从这个方法里面我们再一次了解一下,Director具体都能干什么,和一些内部初始化的工作是怎么完成的

    bool Director::init(void)
    {
        setDefaultValues();
    
        // scenes
        _runningScene = nullptr;
        _nextScene = nullptr;
    
        _notificationNode = nullptr;
    
        _scenesStack.reserve(15);
    
        // FPS
        _accumDt = 0.0f;
        _frameRate = 0.0f;
        _FPSLabel = _drawnBatchesLabel = _drawnVerticesLabel = nullptr;
        _totalFrames = _frames = 0;
        _lastUpdate = new struct timeval;
    
        // paused ?
        _paused = false;
    
        // purge ?
        _purgeDirectorInNextLoop = false;
    
        _winSizeInPoints = Size::ZERO;
    
        _openGLView = nullptr;
    
        _contentScaleFactor = 1.0f;
    
        // scheduler
        _scheduler = new Scheduler();
        // action manager
        _actionManager = new ActionManager();
        _scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false);
    
        _eventDispatcher = new EventDispatcher();
        _eventAfterDraw = new EventCustom(EVENT_AFTER_DRAW);
        _eventAfterDraw->setUserData(this);
        _eventAfterVisit = new EventCustom(EVENT_AFTER_VISIT);
        _eventAfterVisit->setUserData(this);
        _eventAfterUpdate = new EventCustom(EVENT_AFTER_UPDATE);
        _eventAfterUpdate->setUserData(this);
        _eventProjectionChanged = new EventCustom(EVENT_PROJECTION_CHANGED);
        _eventProjectionChanged->setUserData(this);
    
    
        //init TextureCache
        initTextureCache();
    
        _renderer = new Renderer;
    
    #if (CC_TARGET_PLATFORM != CC_PLATFORM_WINRT) && (CC_TARGET_PLATFORM != CC_PLATFORM_WP8)
        _console = new Console;
    #endif
        return true;
    }

    可以看到,Director这个大管家初始化了 ActionManager 动作管理器 并将 _actionManager加到了定时器里

    初始化了EventDispatcher EventCustom 等事件

    初始化了纹理 和渲染器 Renderer

    下面我们再看一下DisplayLinkDirector这个类

    这是Director的实体类。

    class DisplayLinkDirector : public Director
    {
    public:
        DisplayLinkDirector() 
            : _invalid(false)
        {}
    
        //
        // Overrides
        //
        virtual void mainLoop() override;
        virtual void setAnimationInterval(double value) override;
        virtual void startAnimation() override;
        virtual void stopAnimation() override;
    
    protected:
        bool _invalid;
    };

    这个类实现了Director的几个关键的虚函数。

    mainLoop这个是最主要的了,上面我们一再说逻辑循环 逻辑循环,其实都是指这个函数,所有的操作,动画,渲染,定时器都在这里驱动的。

    游戏主循环里反复的调度 mainLoop来一帧一帧的实现游戏的各种动作,动画……. mainLoop来决定当前帧该执行什么,是否到时间执行等等。

    void DisplayLinkDirector::mainLoop()
    {
        if (_purgeDirectorInNextLoop)
        {
            _purgeDirectorInNextLoop = false;
            purgeDirector();
        }
        else if (! _invalid)
        {
            drawScene();
         
            // release the objects
            PoolManager::getInstance()->getCurrentPool()->clear();
        }
    }

    代码很简单,根据对 purgeDirectorInNextLoop 判断来决定mainLoop是否应该清除。

    _invalid来决定 Director是否应该进行逻辑循环

    这段代码很简单,主要操作都封闭到了 drawScene里面后面我们跟进drawScene来看看每个逻辑帧都干了些什么。

    后面还有一个代码PoolManager::getInstance()->getCurrentPool()->clear(); 从命名上来看是做清除操作,应该是内存操作,每帧回收不用的引用对象应该是在这里触发的,我们在内存应用的章节再回过头来讨论这块。

    下面看drawScene的代码

    void Director::drawScene()
    {
        // 计算帧之间的时间间隔,下面根据这个时间间隔来判断是否应该进行某某操作
        calculateDeltaTime();
        
        // skip one flame when _deltaTime equal to zero.
        if(_deltaTime < FLT_EPSILON)
        {
            return;
        }
    
        if (_openGLView)
        {
            _openGLView->pollInputEvents();
        }
    
        //Director没有暂停的情况下,更新定时器,分发 update后的消息
        if (! _paused)
        {
            _scheduler->update(_deltaTime);
            _eventDispatcher->dispatchEvent(_eventAfterUpdate);
        }
        //opengl清理
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        /*设置下个场景*/
        if (_nextScene)
        {
            setNextScene();
        }
    
        kmGLPushMatrix();
    
        // global identity matrix is needed... come on kazmath!
        kmMat4 identity;
        kmMat4Identity(&identity);
    
        // 渲染场景
        if (_runningScene)
        {
            _runningScene->visit(_renderer, identity, false);
            // 分发场景渲染后的消息 
            _eventDispatcher->dispatchEvent(_eventAfterVisit);
        }
    
        // 渲染notifications 结点,这个结点有什么用现在还看不太清楚,后面章节我们一定会摸清楚的
        if (_notificationNode)
        {
            _notificationNode->visit(_renderer, identity, false);
        }
    
        if (_displayStats)// 渲染 FPS等帧频显示
        {
            showStats();
        }
    
        _renderer->render(); // 调用了渲染器的render方法,具体我们在分析Render类的时候再回过来看都干了些什么
        _eventDispatcher->dispatchEvent(_eventAfterDraw);
    
        kmGLPopMatrix();
    
        _totalFrames++;
    
        // swap buffers
        if (_openGLView)
        {
            _openGLView->swapBuffers();
        }
    
        if (_displayStats)
        {
            calculateMPF();
        }
    }

    到现在,我们完整的分析了Director类,了解了这个大管家都管理了哪些对象。下面我们做个总结。

    Director主要管理了场景,四个事件的分发,渲染, Opengl对象,等

    它主要是以场景为单位来控制游戏的逻辑帧,通过场景的切换来实现游戏中不同界面的变化。

    其实 mainloop这个函数 调用 了drawscene来实现每一帧的逻辑主要是渲染逻辑。

    上一章节,我们读到了application里面有一个run方法 ,在run方法里面有一个死循环,那个是游戏的主循环,在那个死循环里不断的调用 director->mainLoop这个就是在主游戏循环里不断的执行逻辑帧的操作.

    下一节我们从最基本的开始分析,看一下 ref这个类cocos2d-x的内存管理机制。

    选择游戏作为职业
  • 相关阅读:
    switch能否作用在作用在byte、long、string上面?
    websocket(转)
    equal和hashcode、==
    List常用方法
    String,Integer,Double等类型互相转换
    BigDecimal的转换和使用
    gitHub常用命令和技巧
    SQL语句
    SpringBoot注解
    vue格式化时间
  • 原文地址:https://www.cnblogs.com/mmidd/p/3732689.html
Copyright © 2011-2022 走看看