Cocos2d-X3.0 刨根問底(三)----- Director類源碼分析

來源:互聯網
上載者:User

上一章我們完整的跟了一遍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的記憶體管理機制。





聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.