小鱼习惯直接从代码实例来学习一套成型的引擎库。
运行cpp-empty-test
一个典型的HelloWorld程序翻看代码结构
看到了 main.h与main.cpp文件就从这里开始
#ifndef __MAIN_H__ #define __MAIN_H__ #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files: #include <windows.h> #include <tchar.h> // C RunTime Header Files #include "CCStdC.h" #endif // __MAIN_H__
从这个头文件中没有得到什么有价值的信息,包含了一些库文件,还有一个就是引入了 CCStdC.h 这个文件,从命名上来看应该是Cocos2d-x的标准库头文件,打开来看了几眼,都是一些常用的宏定义,还有就是平台定义,先忽略掉这些信息,继续向下看。
main.cpp
#include "main.h" #include "../Classes/AppDelegate.h" 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(); }
打开了main.cpp文件我立刻石化了,竟然就有这么几行代码,看来cocos2d-x封装的很牛X啊
这个helloWorld的核心代码就一句 Application::getInstance()->run();
刨根问底我们去了解一下 Application类, 从在这里的调用上来看,整个cocox2d-x都封装在了Application这个类里面每一个应用程序都有唯一的一个Application对象,这种调用明显采用了单例的设计模式,我们跟一下代码,追踪线索。
打开Application.h文件发现
class CC_DLL Application : public ApplicationProtocol
Applilcation类继承了 ApplicationProtocol类, 继续看 ApplicationProtocol.h
CCApplicationProtocol.h
#ifndef __CC_APPLICATION_PROTOCOL_H__ #define __CC_APPLICATION_PROTOCOL_H__ #include "CCPlatformMacros.h" NS_CC_BEGIN 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<span style="white-space:pre"> </span>// 平台种类的一个枚举 { OS_WINDOWS, OS_LINUX, OS_MAC, OS_ANDROID, OS_IPHONE, OS_IPAD, OS_BLACKBERRY, OS_NACL, OS_EMSCRIPTEN, OS_TIZEN, OS_WINRT, OS_WP8 }; virtual ~ApplicationProtocol() {} virtual bool applicationDidFinishLaunching() = 0;// 程序启动后的一个回调,应该在这里可以放置一些初始化的操作 virtual void applicationDidEnterBackground() = 0;// 程序进入后台时的回调函数,pc中的最小化,手机中的程序转入后台的这个时机调用。 virtual void applicationWillEnterForeground() = 0;// 程序又重回前台时的回调 virtual void setAnimationInterval(double interval) = 0;// 设置动画两帧之间的时间间隔,也就是常说的帧速度之类的参数 virtual LanguageType getCurrentLanguage() = 0;// 应该是和语种相关的,用来做多语言版本时得到当前语言类型参数 可以打开LanguageType看看 virtual const char * getCurrentLanguageCode() = 0;// 得到当前语言的编码,返回的是一个字符串,可能是 utf8 gb2312(这里是推测) virtual Platform getTargetPlatform() = 0;// 得到当前平台类型 也就是上面定义的枚举, }; // end of platform group /// @} NS_CC_END #endif // __CC_APPLICATION_PROTOCOL_H__
这里我把这个文件精简了一下去掉了注释
可以看到ApplicationProtocol是一个抽象类,提供了很多抽象方法,有一个平台类型的定义
enum class Platform
通过这个枚举里面的定义可以了解cocos2d-x所支持的平台各类,还是很全面的,很多小鱼都没用到过。
从纯虚函数的名称上我可以大致猜到这些函数的作用,以注释的形式写到上面代码的后面。
这个类并不复杂,都是定义了一些接口,可以推断,不同平台下的App都要分别继承这个接口来实现不同的处理。
下面我回到 Application 类 一点一点的看,确定Application这个类继承了ApplicationProtocol类
并且在这个头文件上面我看到了这样的一个宏定义
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
明显这个Application类是为了 Windows平台准备的
下面列出部分代码来分析
NS_CC_BEGIN class Rect; class CC_DLL Application : public ApplicationProtocol { public: Application(); virtual ~Application(); int run(); static Application* getInstance(); CC_DEPRECATED_ATTRIBUTE static Application* sharedApplication(); virtual void setAnimationInterval(double interval); virtual LanguageType getCurrentLanguage(); virtual const char * getCurrentLanguageCode(); virtual Platform getTargetPlatform(); /** * Sets the Resource root path. * @deprecated Please use FileUtils::getInstance()->setSearchPaths() instead. */ CC_DEPRECATED_ATTRIBUTE void setResourceRootPath(const std::string& rootResDir); 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 * sm_pSharedApplication; }; NS_CC_END
实现父类的几个虚函数我们就不讨论了。
static Application* getInstance();
App是个单例,这是得到app对象的静态方法,不用多说。
int run();
游戏启动及游戏循环肯定在这里了。
我们稍后再进入到run里面看个究竟,先把Application看完整
CC_DEPRECATED_ATTRIBUTE void setResourceRootPath(const std::string& rootResDir);
这个函数肯定是设置资源文件的路径地址的。
void setStartupScriptFilename(const std::string& startupScriptFile);
设置启动脚本,这个肯定是设置启动Lua或者js脚本的地方
Application类小鱼总结一下,这个类也是一个抽象类,因为有些父类的虚函数并没有实现,是针对win32平台下的,如果 想在自己的平台使用应该仿照定义自己的Application类,主要负责启动Cocos2d-x 开发人员应该继承 Application类来定制自己的应用程序。值得学习的是这块Application的封装应用了抽象工厂的设计模式,来满足不同平台的整合
这个HelloWorld示例程序中私人订制的App类为AppDelegate类,我们打开看一下,实现了三个抽象方法.
virtual bool applicationDidFinishLaunching(); virtual void applicationDidEnterBackground(); virtual void applicationWillEnterForeground();
后两个函数主要是在程序前后台运行的时候进行了暂停和恢复暂停的操作,
第一个函数 applicationdidFinishLaunching 进行了一系列初始化。稍后我们再分析它。
至此我们大致分析了Application这个类,看了源码,了解了这个类的作用,那么具体是怎么驱动的呢,我们回过头来看 Application:run()这个函数,这也是helloworld里调用 的唯一一个方法。程序从这个run开始运行。我将分析写入到代码间,这样能方便大家阅读。
int Application::run() { PVRFrameEnableControlWindow(false); //跟进函数看到了一些注册表的操作,操作了窗口信息的变量。先过,可以了解到如果自己有些平台性质的全局数据交互可以扩展这里面的代码。 // Main message loop: LARGE_INTEGER nFreq; LARGE_INTEGER nLast; LARGE_INTEGER nNow; QueryPerformanceFrequency(&nFreq);// windows平台得到硬件时钟的频率 QueryPerformanceCounter(&nLast);//记录上一次计时的时间 // Initialize instance and cocos2d. if (!applicationDidFinishLaunching())//调用上面提到过的私人定制的初始化过程。这也是虚函数的一个应用 { return 0; } auto director = Director::getInstance();//得到Director类的单例对象,Director类是干什么的先不管,从字面上理解是导演的意思,肯定是一个很重要的类 auto glview = director->getOpenGLView();//可以看出 Director类里封装了关于OPENGL的操作,这里是得到opengl对象 // Retain glview to avoid glview being released in the while loop glview->retain();//从上面的注释可以了解到在这里为了防止glview这个opengl对象在下面的循环中被释放,增加了一次引用。 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())//释放opengl { director->end(); director->mainLoop(); director = nullptr; } glview->release(); return true; }
这里面Director这个类出镜率很高,之后我们单独分析这个类。
下面我们分析一下私人定制的applicationDidFinishLaunching函数在程序启动中都干了些什么。
bool AppDelegate::applicationDidFinishLaunching() { // initialize director auto director = Director::getInstance();//获取director对象,具体Director类是什么玩意,下文分析,目前可以知道这是个大管家 auto glview = director->getOpenGLView();//获取opengl渲染对象,注意这里的 auto用法,这是c++11的新特性,大家可以去百度,就是根据后面的初始化来自动识别变量类型,真是方便啊。 if(!glview) { glview = GLView::create("Cpp Empty Test"); director->setOpenGLView(glview); }//如果获取opengl失败那么重新创建一个opengl对象。 director->setOpenGLView(glview);// 将opengl对象绑定到大管家,大导演身上。 // Set the design resolution #if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) // a bug in DirectX 11 level9-x on the device prevents ResolutionPolicy::NO_BORDER from working correctly glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::SHOW_ALL); #else glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER); #endif //这段代码是判断平台来设置此app的长宽及边框模式。还有一些预定义在AppMacros.h里面定义的,可以翻看一下。 Size frameSize = glview->getFrameSize(); vector<string> searchPath; // In this demo, we select resource according to the frame's height. // If the resource size is different from design resolution size, you need to set contentScaleFactor. // We use the ratio of resource's height to the height of design resolution, // this can make sure that the resource's height could fit for the height of design resolution. // if the frame's height is larger than the height of medium resource size, select large resource. if (frameSize.height > mediumResource.size.height){ searchPath.push_back(largeResource.directory); director->setContentScaleFactor(MIN(largeResource.size.height/designResolutionSize.height, largeResource.size.width/designResolutionSize.width));} // if the frame's height is larger than the height of small resource size, select medium resource. else if (frameSize.height > smallResource.size.height) { searchPath.push_back(mediumResource.directory); director->setContentScaleFactor(MIN(mediumResource.size.height/designResolutionSize.height, mediumResource.size.width/designResolutionSize.width)); } // if the frame's height is smaller than the height of medium resource size, select small resource. else { searchPath.push_back(smallResource.directory); director->setContentScaleFactor(MIN(smallResource.size.height/designResolutionSize.height, smallResource.size.width/designResolutionSize.width)); } // set searching path FileUtils::getInstance()->setSearchPaths(searchPath); // 上面的那几段代码是针对不同分辨率读取不同的资源,并且计算了缩放比例,设置了资源路径。 // turn on display FPS 设置帧速率的显示 director->setDisplayStats(true); // set FPS. the default value is 1.0/60 if you don't call this director->setAnimationInterval(1.0 / 60); //这里设置了帧速率,回想上面我们看Application::run代码里面while循环就是依据这块设置的速率来实现的。 // create a scene. it's an autorelease object auto scene = HelloWorld::scene();//这里又出现了一个新的类 HelloWorld 这是什么玩意?后面再说。肯定是定义了一个场景。 // run director->runWithScene(scene);//最后一行代码是让大总管,大导演,run这个HelloWorld场景。 return true; }
总结:到这里我们通过HelloWorld及跟踪代码,了解了cocos2d-x的启动流程每个平台都要有一个Application类继承自ApplicationProtocol来驱动整个程序。
每个项目都要有一个自己的App类继承自相应平台的Application调用Application::run方法来实现程序的启动。
在run里面可以定制自己的启动初始化 用Application::applicationDidFinishLaunching来实现 还涉及到了几个重要的类 Director, HelloWorld里面的Layer,Scene这几个类.
下章我们来继续刨根问底从 Director Layer Scene这几个类开始小鱼看代码喜欢一层一层的看,也就是广度优先,而不是一个类看到底的方式。
备注:此系列文章为小鱼自己学习cocos2d-x源码的一个过程,并非教程,有些内容在前面可能在前面章节说的不准确(因小鱼也不知道),但后面涉及到后肯定会给出明确的解释。