cocos2d-x學習之路(二)——分析AppDelegate和HelloWorldScene檔案
這裡我們來看一下cocos自動給我們生成的工程裡有些什麼東西,並且分析一下這些程式碼的用途,來為我們以後編寫cocos程式鋪下基礎。
這裡我建議看我這份隨筆的看官先看看cocos官網的快速入門手冊,不然可能會比較迷糊(因為待會要分析一些程式碼,如果以前沒見過的話會比較昏)。傳送門在這裡
其中一些基本不需要程式設計師干涉的程式碼我可能會不予分析。你也可以檢視官方API手冊。
下面的程式碼分析,如果是非常有用的東西我會在分析中用藍色標出。
首先我們進入相關的系統(你的如果是mac就開啟proj.ios_mac資料夾下的工程,是windows就開啟proj.win32資料夾下的工程,以此類推)的工程。我這裡是mac,執行一下cocos給我們生成的程式碼,結果如下:
既然是自己的demo嘛,當然要給自己打個廣告啦(那個居中的圖示顯然是cocos的logo)
這個介面裡面有一些我們可以直接看出來的東西:
這裡所有的元素都是我根據以往用過的引擎猜測的,實際上我們還是要看一下程式碼。不過我們目前知道大概有這麼些東西了,待會可以針對著看一下。
然後我們可以看看這些資源在哪裡,我通過XCODE可以直接看到:
這裡logo是HelloWorld.png,右下角的開關機圖示是CloseNormal.png,而那個CloseSelected.png是按下按鈕的圖片。
分析AppDelegate
好的,到我們的分析階段了。AppDelegate分為標頭檔案和實現檔案。我們當然是先看標頭檔案啦。這個檔案在Classes資料夾下。
1 #ifndef _APP_DELEGATE_H_ 2 #define _APP_DELEGATE_H_ 3 4 #include "cocos2d.h" 5 6 /** 7 @brief The cocos2d Application. 8 9 Private inheritance here hides part of interface from Director. 10 */ 11 class AppDelegate : private cocos2d::Application 12 { 13 public: 14 AppDelegate(); //建構函式15 virtual ~AppDelegate(); //解構函式 16 17 virtual void initGLContextAttrs(); //這個暫時不知道是幹什麼的 18 19 /** 20 @brief Implement Director and Scene init code here. 21 @return true Initialize success, app continue. 22 @return false Initialize failed, app terminate. 23 */ 24 virtual bool applicationDidFinishLaunching(); 25 26 /** 27 @brief Called when the application moves to the background 28 @param the pointer of the application 29 */ 30 virtual void applicationDidEnterBackground(); 31 32 /** 33 @brief Called when the application reenters the foreground 34 @param the pointer of the application 35 */ 36 virtual void applicationWillEnterForeground(); 37 }; 38 39 #endif // _APP_DELEGATE_H_
第4行包含了cocos2d的標頭檔案。
第11行定義了AppDelegate類,繼承自cocos2d的Application類。
這裡堆AppDelegate類的幾個虛擬函式在註釋上都有一定的說明了:
第24行的applicationDidFinishLaunching()是在程式初始化的時候自動呼叫的函式。在這裡面我們可以初始化導演(Director)和場景(Scence)。如果程式初始化成果會返回True,否則返回False。
第30行的applicationDidEnterBackground()是在程式失去焦點的時候呼叫。這裡面一般是加入用來停止程式的程式碼。
第36行的applicationWillEnterForeground()是在程式獲得焦點的時候呼叫,可以在裡面加入繼續遊戲的程式碼。
接下來看看實現檔案:
1 #include "AppDelegate.h" 2 #include "HelloWorldScene.h" 3 4 // #define USE_AUDIO_ENGINE 1 5 // #define USE_SIMPLE_AUDIO_ENGINE 1 6 7 #if USE_AUDIO_ENGINE && USE_SIMPLE_AUDIO_ENGINE 8 #error "Don't use AudioEngine and SimpleAudioEngine at the same time. Please just select one in your game!" 9 #endif 10 11 #if USE_AUDIO_ENGINE 12 #include "audio/include/AudioEngine.h" 13 using namespace cocos2d::experimental; 14 #elif USE_SIMPLE_AUDIO_ENGINE 15 #include "audio/include/SimpleAudioEngine.h" 16 using namespace CocosDenshion; 17 #endif 18 19 USING_NS_CC; 20 21 static cocos2d::Size designResolutionSize = cocos2d::Size(480, 320); 22 static cocos2d::Size smallResolutionSize = cocos2d::Size(480, 320); 23 static cocos2d::Size mediumResolutionSize = cocos2d::Size(1024, 768); 24 static cocos2d::Size largeResolutionSize = cocos2d::Size(2048, 1536); 25 26 AppDelegate::AppDelegate() 27 { 28 } 29 30 AppDelegate::~AppDelegate() 31 { 32 #if USE_AUDIO_ENGINE 33 AudioEngine::end(); 34 #elif USE_SIMPLE_AUDIO_ENGINE 35 SimpleAudioEngine::end(); 36 #endif 37 } 38 39 // if you want a different context, modify the value of glContextAttrs 40 // it will affect all platforms 41 void AppDelegate::initGLContextAttrs() //這個真心沒看懂是個啥 42 { 43 // set OpenGL context attributes: red,green,blue,alpha,depth,stencil,multisamplesCount 44 GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8, 0}; 45 46 GLView::setGLContextAttrs(glContextAttrs); 47 } 48 49 // if you want to use the package manager to install more packages, 50 // don't modify or remove this function 51 static int register_all_packages() 52 { 53 return 0; //flag for packages manager 54 } 55 56 bool AppDelegate::applicationDidFinishLaunching() { 57 // initialize director 58 auto director = Director::getInstance(); 59 auto glview = director->getOpenGLView(); 60 if(!glview) { 61 #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) 62 glview = GLViewImpl::createWithRect("TestGame", cocos2d::Rect(0, 0, designResolutionSize.width, designResolutionSize.height)); 63 #else 64 glview = GLViewImpl::create("TestGame"); 65 #endif 66 director->setOpenGLView(glview); 67 } 68 69 // turn on display FPS 70 director->setDisplayStats(true); 71 72 // set FPS. the default value is 1.0/60 if you don't call this 73 director->setAnimationInterval(1.0f / 60); 74 75 // Set the design resolution 76 glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER); //不知道是幹啥的 77 auto frameSize = glview->getFrameSize(); 78 // if the frame's height is larger than the height of medium size. 79 if (frameSize.height > mediumResolutionSize.height) 80 { 81 director->setContentScaleFactor(MIN(largeResolutionSize.height/designResolutionSize.height, largeResolutionSize.width/designResolutionSize.width)); 82 } 83 // if the frame's height is larger than the height of small size. 84 else if (frameSize.height > smallResolutionSize.height) 85 { 86 director->setContentScaleFactor(MIN(mediumResolutionSize.height/designResolutionSize.height, mediumResolutionSize.width/designResolutionSize.width)); 87 } 88 // if the frame's height is smaller than the height of medium size. 89 else 90 { 91 director->setContentScaleFactor(MIN(smallResolutionSize.height/designResolutionSize.height, smallResolutionSize.width/designResolutionSize.width)); 92 } 93 94 register_all_packages(); 95 96 // create a scene. it's an autorelease object 97 auto scene = HelloWorld::createScene(); 98 99 // run 100 director->runWithScene(scene); 101 102 return true; 103 } 104 105 // This function will be called when the app is inactive. Note, when receiving a phone call it is invoked. 106 void AppDelegate::applicationDidEnterBackground() { 107 Director::getInstance()->stopAnimation(); 108 109 #if USE_AUDIO_ENGINE 110 AudioEngine::pauseAll(); 111 #elif USE_SIMPLE_AUDIO_ENGINE 112 SimpleAudioEngine::getInstance()->pauseBackgroundMusic(); 113 SimpleAudioEngine::getInstance()->pauseAllEffects(); 114 #endif 115 } 116 117 // this function will be called when the app is active again 118 void AppDelegate::applicationWillEnterForeground() { 119 Director::getInstance()->startAnimation(); 120 121 #if USE_AUDIO_ENGINE 122 AudioEngine::resumeAll(); 123 #elif USE_SIMPLE_AUDIO_ENGINE 124 SimpleAudioEngine::getInstance()->resumeBackgroundMusic(); 125 SimpleAudioEngine::getInstance()->resumeAllEffects(); 126 #endif 127 }
第18行以上的亂七八糟的東西我們就先不看了,無非就是包含標頭檔案啊,使用名稱空間什麼的。這些暫時不管。
第19行是一個巨集,可以進入到裡面看看(這個巨集後面用到的還比較多):
#define USING_NS_CC using namespace cocos2d
可以看到就是使用cocos2d的名稱空間。
- 第21~24行定義了四種大小(從cocos2d::Size可以看出是大小的定義),按照變數名字分別是設計時大小,最小化大小,通常化大小和最大化大小。這裡我還是要重複說一下:現在這些只是我們的猜測,具體的還是要看到相關程式碼才行。
那麼我們可以想,我是不是更改一下這些玩意就可以改變視窗大小了呢?嗯……可以嘗試一下,就先從designResolutionSize下手吧。
我這裡將designResolutionSize引數改成1024,480,果然視窗的大小改變了:
不過改變了其他三個尺寸之後視窗沒什麼變化。暫時先不管吧,反正現在知道designResolutionSize變數儲存的是當前視窗的大小就行了
- 30~37行是解構函式的實現。可以看出是釋放了AudioEngine和SimpleAudioEngine兩個模組。
- 51行的函式用於管理包。當你想要使用包管理器(Package Manager)來安裝多個包的時候,就不要修改或者刪除這裡的程式碼。(你問我包是啥?對不起我也不曉得。你說那你怎麼知道這個是包管理器函式?看註釋啊?)
- 56~103是個大函式,為程式初始化的時候呼叫的函式。讓我們來慢慢分析:
- 58行初始化了導演。按照官方快速入門手冊的說法,導演(Direct)是一個單例物件(關於單例請見單例模式),主要用於切換場景啊,維護層啊什麼的,就和我們生活中的導演差不多。
- 59~67行初始化了檢視。並且對檢視建立失敗做了一定的檢測。最後將這個檢視指定為Direct的檢視。
- 70行打開了FPS顯示。這裡你可以把引數改為false,執行程式你會發下左下角的除錯資訊消失了。這個比較重要,因為我們的遊戲最後釋出的時候是不需要顯示FPS資訊的。
- 73行設定了這個程式的幀率,即FPS。從引數可以看出是60幀每秒(FPS=60)。這個到時候在製作自己的遊戲的時候可以根據實際情況改一改。
- 79~92行分別對視窗大小大於smallResolutionSize ,mediumResolutionSize ,largeResolutionSize時做出行動。這裡的setContentScaleFactor函式是“改變Surface裡畫素的大小”。
- 96行建立了一個場景(Scene)。根據官方手冊知道場景是用來容納物體(比如Sprite啊什麼的)的。
- 100行說明在程式開始的時候執行這個場景
- 接下來的106~115行是用來指定視窗獲得焦點時的相關行動
- 118~127則是視窗失去焦點的相關行動
這裡關於APPDelegate檔案就分析完了。我們看出來好像這個檔案裡面沒有什麼和我們一開始看到的介面元素有關係。好像都是一些對遊戲初始化的工作。所以以後我們不會大幅度更改這裡面的程式碼。
分析HelloWorldScene檔案
那麼首先還是來看看其標頭檔案:
1 #ifndef __HELLOWORLD_SCENE_H__ 2 #define __HELLOWORLD_SCENE_H__ 3 4 #include "cocos2d.h" 5 6 class HelloWorld : public cocos2d::Scene 7 { 8 public: 9 static cocos2d::Scene* createScene(); 10 11 virtual bool init(); 12 13 // a selector callback 14 void menuCloseCallback(cocos2d::Ref* pSender); 15 16 // implement the "static create()" method manually 17 CREATE_FUNC(HelloWorld); 18 }; 19 20 #endif // __HELLOWORLD_SCENE_H__
這個標頭檔案挺簡單的,定義了一個HelloWorld類繼承自cocos2d::Scene。
- 第14行的函式定義了一個選單關閉的回撥函式(你說,我咋沒在介面上看到有選單啊?別急,讓我們來一步一步看看這個選單是從哪裡來的)
- 第17行使用了CREATE_FUNC巨集,用於建立層。我們可以跳轉到其定義看一下:
-
#define CREATE_FUNC(__TYPE__) \ static __TYPE__* create() \ { \ __TYPE__ *pRet = new(std::nothrow) __TYPE__(); \ if (pRet && pRet->init()) \ { \ pRet->autorelease(); \ return pRet; \ } \ else \ { \ delete pRet; \ pRet = nullptr; \ return nullptr; \ } \ }
顯然,這個巨集其實是定義了一個static __TYPE__* create()函式。
接下來看看實現檔案:
1 #include "HelloWorldScene.h" 2 #include "SimpleAudioEngine.h" 3 4 USING_NS_CC; 5 6 Scene* HelloWorld::createScene() 7 { 8 return HelloWorld::create(); 9 } 10 11 // Print useful error message instead of segfaulting when files are not there. 12 static void problemLoading(const char* filename) 13 { 14 printf("Error while loading: %s\n", filename); 15 printf("Depending on how you compiled you might have to add 'Resources/' in front of filenames in HelloWorldScene.cpp\n"); 16 } 17 18 // on "init" you need to initialize your instance 19 bool HelloWorld::init() 20 { 21 ////////////////////////////// 22 // 1. super init first 23 if ( !Scene::init() ) 24 { 25 return false; 26 } 27 28 auto visibleSize = Director::getInstance()->getVisibleSize(); 29 Vec2 origin = Director::getInstance()->getVisibleOrigin(); 30 31 ///////////////////////////// 32 // 2. add a menu item with "X" image, which is clicked to quit the program 33 // you may modify it. 34 35 // add a "close" icon to exit the progress. it's an autorelease object 36 auto closeItem = MenuItemImage::create( 37 "CloseNormal.png", 38 "CloseSelected.png", 39 CC_CALLBACK_1(HelloWorld::menuCloseCallback, this)); 40 41 if (closeItem == nullptr || 42 closeItem->getContentSize().width <= 0 || 43 closeItem->getContentSize().height <= 0) 44 { 45 problemLoading("'CloseNormal.png' and 'CloseSelected.png'"); 46 } 47 else 48 { 49 float x = origin.x + visibleSize.width - closeItem->getContentSize().width/2; 50 float y = origin.y + closeItem->getContentSize().height/2; 51 closeItem->setPosition(Vec2(x,y)); 52 } 53 54 // create menu, it's an autorelease object 55 auto menu = Menu::create(closeItem, NULL); 56 menu->setPosition(Vec2::ZERO); 57 this->addChild(menu, 1); 58 59 ///////////////////////////// 60 // 3. add your codes below... 61 62 // add a label shows "Hello World" 63 // create and initialize a label 64 65 auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24); 66 if (label == nullptr) 67 { 68 problemLoading("'fonts/Marker Felt.ttf'"); 69 } 70 else 71 { 72 // position the label on the center of the screen 73 label->setPosition(Vec2(origin.x + visibleSize.width/2, 74 origin.y + visibleSize.height - label->getContentSize().height)); 75 76 // add the label as a child to this layer 77 this->addChild(label, 1); 78 } 79 80 // add "HelloWorld" splash screen" 81 auto sprite = Sprite::create("HelloWorld.png"); 82 if (sprite == nullptr) 83 { 84 problemLoading("'land.png'"); 85 } 86 else 87 { 88 // position the sprite on the center of the screen 89 sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y)); 90 91 // add the sprite as a child to this layer 92 this->addChild(sprite, 0); 93 } 94 return true; 95 } 96 97 98 void HelloWorld::menuCloseCallback(Ref* pSender) 99 { 100 //Close the cocos2d-x game scene and quit the application 101 Director::getInstance()->end(); 102 103 #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) 104 exit(0); 105 #endif 106 107 }
- 第六行的createScene()函式呼叫了HelloWorld類的create方法建立了一個層。你說誒不對啊,上面那個HelloWorld類的定義裡面沒有create方法呀。這個時候你可以回過頭再去看看CREATE_FUNC巨集的程式碼。其實CREATE_FUNC函式就是create函式啦。這個函式被APPDelegate類呼叫過(見APPDelegate的實現檔案97行)
- 12~16行定義的函式用於輸出錯誤資訊 19~95行定義了HelloWorld類的init函式。這個函式是在CREATE_FUNC裡面被呼叫的。我們來看看這個函式裡面到底有些啥
-
- 23~26行為第一步——建立Scene
- 36~57建立選單(原來選單在這裡,趕緊看看那這個選單是什麼)
- 首先36行通過MenuItemImage的create方法建立一個選單元件。注意到這裡面的引數!這個引數正是我們前面在介面上看到的開關機影象!原來那個東西不是按鈕,是一個選單。
- 41~52行判斷是否建立選單項成功,並且調整選單項位置為右下角
- 55行通過選單元件建立一個選單
- 56行設定選單位置
- 57行將選單放到層的樹裡面(就是放到層中)
- 65~95行,註釋上寫著“加入你自己的程式碼”。我們來看看cocos給我們加了什麼程式碼
- 65行建立了一個label,而且是使用TTF字型建立的。用於顯示HelloWorld(果然HelloWorld是個label)
- 66~78行判斷label是否建立成功,並且設定了label的位置,加入了層。
- 81行建立了一個Sprite,也就是精靈。用於繪製那個logo。
- 82~93行同樣是判斷精靈是否建立成功,然後修改位置,加入層。
- 最後98~107定義了選單的回撥函式。這裡如果點選了就會推出程式。然後這裡有一個比較重要的東西:CC_TARGET_PLATFORM常量。這個常量儲存著這個程式執行的作業系統。這裡判斷是否為IOS作業系統。我們可以使用這個常量來獲得當前作業系統。
那麼知道了這些之後,你可以更改一下程式碼來產生自己的介面。我這裡改變了label的顯示,並且改變了logo(把你要替換logo的圖片放在Resource/res下,而且最好是png。我一開始使用bmp不能讀取):
至此,我們的demo就分析完成了。
函式呼叫的過程
可能看完上面的分析有些人會有些頭昏:“這說的都是啥和啥啊我現在腦子裡一片混亂”。沒關係,下面我們使用函式之間的呼叫圖來說明一下這四個檔案到底幹了什麼:
這下清楚許多了吧。
總結
現在我們來總結一些常用的程式碼:
- designResolutionSize變數用於設定視窗大小
- director->setDisplayStats(true);用於設定是否顯示FPS
- director->setAnimationInterval(1.0f / 60);用於設定FPS
其他的程式碼,有的涉及到選單和label的,我們放到以後的專題去說。