Cocos2d-x 3.x启动过程

来源:互联网 发布:300英雄官方淘宝店 编辑:程序博客网 时间:2024/04/28 23:18
目标:- 理解cocos2d-x启动过程- 对整个框架有个初步认识

程序入口

我们在学习C/C++的时候,知道每个C/C++程序都有一个且只有一个入口点(main函数),同样我们通过Cocos引擎生成的初始项目代码也有入口点,由于此时我们所写的代码是区别于控制台的代码,入口点函数便不再是main(这是在学习控制台程序时的入口点)。有过Win32应用开发经验的人知道,每个程序的入口点函数是_tWinMain(这个在win32筛选器下的main.cpp可以找到),它的函数原型如下:

int APIENTRY _tWinMain(HINSTANCE hInstance,                       HINSTANCE hPrevInstance,                       LPTSTR    lpCmdLine,                       int       nCmdShow)

知道了入口点,我们就可以从入口点函数一条一条代码的往下读

int APIENTRY _tWinMain(HINSTANCE hInstance,                       HINSTANCE hPrevInstance,                       LPTSTR    lpCmdLine,                       int       nCmdShow){    UNREFERENCED_PARAMETER(hPrevInstance);    UNREFERENCED_PARAMETER(lpCmdLine);    // create the application instance    AppDelegate app;    return Application::getInstance()->run();}

如上述代码所示,首先实例化了一个AppDelegate对象,这个类可以理解为一个用于管理整个App的类。实例化之后,通过调用getInstance()方法获得实例指针(单例模式),然后调用run()方法,整个_tWinMain函数到此就结束了。众所周知,在C/C++中,main函数执行完了,那么整个程序也就退出了,但是为什么在这里,程序能够一直运行呢?

真正的开始—run()

接着上面的疑问,我们试着调试程序,顺表看看run()的实现。
int Application::run(){    //在注册表中写入对于PVRFrame图像文件帧的显示和隐藏的设置     PVRFrameEnableControlWindow(false);    // Main message loop:    LARGE_INTEGER nLast;    LARGE_INTEGER nNow;    //这是个WIN API,使用QueryPerformanceCounter来查询定时器的计数值,如果硬件里有定时器,它就会启动这个定时器,并且不断获取定时器的值,这样的定时器精度,就跟硬件时钟的晶振一样精确的。    QueryPerformanceCounter(&nLast);     //设置GL上下文环境,需要重载实现,否则使用默认配置</span>    initGLContextAttrs();    // Initialize instance and cocos2d.    // 完成应用程序启动前的一些工作,主要完成导演实例化和场景布置等等工作。    if (!applicationDidFinishLaunching())    {        return 1;    }    auto director = Director::getInstance();    //cocos2d::GLViewImp1,获得整个GL视图的管理    auto glview = director->getOpenGLView();    // Retain glview to avoid glview being released in the while loop    glview->retain();//引用计数+1    while(!glview->windowShouldClose()) //默认返回false    {        //针对 每帧渲染时间消耗 与 fps之间的矛盾着的“异常”处理        //nNow :   本次渲染开始时刻        //nLast : 上一次渲染结束时刻        QueryPerformanceCounter(&nNow);        //如果上一次渲染花费的时间 > 设定的fps         if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)        {            nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);            director->mainLoop(); //如果没有设置nextScene,那么将一直调用对当前场景渲染            glview->pollEvents();        }        else        {            Sleep(1);        }    }    // Director should still do a cleanup if the window was closed manually.    if (glview->isOpenGLReady())    {        director->end();        director->mainLoop();        director = nullptr;    }    glview->release();    return 0;}void AppDelegate::initGLContextAttrs(){    GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8};    GLView::setGLContextAttrs(glContextAttrs);}
  • 在调试的过程中我们发现,程序将一直“徘徊”在while(!glview->windowShouldClose())循环处。其实,这个循环就是类似Win32开发当中的消息循环,也就是说,在关闭应用程序之前,整个程序将一直在处于上述这个循环当中。
  • 其实,整个程序的开始是由调用run()开始,run()方法中,前半部分负责初始化应用程序环境,中间部分while循环负责消息循环及场景渲染,后半部份负责清理。

以下函数负责初始化OpenGL视图上下文环境。

void AppDelegate::initGLContextAttrs(){    //结构体    GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8};    //通过GLView的一些函数可以操作GL视图的界面信息    GLView::setGLContextAttrs(glContextAttrs);}struct GLContextAttrs { //设置渲染所用的调色风格,如:redBits = 8,表示R通道用8bit来表示    int redBits;    int greenBits;    int blueBits;    int alphaBits;    int depthBits;    int stencilBits;};

准备工作

首先先看下AppDelegate这个类:
class  AppDelegate : private cocos2d::Application{public:    AppDelegate();    virtual ~AppDelegate();    virtual void initGLContextAttrs();    virtual bool applicationDidFinishLaunching();    virtual void applicationDidEnterBackground();    virtual void applicationWillEnterForeground();};

AppDelegate继承于Application,主要实现了6个方法,其中initGLContextAttrs()用于初始化GL视图环境;applicationDidFinishLaunching()是在程序位于前台时候会调用;applicationDidEnterBackground()是程序转向后台时调用,一般实现是暂停游戏;applicationWillEnterForeground()是程序由后台转向前台时调用,一般实现是恢复游戏。

在上述run()方法中,我们发现依次调用了initGLContextAttrs()用于初始化GL视图 和 applicationDidFinishLaunching()用于负责应用程序生成前的一些准备工作,比如实例化导演对象和场景布置:
bool AppDelegate::applicationDidFinishLaunching() {    auto director = Director::getInstance();    auto glview = director->getOpenGLView();    if(!glview) {     //因为cocos2d底层采用opengl进行渲染,首先需要一张opengl的画布     //有了画布之后,整个给app才能显示        glview = GLViewImpl::create("Cpp Empty Test");        director->setOpenGLView(glview);    }    director->setOpenGLView(glview);    //设置分辨率,因为director->setOpenGLView(glview)传的是指针,所以此处修改,能直接director所指对象的状态。    glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);    //获得app框架大小,这个值区别于分辨率。screen size    Size frameSize = glview->getFrameSize();    vector<string> searchPath;    // 下面这段代码是cocos2d关于屏幕适配,省略部分代码,不影响讲解    if (frameSize.height > mediumResource.size.height)        ...    //得出屏幕适配方案,设置资源路径    FileUtils::getInstance()->setSearchPaths(searchPath);    // 用于调试用的    director->setDisplayStats(true);    // FPS 默认是1/60.0    director->setAnimationInterval(1.0 / 60);    // 创建场景:通常需要我们写的就是HelloWorld等等具体的游戏场景    auto scene = HelloWorld::scene();    // 导演负责将场景布置到opengl视图之上,opengl负责渲染场景    director->runWithScene(scene);    return true;}

上述代码,负责将导演实例化之后,并且为导演分配工具(GL视图)和场景,导演将根据场景布置现场。runWithScene将场景Scene压入场景栈,在Application::run()的循环中,将把该被渲染的场景(被设置为_runningScene的场景)解析成绘图命令(draw command),形成一个queue(绘图队列)。

void Director::runWithScene(Scene *scene){    CCASSERT(scene != nullptr, "This command can only be used to start the Director. There is already a scene present.");    CCASSERT(_runningScene == nullptr, "_runningScene should be null");    pushScene(scene);    startAnimation();}void Director::pushScene(Scene *scene){    CCASSERT(scene, "the scene should not null");    _sendCleanupToScene = false;    //一个导演并不只是负责一个场景,导演类维护了一个场景栈(保存了所有要渲染的场景)    _scenesStack.pushBack(scene);     //当将一个场景压入场景栈中时,也顺便指定下一个要渲染的场景为压入的场景    _nextScene = scene;}void DisplayLinkDirector::startAnimation(){    if (gettimeofday(_lastUpdate, nullptr) != 0)    {        CCLOG("cocos2d: DisplayLinkDirector: Error on gettimeofday");    }    //这个函数中并没有真正开始播放动画,设置场景无效标志。    _invalid = false;    _cocos2d_thread_id = std::this_thread::get_id();    //设置FPS :帧间隔时间(单位:秒),默认是1.0/60    Application::getInstance()->setAnimationInterval(_animationInterval);    // fix issue #3509, skip one fps to avoid incorrect time calculation.    setNextDeltaTimeZero(true);}
所谓的“渲染”在mainLoop中进行,但是drawScene并不进行真正的渲染(v3.x版本之后,cocos2d-x改变了他的渲染方式):
void DisplayLinkDirector::mainLoop(){   //检测Director对象的标志,根据标志选择接下来的动作    if (_purgeDirectorInNextLoop)    {        _purgeDirectorInNextLoop = false;        purgeDirector(); //清理    }    else if (_restartDirectorInNextLoop)    {        _restartDirectorInNextLoop = false;        restartDirector();    }    else if (! _invalid)    {   //检测到场景无效标志,则渲染场景        drawScene();        // release the objects        //因为场景中有各种Sprite/Layer等等对象,这些对象可能采用静态工厂方法创建(如:create),那么这些对象都将在创建的时候被加入到自动释放池,由引擎管理的自动释放池来维护这些对象        //当“渲染”完场景之后,自动释放池将被调用来清理这些对象。清理的        //上述glview也是采用静态工厂方法生成,所以为免被自动释放池清理,需要在创建之后retain一次。        PoolManager::getInstance()->getCurrentPool()->clear();    }}
drawScene只是生成一些渲染命令,并将命令压入一个队列:
// Draw the Scenevoid Director::drawScene(){    // calculate "global" dt    calculateDeltaTime();    if (_openGLView)    { //GL视图轮询事情,这是个空函数        _openGLView->pollEvents();    }    //tick before glClear: issue #533    if (! _paused)    {        _eventDispatcher->dispatchEvent(_eventBeforeUpdate);        _scheduler->update(_deltaTime);        _eventDispatcher->dispatchEvent(_eventAfterUpdate);    }    _renderer->clear();    experimental::FrameBuffer::clearAllFBOs();    /* to avoid flickr, nextScene MUST be here: after tick and before draw.     * FIXME: Which bug is this one. It seems that it can't be reproduced with v0.9     */    if (_nextScene)    {        setNextScene();    }    pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);    if (_runningScene)    {#if (CC_USE_PHYSICS || (CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION) || CC_USE_NAVMESH)        _runningScene->stepPhysicsAndNavigation(_deltaTime);#endif        //clear draw stats        _renderer->clearDrawStats();        //render the scene//render负责节点树中的各节点渲染需要</span>        _runningScene->render(_renderer);        _eventDispatcher->dispatchEvent(_eventAfterVisit);    }    // draw the notifications node    if (_notificationNode)    {//visit函数负责处理由事情触发的渲染请求</span>        _notificationNode->visit(_renderer, Mat4::IDENTITY, 0);    }    if (_displayStats)    {        showStats();    }    _renderer->render();    _eventDispatcher->dispatchEvent(_eventAfterDraw);    popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);    _totalFrames++;    // swap buffers    if (_openGLView)    {        _openGLView->swapBuffers();    }    if (_displayStats)    {        calculateMPF();    }}

小结

  1. 程序的路口点是_tWinMain
  2. 程序真的开始是由调用AppDelegate::run()开始,run()中实现了消息循环和场景渲染。
  3. applicationDidFinishLaunching()负责程序开始前的一些准备工作,如场景大小、导演等等的初始化。
1 0
原创粉丝点击