cocos2d-x 3.2 启动过程分析 - win32 与 android 平台(一)
我是从3.2版,也就是目前的最新版本开始接触cocos2dx的。不得不说cocos2dx确实是一款优秀的游戏引擎,使用它的这段时间里我也学到了很多知识。好了,话不多说,今天试着来分析一下cocos2dx-3.2的启动过程。
一.windows平台
windows平台下面,引擎的启动过程比较直观。
进入你所建立的项目的根目录,一般是这样的:
Classes文件夹里面一般放置平台无关的c++代码,而其他的proj.xxx,自然就是与各个平台相关的文件啦。
进入proj.win32,这里就存放着windows平台的入口点main.cpp。
看一下main.cpp里面都有什么:
<span style="font-family:Microsoft YaHei;font-size:12px;"><span style="font-size:12px;"><span style="font-size:14px;">USING_NS_CC; 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(); } </span></span></span>
显然,这是一个windows窗体程序的入口。函数中实例化了一个AppDelegate类型(在栈上),并调用了这么一句:
return Application::getInstance()->run();
run() 函数里就开启了引擎的主循环。
有人可能会问:不对呀,示例化的是AppDelegate类,可是调用的是Application类型的run()啊,看起来上一句话只是定义了一个变量,却没有拿这个变量来做任何事情,不是有点奇怪吗?
别急,先来看一下 Application类型的定义,Application类是平台相关的,也就是说每个平台都有自己的Application类型,所以这里指的是这里的:
(注意上方的目录)。
类声明也贴出来:
NS_CC_BEGIN class Rect; class CC_DLL Application : public ApplicationProtocol { public: /** * @js ctor */ Application(); /** * @js NA * @lua NA */ virtual ~Application(); /** @brief Run the message loop. */ int <span style="color:#FF0000;">run</span>(); /** @brief Get current applicaiton instance. @return Current application instance pointer. */ static Application* getInstance(); /** @deprecated Use getInstance() instead */ CC_DEPRECATED_ATTRIBUTE static Application* sharedApplication(); /* override functions */ virtual void setAnimationInterval(double interval); virtual LanguageType getCurrentLanguage(); virtual const char * getCurrentLanguageCode(); /** @brief Get target platform */ virtual Platform getTargetPlatform(); /** * Sets the Resource root path. * @deprecated Please use FileUtils::getInstance()->setSearchPaths() instead. */ CC_DEPRECATED_ATTRIBUTE void setResourceRootPath(const std::string& rootResDir); /** * Gets the Resource root path. * @deprecated Please use FileUtils::getInstance()->getSearchPaths() instead. */ CC_DEPRECATED_ATTRIBUTE const std::string& getResourceRootPath(void); void setStartupScriptFilename(const std::string& startupScriptFile); const std::string& getStartupScriptFilename(void) { return _startupScriptFilename; } protected: HINSTANCE _instance; HACCEL _accelTable; LARGE_INTEGER _animationInterval; std::string _resourceRootPath; std::string _startupScriptFilename; static Application * <span style="color:#FF0000;">sm_pSharedApplication</span>; }; NS_CC_END
可以看到,Application类提供了一些设置/获取平台相关属性的函数和一个关键的 run() 函数。同时它是一个抽象类,因为它继承自ApplicationProtocol 类型,却没有实现后者的纯虚函数,因此 Application 是不能直接实例化的。
Application类维护了一个本类型的指针
static Application * <span style="color:#FF0000;">sm_pSharedApplication</span>这个指针在构造函数中被设为指向自身,并在getInstance()中被返回。
Application::Application() : _instance(nullptr) , _accelTable(nullptr) { _instance = GetModuleHandle(nullptr); _animationInterval.QuadPart = 0; CC_ASSERT(! sm_pSharedApplication); sm_pSharedApplication = this; }Application* Application::getInstance() { CC_ASSERT(sm_pSharedApplication); return sm_pSharedApplication; }
而 Application 是并不能被实例化的,因此只有当 Application 的子类被构造时,它的构造函数才会被调用,且 getInstance() 返回的也是指向子类的指针。这样就解释了开始的疑问, AppDelegate 类是 Application 类的直接子类。所以
Application::getInstance() -> run()就相当于app -> run()我们的游戏从此开启了波澜壮阔的一生。。。
二.run() 函数探究
继续分析,我们先来看看在windows平台下 run() 函数的实现:
int Application::run() { PVRFrameEnableControlWindow(false); // Main message loop: LARGE_INTEGER nFreq; LARGE_INTEGER nLast; LARGE_INTEGER nNow; QueryPerformanceFrequency(&nFreq); QueryPerformanceCounter(&nLast); // Initialize instance and cocos2d. if (!applicationDidFinishLaunching()) { return 0; } 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; director->mainLoop(); glview->pollEvents(); } else { Sleep(0); } } // Director should still do a cleanup if the window was closed manually. if (glview->isOpenGLReady()) { director->end(); director->mainLoop(); director = nullptr; } glview->release(); return true; }在该函数中
// Main message loop: LARGE_INTEGER nFreq; LARGE_INTEGER nLast; LARGE_INTEGER nNow; QueryPerformanceFrequency(&nFreq); <span style="font-family:Microsoft YaHei;">//<span>返回硬件支持的高精度计数器的频率</span></span> QueryPerformanceCounter(&nLast); <span><span style="font-family:Microsoft YaHei;">//返回硬件支持的高精度计数器的值</span></span><span style="font-family:Microsoft YaHei;"></span>这段是取得系统高精度计时器次数和频率用来计算每一帧经过的时间(大致是这么个意思,这两个函数的用法可以参考此处)。接下来
// Initialize instance and cocos2d. if (!applicationDidFinishLaunching()) { return 0; }调用 applicationDidFinishLaunching() 。这个函数我们很熟悉,但是他并没有在Application里被实现,它实际上是Application类的父类ApplicationProtocl声明的接口。真正的实现是在 AppDelegate 类中。因此这里调用的是AppDelegate类的 applicationDidFinishLaunching() 函数。
再来顺便看看 ApplicationProtocol 类,这也是个抽象类,它定义了一系列平台无关,而与引擎生命周期相关的纯虚函数接口,所有平台的 Application 类型都继承于此。所谓求同存异,大致如此。
ApplicationProtocol 类位置如下:
声明如下:
<span style="font-size:12px;"><span style="font-family:Microsoft YaHei;">NS_CC_BEGIN /** * @addtogroup platform * @{ */ class CC_DLL ApplicationProtocol { public: // Since WINDOWS and ANDROID are defined as macros, we could not just use these keywords in enumeration(Platform). // Therefore, 'OS_' prefix is added to avoid conflicts with the definitions of system macros. enum class Platform { OS_WINDOWS, OS_LINUX, OS_MAC, OS_ANDROID, OS_IPHONE, OS_IPAD, OS_BLACKBERRY, OS_NACL, OS_EMSCRIPTEN, OS_TIZEN, OS_WINRT, OS_WP8 }; /** * @js NA * @lua NA */ virtual ~ApplicationProtocol(){ #if CC_ENABLE_SCRIPT_BINDING ScriptEngineManager::destroyInstance(); #endif // clean auto release pool PoolManager::destroyInstance(); } /** @brief Implement Director and Scene init code here. @return true Initialize success, app continue. @return false Initialize failed, app terminate. * @js NA * @lua NA */ virtual bool applicationDidFinishLaunching() = 0; /** @brief This function will be called when the application enters background. * @js NA * @lua NA */ virtual void applicationDidEnterBackground() = 0; /** @brief This function will be called when the application enters foreground. * @js NA * @lua NA */ virtual void applicationWillEnterForeground() = 0; /** @brief Callback by Director for limit FPS. @param interval The time, expressed in seconds, between current frame and next. * @js NA * @lua NA */ virtual void setAnimationInterval(double interval) = 0; /** @brief Get current language config @return Current language config * @js NA * @lua NA */ virtual LanguageType getCurrentLanguage() = 0; /** @brief Get current language iso 639-1 code @return Current language iso 639-1 code * @js NA * @lua NA */ virtual const char * getCurrentLanguageCode() = 0; /** @brief Get target platform * @js NA * @lua NA */ virtual Platform getTargetPlatform() = 0; }; // end of platform group /// @} NS_CC_END</span></span>可以看到,像
<span style="font-size:12px;"><span style="font-family:Microsoft YaHei;"> </span></span>virtual bool applicationDidFinishLaunching() = 0; virtual void applicationDidEnterBackground() = 0; virtual void applicationWillEnterForeground() = 0; virtual void applicationWillEnterForeground() = 0;这几个函数都是游戏在不同生命周期下会被调用的,它们都在 AppDelegate 类中实现。applicationDidFinishLaunching() 实际上只是做了一些初始化,一般我们是定义一个场景,然后 director->runWithScene(scene)。 你可以看看runWithScene() 的实现,只是把场景压栈,真正的运行还在后面:
// 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; director->mainLoop(); glview->pollEvents(); } else { Sleep(0); } }在这个循环中
if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart) { nLast.QuadPart = nNow.QuadPart; director->mainLoop(); glview->pollEvents(); }当当前时间与上次记录的时间间隔大于一帧所占用的时间时,说明需要进入下一帧,调用mainLoop(),由此实现每一帧都进入引擎的内部流程。mainLoop()函数内部实现了很多功能,包括OpenGL的渲染,内存管理,定时器任务执行等等,鉴于篇幅有限,我就不深入讲下去了,有兴趣的可以自己去看看。
至此,windows平台的启动流程分析大概说完了,下一篇讲一下android平台的过程,这些全部都是本人自己的理解,在此抛砖引玉,有错误或遗漏的地方希望大家帮我指出来,有则改之,无则加勉,嘿嘿。
转载还请注明。