cocos2d-x的主循环和导演类

继上一篇文章中讲到,当cocos2d-x游戏加载完场景后,便将程序的拥有权交给导演类的主循环去控制,这样可以让游戏不断的处理和监听用户的输入,处理和监听定时的事件和绘图渲染等。

主循环会每帧调用一次(默认是每秒60帧,即一秒调用60次这个主循环函数),所以,当调用的主循环计算量过大,就会无法保证每秒完成60次的调用,而导致帧率降低,影响游戏的流畅性。

mainLoop()方法是定义再CCDirector中的抽象方法,它的实现是它的一个子类CCDisplayLinkDirector去实现。


代码如下:

void CCDisplayLinkDirector::mainLoop(void)
{
    if (m_bPurgeDirecotorInNextLoop)//判断在下一帧游戏是否结束
    {
        m_bPurgeDirecotorInNextLoop = false;
        purgeDirector();//释放资源
    }
    else if (! m_bInvalid)//如果没有暂停,则继续渲染和管理内存
     {
         drawScene();//渲染场景
     
         // release the objects
         CCPoolManager::sharedPoolManager()->pop();//回收内存        
     }
}

代码很简单,首先判断游戏结束的标志位是否为真。

这个标志位有导演类定义和控制

bool m_bPurgeDirecotorInNextLoop; // this flag will be set to true in end()
注释上也写得很清楚,这个标志位可以调用end()方法来把它置为true。

void CCDirector::end()
{
    m_bPurgeDirecotorInNextLoop = true;
}

为true的话则删除和释放导演的资源,否则再判断是否调用了

void CCDisplayLinkDirector::stopAnimation(void)
{
    m_bInvalid = true;
}
这个函数来暂停渲染,通常这个函数一般会游戏进入后台时去调用,也就是上一篇文章中引擎自带的例子中的AppDelegate::applicationDidEnterBackground函数调用了它。

接下来就是场景的渲染,其操作了除内存管理外的所有操作,因此这个方法的实现非常重要:

void CCDirector::drawScene(void)
{
	//计算全局帧间的时间差
    calculateDeltaTime();

    //处理定时器的调用
    if (! m_bPaused)
    {
        m_pScheduler->update(m_fDeltaTime);
    }
    //清除颜色和深度缓冲
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //是否需要切换场景
    if (m_pNextScene)
    {
        setNextScene();
    }
    //OpenGL矩阵入栈
    kmGLPushMatrix();

    //绘制渲染场景
    if (m_pRunningScene)
    {
        m_pRunningScene->visit();
    }

    //处理通知节点
    if (m_pNotificationNode)
    {
        m_pNotificationNode->visit();
    }
    //是否显示FPS
    if (m_bDisplayStats)
    {
        showStats();
    }
    //OpenGL矩阵出栈
    kmGLPopMatrix();
    //渲染的总帧数加1
    m_uTotalFrames++;

    //交换缓冲
    if (m_pobOpenGLView)
    {
        m_pobOpenGLView->swapBuffers();
    }
    //计算每帧渲染所需的时间
    if (m_bDisplayStats)
    {
        calculateMPF();
    }
}
可以看到drawScene主要处理了以下的三个操作

1)定时器

2)场景切换

3)渲染场景

当然也处理一些其他细节,如计算帧差,OpenGL的处理等。


再接下来就是内存管理(这个单独抽成一篇文章来讲,略过)。处理完内存管理后便完成了一帧的调用。而游戏就是这样周而复始的运行这个主循环来刷新画面。


cocos2d

在cocos2d-x中导演类扮演着一个非常中要的角色,它是整个游戏流程控制的上级组件。它包括了很多的游戏控制操作:

class CC_DLL CCDirector : public CCObject, public TypeInfo
{
public:
    /**
     *  @js ctor
     */
	//构造
    CCDirector(void);
    /**
     *  @js NA
     *  @lua NA
     */
    //析构
    virtual ~CCDirector(void);
    //初始化
    virtual bool init(void);
    /**
     * @js NA
     * @lua NA
     */
    //获取导演类的id
    virtual long getClassTypeInfo() {
		static const long id = cocos2d::getHashCodeByString(typeid(cocos2d::CCDirector).name());
		return id;
    }

    // attribute

    /** Get current running Scene. Director can only run one Scene at the time */
    //返回当前正在运行的场景。(整个游戏只能运行一个场景)
    inline CCScene* getRunningScene(void) { return m_pRunningScene; }

    /** Get the FPS value */
    //获取帧数值
    inline double getAnimationInterval(void) { return m_dAnimationInterval; }
    /** Set the FPS value. */
    //设置帧数
    virtual void setAnimationInterval(double dValue) = 0;

    /** Whether or not to display the FPS on the bottom-left corner */
    //是否显示FPS
    inline bool isDisplayStats(void) { return m_bDisplayStats; }
    /** Display the FPS on the bottom-left corner */
    //在屏幕的左下角显示FPS
    inline void setDisplayStats(bool bDisplayStats) { m_bDisplayStats = bDisplayStats; }
    
    /** seconds per frame */
    //获取一帧的渲染时间
    inline float getSecondsPerFrame() { return m_fSecondsPerFrame; }

    /** Get the CCEGLView, where everything is rendered
     * @js NA
     */
    //获取OpenGL窗口
    inline CCEGLView* getOpenGLView(void) { return m_pobOpenGLView; }
    //设置OpenGL窗口
    void setOpenGLView(CCEGLView *pobOpenGLView);
    //???
    inline bool isNextDeltaTimeZero(void) { return m_bNextDeltaTimeZero; }
    void setNextDeltaTimeZero(bool bNextDeltaTimeZero);

    /** Whether or not the Director is paused */
    //是否暂停
    inline bool isPaused(void) { return m_bPaused; }

    /** How many frames were called since the director started */
    //获取总帧数
    inline unsigned int getTotalFrames(void) { return m_uTotalFrames; }
    
    /** Sets an OpenGL projection
     @since v0.8.2
     @js NA
     */
    //OpenGL投影相关
    inline ccDirectorProjection getProjection(void) { return m_eProjection; }
    void setProjection(ccDirectorProjection kProjection);
     /** reshape projection matrix when canvas has been change"*/
    void reshapeProjection(const CCSize& newWindowSize);
    
    /** Sets the glViewport*/
    //设置OpenGL的窗口
    void setViewport();

    /** How many frames were called since the director started */
    
    
    /** Whether or not the replaced scene will receive the cleanup message.
     If the new scene is pushed, then the old scene won't receive the "cleanup" message.
     If the new scene replaces the old one, the it will receive the "cleanup" message.
     @since v0.99.0
     */
    //获取场景切换时是否需要收到清除的消息
    //如果新场景入栈,那么旧场景将不会收到消息(对应pushScene)
    //如果新场景替换了旧场景,则会收到消息(对应 replaceScene)
    inline bool isSendCleanupToScene(void) { return m_bSendCleanupToScene; }

    /** This object will be visited after the main scene is visited.
     This object MUST implement the "visit" selector.
     Useful to hook a notification object, like CCNotifications (http://github.com/manucorporat/CCNotifications)
     @since v0.99.5
     */
    //通知节点相关
    CCNode* getNotificationNode();
    void setNotificationNode(CCNode *node);
    
    /** CCDirector delegate. It shall implemente the CCDirectorDelegate protocol
     @since v0.99.5
     */
    //自定义投影相关(当OpenGL每次进行投影操作时会调用CCDirectorDelegate中的updateProjection方法来时实现自定义)
    CCDirectorDelegate* getDelegate() const;
    void setDelegate(CCDirectorDelegate* pDelegate);

    // window size

    /** returns the size of the OpenGL view in points.
    */
    //获取OpenGL的屏幕大小
    CCSize getWinSize(void);

    /** returns the size of the OpenGL view in pixels.
    */
    //返回OpenGL的屏幕的像素点
    CCSize getWinSizeInPixels(void);
    
    /** returns visible size of the OpenGL view in points.
     *  the value is equal to getWinSize if don't invoke
     *  CCEGLView::setDesignResolutionSize()
     */
    //获取OpenGL的屏幕的可视化大小
    CCSize getVisibleSize();
    
    /** returns visible origin of the OpenGL view in points.
     */
    //获取OpenGL的屏幕的可视化的原点(通常配合getVisibleSize一起使用)
    CCPoint getVisibleOrigin();

    /** converts a UIKit coordinate to an OpenGL coordinate
     Useful to convert (multi) touch coordinates to the current layout (portrait or landscape)
     */
    //将UIKit坐标转换为OpenGL坐标
    CCPoint convertToGL(const CCPoint& obPoint);

    /** converts an OpenGL coordinate to a UIKit coordinate
     Useful to convert node points to window points for calls such as glScissor
     */
    //将OpenGL坐标转换为UIKit坐标
    CCPoint convertToUI(const CCPoint& obPoint);

    /// XXX: missing description 
    //???
    float getZEye(void);

    // Scene Management

    /** Enters the Director's main loop with the given Scene.
     * Call it to run only your FIRST scene.
     * Don't call it if there is already a running scene.
     *
     * It will call pushScene: and then it will call startAnimation
     */
    //运行某一场景
    void runWithScene(CCScene *pScene);

    /** Suspends the execution of the running scene, pushing it on the stack of suspended scenes.
     * The new scene will be executed.
     * Try to avoid big stacks of pushed scenes to reduce memory allocation. 
     * ONLY call it if there is a running scene.
     */
    //将场景压入栈中
    void pushScene(CCScene *pScene);

    /** Pops out a scene from the queue.
     * This scene will replace the running one.
     * The running scene will be deleted. If there are no more scenes in the stack the execution is terminated.
     * ONLY call it if there is a running scene.
     */
    //场景出栈
    void popScene(void);

    /** Pops out all scenes from the queue until the root scene in the queue.
     * This scene will replace the running one.
     * Internally it will call `popToSceneStackLevel(1)`
     */
    //将所有场景出栈,除了根场景
    void popToRootScene(void);

    /** Pops out all scenes from the queue until it reaches `level`.
     If level is 0, it will end the director.
     If level is 1, it will pop all scenes until it reaches to root scene.
     If level is <= than the current stack level, it won't do anything.
     */
    //以等级的方式出栈
 	void popToSceneStackLevel(int level);

    /** Replaces the running scene with a new one. The running scene is terminated.
     * ONLY call it if there is a running scene.
     */
 	//替换场景
    void replaceScene(CCScene *pScene);

    /** Ends the execution, releases the running scene.
     It doesn't remove the OpenGL view from its parent. You have to do it manually.
     */
    //终止游戏,但不会释放OpenGL view,需要手动移除
    void end(void);

    /** Pauses the running scene.
     The running scene will be _drawed_ but all scheduled timers will be paused
     While paused, the draw rate will be 4 FPS to reduce CPU consumption
     */
    //暂停当前正在运行的场景
    void pause(void);

    /** Resumes the paused scene
     The scheduled timers will be activated again.
     The "delta time" will be 0 (as if the game wasn't paused)
     */
    //恢复被暂停的场景
    void resume(void);

    /** Stops the animation. Nothing will be drawn. The main loop won't be triggered anymore.
     If you don't want to pause your animation call [pause] instead.
     */
    //暂停主循环的渲染和内存管理
    virtual void stopAnimation(void) = 0;

    /** The main loop is triggered again.
     Call this function only if [stopAnimation] was called earlier
     @warning Don't call this function to start the main loop. To run the main loop call runWithScene
     */
    //恢复主循环的渲染和内存管理
    virtual void startAnimation(void) = 0;

    /** Draw the scene.
    This method is called every frame. Don't call it manually.
    */
    //绘制场景
    void drawScene(void);

    // Memory Helper

    /** Removes cached all cocos2d cached data.
     It will purge the CCTextureCache, CCSpriteFrameCache, CCLabelBMFont cache
     @since v0.99.3
     */
    //清空缓存数据
    void purgeCachedData(void);

	/** sets the default values based on the CCConfiguration info */
    //初始化配置
    void setDefaultValues(void);

    // OpenGL Helper

    /** sets the OpenGL default values */
    //配置OpenGL的默认属性
    void setGLDefaultValues(void);

    /** enables/disables OpenGL alpha blending */
    //设置是否启动OpenGL的alpha通道
    void setAlphaBlending(bool bOn);

    /** enables/disables OpenGL depth test */
    //设置是否启动OpenGL的景深
    void setDepthTest(bool bOn);
    //主循环
    virtual void mainLoop(void) = 0;

    /** The size in pixels of the surface. It could be different than the screen size.
    High-res devices might have a higher surface size than the screen size.
    Only available when compiled using SDK >= 4.0.
    @since v0.99.4
    */
    //屏幕显示比例大小相关
    void setContentScaleFactor(float scaleFactor);
    float getContentScaleFactor(void);

public:
    /** CCScheduler associated with this director
     @since v2.0
     */
    //定时器
    CC_PROPERTY(CCScheduler*, m_pScheduler, Scheduler);

    /** CCActionManager associated with this director
     @since v2.0
     */
    //动作管理类
    CC_PROPERTY(CCActionManager*, m_pActionManager, ActionManager);

    /** CCTouchDispatcher associated with this director
     @since v2.0
     */
    //触摸分发
    CC_PROPERTY(CCTouchDispatcher*, m_pTouchDispatcher, TouchDispatcher);

    /** CCKeypadDispatcher associated with this director
     @since v2.0
     */
    //键盘分发
    CC_PROPERTY(CCKeypadDispatcher*, m_pKeypadDispatcher, KeypadDispatcher);

    /** CCAccelerometer associated with this director
     @since v2.0
     @js NA
     @lua NA
     */
    //加速器
    CC_PROPERTY(CCAccelerometer*, m_pAccelerometer, Accelerometer);

    /* delta time since last tick to main loop */
    //延迟
	CC_PROPERTY_READONLY(float, m_fDeltaTime, DeltaTime);
	
public:
    /** returns a shared instance of the director 
     *  @js getInstance
     */
	//获取导员类
    static CCDirector* sharedDirector(void);

protected:
    //回收导演资源
    void purgeDirector();
    //退出主循环的标志位
    bool m_bPurgeDirecotorInNextLoop; // this flag will be set to true in end()
    //设置场景
    void setNextScene(void);
    //显示FPS相关
    void showStats();
    void createStatsLabel();
    void calculateMPF();
    void getFPSImageData(unsigned char** datapointer, unsigned int* length);
    
    /** calculates delta time since last time it was called */    
    //计算全局帧间的时间差
    void calculateDeltaTime();
protected:
    /* The CCEGLView, where everything is rendered */
    //OpenGL窗口
    CCEGLView    *m_pobOpenGLView;
    //帧率
    double m_dAnimationInterval;
    double m_dOldAnimationInterval;

    /* landscape mode ? */
    //???
    bool m_bLandscape;
    //是否显示FPS和显示FPS的相关Label
    bool m_bDisplayStats;
    float m_fAccumDt;
    float m_fFrameRate;
    
    CCLabelAtlas *m_pFPSLabel;
    CCLabelAtlas *m_pSPFLabel;
    CCLabelAtlas *m_pDrawsLabel;
    
    /** Whether or not the Director is paused */
    //是否暂停
    bool m_bPaused;

    /* How many frames were called since the director started */
    //渲染的总帧数
    unsigned int m_uTotalFrames;
    //计算被渲染的帧数
    unsigned int m_uFrames;
    //渲染一帧的时间
    float m_fSecondsPerFrame;
     
    /* The running scene */
    //运行中的场景
    CCScene *m_pRunningScene;
    
    /* will be the next 'runningScene' in the next frame
     nextScene is a weak reference. */
    //下一个场景,弱引用
    CCScene *m_pNextScene;
    
    /* If YES, then "old" scene will receive the cleanup message */
    //上一个场景被切换时是否可以收到清除消息
    bool    m_bSendCleanupToScene;

    /* scheduled scenes */
    //场景栈
    CCArray* m_pobScenesStack;
    
    /* last time the main loop was updated */
    //记录主循环刷新时的上一次时间
    struct cc_timeval *m_pLastUpdate;

    /* whether or not the next delta time will be zero */
    //???
    bool m_bNextDeltaTimeZero;
    
    /* projection used */
    //投影
    ccDirectorProjection m_eProjection;

    /* window size in points */
    //窗口大小
    CCSize    m_obWinSizeInPoints;
    
    /* content scale factor */
    //屏幕比例大小因子
    float    m_fContentScaleFactor;

    /* store the fps string */
    //存储FPS值
    char *m_pszFPS;

    /* This object will be visited after the scene. Useful to hook a notification node */
    //通知节点
    CCNode *m_pNotificationNode;

    /* Projection protocol delegate */
    //自定义投影的协议
    CCDirectorDelegate *m_pProjectionDelegate;
    
    // CCEGLViewProtocol will recreate stats labels to fit visible rect
    //声明自定义投影协议类为友元类
    friend class CCEGLViewProtocol;
};

总结一下导演类的主要操作内容:

1)控制场景的切换

2)设置OpenGL View等于OpenGL相关的操作

3)计算和显示与帧率相关的信息

4)控制游戏的主循环,可暂停和恢复

5)配置和获取屏幕大小

6)坐标转换


看完了导演类的主要操作后再来回顾一下cocos2d-x的导演调用流程:

1. 游戏启动,进入主函数调用CCApplication::run

2. 在AppDelegate中创建和运行场景(applicationDidFinishLaunching被调用)

3. 导演开始执行主循环


那么整个流程就剩下运行场景没有分析了。其调用时在AppDelegate::applicationDidFinishLaunching的最后一句

pDirector->runWithScene(pScene);
当调用这句话后游戏便有了场景,这句话是在执行主循环前就被调用的,这样在主循环被调用时就有场景可以被渲染。

void CCDirector::runWithScene(CCScene *pScene)
{
    CCAssert(pScene != NULL, "This command can only be used to start the CCDirector. There is already a scene present.");
    CCAssert(m_pRunningScene == NULL, "m_pRunningScene should be null");

    pushScene(pScene);
    startAnimation();
}
这个函数的内容非常简单,先断言判断参数的合法化,然后场景入栈,最后运行主循环。(其流程与之前写过的Android的执行流程的文章一致)

入栈操作:

void CCDirector::pushScene(CCScene *pScene)
{
	//断言判断场景是否为空
    CCAssert(pScene, "the scene should not null");
    //入栈时不需要通知,isSendCleanupToScene方法的注释中有提到
    m_bSendCleanupToScene = false;
    //加入栈中的队列中,由一个数组来维护
    m_pobScenesStack->addObject(pScene);
    //将此栈赋给m_pNextScene,注意m_pNextScene是一个弱引用
    m_pNextScene = pScene;
}
注意到这时的m_pNextScene是有指向一个对象的

在反过来观察drawScene方法中一个判断语句

    if (m_pNextScene)
    {
        setNextScene();
    }
在这里,会设置一个场景进行渲染,也就是说当applicationDidFinishLaunching方法执行完后,游戏的主循环中便有了场景可以进行渲染,因为这时肯定有一个场景已经入栈。

void CCDirector::setNextScene(void)
{
	//当前场景是否是通过跳转效果类进行跳转(通常是通过CCTransitionScene来进行场景跳转的,如果是CCTransitionScene来跳转,那么下面的值变为true)
    bool runningIsTransition = dynamic_cast<CCTransitionScene*>(m_pRunningScene) != NULL;
	//是否是通过跳转效果类进行跳转(通常是通过CCTransitionScene来进行场景跳转的,如果是CCTransitionScene来跳转,那么下面的值变为true)
    bool newIsTransition = dynamic_cast<CCTransitionScene*>(m_pNextScene) != NULL;

    // If it is not a transition, call onExit/cleanup
     if (! newIsTransition)//如果不是跳转进来的,而是直接切换的则直接清空上一个场景的资源
     {
         if (m_pRunningScene)//这里要先判断m_pRunningScene是否为空,因为第一次加载场景时m_pRunningScene肯定是为NULL
         {
             m_pRunningScene->onExitTransitionDidStart();//如果用CCTransitionScene跳转时会进入到CCTransitionScene的onExitTransitionDidStart中
             m_pRunningScene->onExit();//如果用CCTransitionScene跳转时会进入到CCTransitionScene的onExit中
         }
 
         // issue #709. the root node (scene) should receive the cleanup message too
         // otherwise it might be leaked.
         if (m_bSendCleanupToScene && m_pRunningScene)//如果清除场景需要收到清除消息,则调用cleanup方法
         {
             m_pRunningScene->cleanup();
         }
     }

    if (m_pRunningScene)//释放上一个场景的资源
    {
        m_pRunningScene->release();
    }
    m_pRunningScene = m_pNextScene;//将要运行的场景赋给表示正在运行的场景的指针
    m_pNextScene->retain();
    m_pNextScene = NULL;//清掉m_pNextScene,直到有新场景入栈

    //这里之所以要加这个判断(! runningIsTransition)来启动新场景的生命周期,是因为
    //如果是通过CCTransitionScene来进行场景跳转的,那么新场景的生命周期的启动交给了CCTransitionScene类来启动并调用。所以要加上(! runningIsTransition)这个判断
    if ((! runningIsTransition) && m_pRunningScene)//进入新场景的生命周期
    {
        m_pRunningScene->onEnter();
        m_pRunningScene->onEnterTransitionDidFinish();
    }
}


这样下一篇便开始分析场景的生命周期。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值