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的內存管理機制。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM