【cocos2d-x 源码剖析】启动窗口

程序从哪里启动

不仅游戏,任何程序都必须有一个启动的入口;几乎每个程序员接触编程的第一个程序都是 HelloWorld 控制台程序,这个最简单的程序的入口就是 Main 函数。接下来就让我们看看 cocos2d-x 创建的游戏是从哪里开始启动的,这里只探讨 win32 程序,其它平台以后有时间再探讨。使用 cocos2d-x 新建一个游戏后,可以在项目下看到这几个文件

|-HelloWorld
    |-AppDelegate.h
    |-AppDelegate.cpp
    |-HelloWorldScene.h
    |-HelloWorldScene.cpp
    |-main.h
    |-main.cpp

直觉告诉我们,游戏启动的入口应该在 main.cpp 中,打开这个文件

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();
}

果然没错,这就是 win32 程序的主函数写法,我们的游戏就是从这个函数开始启动的。再打开 main.h,可以看到这里包含了 win32 程序需要的头文件

#include <windows.h>
#include <tchar.h>

再回到我们的主函数中,这里用到了 AppDelegate 和 Application 这两个类,AppDelegate 在 AppDelegate.h 和 AppDelegate.cpp 中定义

class  AppDelegate : private cocos2d::Application

AppDelegate 继承自 Application 类,Application 类在 CCApplication-win.h 和 CCApplication-win.cpp 中定义

class CC_DLL Application : public ApplicationProtocol

可以看到 Application 又继承自 ApplicationProtocal 类,ApplicationProtocol 类在 CCApplicationProtocol.h 中定义,这几个文件在 cocos2d-x 引擎中定义

|-libcocos2d
    |-platform
        |-win32
            |-CCApplication-win32.h
            |-CCApplication-win32.cpp
        |-CCApplicationProtocol.h

总结: 游戏从 main.cpp 中启动,AppDelegate 是应用程序的实例,AppDelegate 继承自 Application,Application 继承自 ApplicationProtocol。接下来探究一下这三个类。

ApplicationProtocol

这是应用程序类的最顶层基类,它其实是一个抽象类,定义了一套应用程序接口,最主要的是下面四个方法

virtual void initGLContextAttrs() {}
virtual bool applicationDidFinishLaunching() = 0;
virtual void applicationDidEnterBackground() = 0;
virtual void applicationWillEnterForeground() = 0;

Application

这个类控制应用程序的生命周期,提供和管理一些全局的资源,创建和管理 win32 窗口以及消息循环都是在这个类定义的。Application 定义了几个 protected 字段

HINSTANCE           _instance;
HACCEL              _accelTable;
LARGE_INTEGER       _animationInterval;
std::string         _resourceRootPath;
std::string         _startupScriptFilename;
static Application * sm_pSharedApplication;

前两个字段是跟窗口相关的属性,第三个字段是动画播放的间隔,第四个字段是资源文件的搜索路径,第五个字段是绑定的脚本名称,最后一个字段是 Application 的单例。Application 中有两个重要的方法

int run();
static Application* getInstance();

getInstance 是获取单例,run 实现 win32 程序窗体创建和消息注册,后面再讲。

AppDelegate

AppDelegate 继承自 Application,主要实现了 ApplicationProtocol 的四个虚函数,
* initGLContextAttrs 设置 OpenGL context 属性
* applicationDidFinishLaunching 游戏资源加载完成后调用
* applicationDidEnterBackground 游戏进入后台时调用
* applicationWillEnterForeground 游戏回到前台时调用

游戏启动过程

我们先来回顾一下 win32 程序的启动过程,定义窗口类,绑定回调方法,注册窗口类,创建窗口,显示和刷新窗口,开始消息循环。然后我们再来看看 cocos2d-x 程序的启动过程以及在哪里完成窗口的初始化工作。

首先,从 main.cpp 的主函数开始运行

AppDelegate app;
return Application::getInstance()->run();

先创建一个 AppDelegate 的实例,目的是调用构造函数进行一些初始化工作。回顾 C++ 调用构造函数和析构函数的顺序,是 父类构造函数–>子类构造函数–>…->子类析构函数–>父类析构函数,所以会先调用 Application 的构造函数再调用 AppDelegate 的构造函数。先看看 Application 的构造函数

Application::Application()
: _instance(nullptr)
, _accelTable(nullptr)
{
    _instance    = GetModuleHandle(nullptr);
    _animationInterval.QuadPart = 0;
    CC_ASSERT(! sm_pSharedApplication);
    sm_pSharedApplication = this;
}

主要是初始化一些值,最主要的就是给静态单例变量 sm_pSharedApplication 赋值,之后 Application::getInstance() 就可以得到正确的值了。AppDelegate 的构造函数并没做什么,接下来看看 Application 的 run 函数

int Application::run()
{
    PVRFrameEnableControlWindow(false);

    // Main message loop:
    LARGE_INTEGER nLast;
    LARGE_INTEGER nNow;

    QueryPerformanceCounter(&nLast);

    initGLContextAttrs();

    // Initialize instance and cocos2d.
    if (!applicationDidFinishLaunching())
    {
        return 1;
    }

    auto director = Director::getInstance();
    auto glview = director->getOpenGLView();

    // Retain glview to avoid glview being released in the while loop
    glview->retain();

    while(!glview->windowShouldClose())
    {
        QueryPerformanceCounter(&nNow);
        if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
        {
            nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);

            director->mainLoop();
            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;
}

其中 initGLContextAttrs 和 applicationDidFinishLaunching 在子类 AppDelegate 中定义,while(!glew->windowShouldClose()) 实时检测窗口是否关闭,如果没关闭,则调用 director->mainloop();如果关闭了,则做一些回收工作,然后程序结束。程序在 while 循环这里阻塞住,直到游戏窗口被关闭。看完 run 函数,发现并没有创建窗口的代码啊,但在 while 循环这里却判断窗口是否关闭,说明此时窗口已经创建成功了;如果你断点调试一下,你就会发现执行完函数 applicationDidFinishLaunching 之后窗口就被创建出来了,说明创建窗口的代码在 applicationDidFinishLaunching 函数里,我们打开这个函数

glview = GLViewImpl::createWithRect("HelloWorld", Rect(0, 0, designResolutionSize.width, designResolutionSize.height));

这条语句说明窗口的创建和管理被封装在类 GLViewImpl 里,GLViewImpl 继承自 GlView,GLView 负责 OpenGL 的初始化工作和窗口管理工作。

总结

ApplicationProtocol 定义了一套应用程序的接口,Application 控制着整个应用程序的生命周期,AppDelegate 是 Application 的子类,实现应用程序接口;每个项目都会有一个 AppDelegate 类,在 AppDelegate 中调用 GLView 来初始化窗口和 OpenGL。

猜你喜欢

转载自blog.csdn.net/xingxinmanong/article/details/77256203
今日推荐