程序从哪里启动
不仅游戏,任何程序都必须有一个启动的入口;几乎每个程序员接触编程的第一个程序都是 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。