小魚習慣直接從代碼實例來學習一套成型的引擎庫。
運行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源碼的一個過程,並非教程,有些內容在前面可能在前面章節說的不准確(因小魚也不知道),但后面涉及到后肯定會給出明確的解釋。