cocos2D(三)---- 第一個cocos2d程序的代碼分析

第一講中已經新建了第一個cocos2d程序,運行效果如下:


在這講中我們來分析下里面的代碼,瞭解cocos2d的工作原理,看看屏幕上的這個"Hello World"是如何顯示出來的。

這是HelloWorld項目的代碼結構:

下面,我們開始分析項目中的這些源文件:


從程序的入口點開始

這麼多源文件在這裏,究竟先看哪個呢?有些人可能會先挑內容少的來看,覺得這樣就可以輕鬆解決掉一個源文件了,其實這是不對的,這樣看起來更加是一頭霧水,根本搞不清楚每個源文件之間的聯繫。正確的做法應該是從程序的入口開始看起,慢慢理順各個源文件之間的關係。

有過iOS開發經驗的朋友都知道,一個iOS程序的入口是main函數,這個main函數存在於main.m中。打開main.m文件,看看main函數的內容:

[java] view plaincopy
  1. int main(int argc, char *argv[]) {  
  2.     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];  
  3.     int retVal = UIApplicationMain(argc, argv, nil, @"AppDelegate");  
  4.     [pool release];  
  5.     return retVal;  
  6. }  

重點是這句代碼:UIApplicationMain(argc, argv, nil, @"AppDelegate"),UIApplicationMain函數會根據第4個參數傳入的類名創建一個應用程序的代理對象,這裏創建的是AppDelegate對象,因此AppDelegate對象就是整個應用程序的代理。

那應用程序的代理有什麼作用呢?

UIApplicationMain函數創建好應用程序的代理之後,會開啓事件循環,一旦監聽到系統事件,就會通知代理對象,並調用代理對象相關的生命週期方法來處理事件。比如:

* ios設備的內存極其優先,如果一個應用程序佔用了太多內存,操作系統會發出內存警告,在應用程序接收到這個事件後它會調用代理的applicationDidReceiveMemoryWarning方法,代理在這個方法內可以進行釋放內存的操作以防止操作系統強制終止應用程序的運行

* 當應用程序成功加載完畢後,會調用代理的application:didFinishLaunchingWithOptions:方法,一般會在這個方法裏面初始化應用程序的第一個界面


AppDelegate解讀

從上面的分析可知,一般會在AppDelegate的application:didFinishLaunchingWithOptions:方法中初始化應用程序的第一個界面,是的,開發者會在該方法中添加cocos2d的所有初始化代碼

打開AppDelegate.m,查看application:didFinishLaunchingWithOptions:方法

提示:我在這個方法裏面加了相應的中文註釋

[java] view plaincopy
  1. - (void) applicationDidFinishLaunching:(UIApplication*)application  
  2. {  
  3.     // 初始化窗口  
  4.     window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];  
  5.       
  6.     // 設置CCDirector的類型  
  7.     if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )  
  8.         [CCDirector setDirectorType:kCCDirectorTypeDefault];  
  9.       
  10.     // 獲取CCDirector的單例對象  
  11.     CCDirector *director = [CCDirector sharedDirector];  
  12.       
  13.     // 初始化控制器  
  14.     viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];  
  15.     viewController.wantsFullScreenLayout = YES;  
  16.       
  17.     // 創建一個視圖對象  
  18.     EAGLView *glView = [EAGLView viewWithFrame:[window bounds]  
  19.                                    pixelFormat:kEAGLColorFormatRGB565  
  20.                                    depthFormat:0];  
  21.     // 關聯這個視圖對象到CCDirector  
  22.     [director setOpenGLView:glView];  
  23.       
  24.     // 設置屏幕方向  
  25. #if GAME_AUTOROTATION == kGameAutorotationUIViewController  
  26.     // 如果是使用UIViewController來實現旋轉,就設置豎屏  
  27.     [director setDeviceOrientation:kCCDeviceOrientationPortrait];  
  28. #else  
  29.     // 其他情況,就設置橫屏  
  30.     [director setDeviceOrientation:kCCDeviceOrientationLandscapeLeft];  
  31. #endif  
  32.       
  33.     // 設置刷新間隔時間  
  34.     [director setAnimationInterval:1.0/60];  
  35.     // 設置是否要顯示FPS  
  36.     [director setDisplayFPS:YES];  
  37.       
  38.       
  39.     // 設置控制器的視圖  
  40.     [viewController setView:glView];  
  41.       
  42.     // 添加控制器的視圖到window中  
  43.     [window addSubview: viewController.view];  
  44.     // 顯示window  
  45.     [window makeKeyAndVisible];  
  46.       
  47.     // 設置紋理格式  
  48.     [CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA8888];  
  49.   
  50.     // 消除啓動時的閃爍  
  51.     [self removeStartupFlicker];  
  52.       
  53.     // 設置第一個顯示的屏幕  
  54.     [[CCDirector sharedDirector] runWithScene: [HelloWorldLayer scene]];  
  55. }  

看完這個方法後,你可能會一頭霧水,看到一大堆沒見過的API,這些就是cocos2d的API。

1.UIKit與OpenGL

這麼多看不懂的代碼,該怎麼解讀呢?有些人可能會硬着頭皮一行一行按順序往下解讀,這樣往往是事倍功半。

一般是先找自己能夠看懂的代碼,比如這幾句:

[java] view plaincopy
  1. // 初始化控制器  
  2. viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];  
  3. viewController.wantsFullScreenLayout = YES;  
  4. // 設置控制器的視圖  
  5. [viewController setView:glView];  
  6.       
  7. // 添加控制器的視圖到window中  
  8. [window addSubview: viewController.view];  
  9. // 顯示window  
  10. [window makeKeyAndVisible];  
可以看出,屏幕上顯示的內容就是這個glView視圖,這個glView是EAGLView類型的:

[java] view plaincopy
  1. // 創建一個視圖對象  
  2. EAGLView *glView = [EAGLView viewWithFrame:[window bounds]  
  3.                                    pixelFormat:kEAGLColorFormatRGB565  
  4.                                    depthFormat:0];  
cocos2d利用OpenGL將內容都渲染到了這個glView上,最後呈現到屏幕上給用戶看。因此,cocos2d的本質是在UIKit和OpenGL之間進行了轉換。


2.CCDirector

cocos2d中以節點(CCNode)爲基本元素,整個遊戲都是由節點構成的,其實一個很重要的節點元素就是場景(CCScene類,繼承自CCNode),一個遊戲裏面可能有很多個場景,比如闖關遊戲,可以一個關卡就是一個場景,一個遊戲設置界面也可以是一個場景。怎樣才能正常顯示一個場景呢?那麼就需要CCDirector這個類,沒有CCDirector,就不能顯示場景。

CCDirector類是整個cocos2d遊戲引擎的核心,全局只有一個實例,通過[CCDirector sharedDirector]可以獲取這個單例對象。

[java] view plaincopy
  1. // 獲取CCDirector的單例對象  
  2. CCDirector *director = [CCDirector sharedDirector];  

CCDirector的主要用途:

* 運行、替換、推入和彈出場景(即場景過渡)

* 訪問正在運行的場景

* 暫停、恢復、終止遊戲

* 在UIKit和OpengGL之間轉換座標

* 獲取窗口的尺寸


下面這句代碼非常關鍵:

[java] view plaincopy
  1. // 關聯這個視圖對象到CCDirector  
  2. [director setOpenGLView:glView];  

給CCDirector設置視圖後,CCDirector才知道場景(CCScene)上的內容要渲染到哪個視圖上面。因此,說CCDirector是UIKit和OpenGL之間的橋樑,一點也不爲過


cocos2d提供了4種類型的CCDirector,不同類型的CCDirector有不同的更新遊戲狀態的方式,這些更新方式會對遊戲的性能、與UIKit視圖的兼容性產生很大影響。

下面這句代碼就是設置CCDirector的類型:

[java] view plaincopy
  1. // 設置CCDirector的類型,如果iOS<3.1,就不支持kCCDirectorTypeDisplayLink,使用kCCDirectorTypeDefault  
  2. if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )  
  3.         [CCDirector setDirectorType:kCCDirectorTypeDefault];  
可以供設置的4種CCDirector:

kCCDirectorTypeNSTimer \ kCCDirectorTypeDefault 最慢
kCCDirectorTypeMainLoop 比NSTimer快,但是與UIKit視圖存在兼容性問題
kCCDirectorTypeThreadMainLoop 比NSTimer快,但是與UIKit視圖存在兼容性問題
kCCDirectorTypeDisplayLink 最快,最實用,但iOS版本至少是3.1

3.設置屏幕方向

通過CCDirector來設置屏幕方向

[java] view plaincopy
  1. // 設置屏幕方向    
  2. #if GAME_AUTOROTATION == kGameAutorotationUIViewController    
  3.     // 如果是使用UIViewController來實現旋轉,就設置豎屏    
  4.     [director setDeviceOrientation:kCCDeviceOrientationPortrait];    
  5. #else    
  6.     // 其他情況,就設置橫屏    
  7.     [director setDeviceOrientation:kCCDeviceOrientationLandscapeLeft];    
  8. #endif    
GAME_AUTOROTATION是一個宏定義,可以用來判斷設備對旋轉的支持情況,iOS第1和2代設備上面的旋轉是十分耗性能的,但從iOS第3代設備開始,可以使用UIViewController來輕鬆實現自動旋轉。

下面列出設備支持的所有方向:

kCCDeviceOrientationPortrait
kCCDeviceOrientationPortraitUpsideDown
kCCDeviceOrientationLandscapeLeft
kCCDeviceOrientationLandscapeRight


4.設置遊戲幀率

大家都知道,遊戲界面上的內容是需要經常刷新的,比如一個子彈打出去,需要經常刷新子彈的位置。刷新速度就取決於遊戲幀率。

[java] view plaincopy
  1. [director setAnimationInterval:1.0/60];  
這裏設置的1.0/60並不是指遊戲幀率,是指刷幀的時間間隔,即屏幕連續2次刷新之間的時間間隔:1.0/60秒。換算一下,可以得出遊戲幀率爲60fps(frame per seconds,幀/秒),即1秒鐘刷新60幀。當然,如果遊戲比較複雜,CPU/GPU需要畫大於1.0/60秒的時間來刷新屏幕,就無法保證遊戲始終保持60fps的刷新速度,會造成幀率不穩定。如果遊戲的幀率發生大幅度波動,會造成時快時慢的效果,會嚴重降低玩家的用戶體驗,因此,複雜遊戲的幀率不用設置得太高,最好設置爲30fps。

注意:由於設備的限制,在iOS設備上的幀率不能大於60fps。如果強迫cocos2d以大於60fps的幀率進行渲染,很有可能會反而使幀率降低。因此,要想達到最快的渲染速度,使用60fps的幀率即可。


5.顯示遊戲幀率

[java] view plaincopy
  1. // 設置是否要顯示FPS  
  2. [director setDisplayFPS:YES];  
設置了顯示幀率後,屏幕左下角顯示的數字就是遊戲的幀率,當前是59.9fps。

cocos2d會隔一段時間就更新這個數值,通過修改ccConfig.h的CC_DIRECTOR_FPS_INTERVAL值可以調整刷新數值的時間間隔,默認是0.1,即1秒鐘更新10次。ccConfig.h在項目的libs/cocos2d文件夾中。

[java] view plaincopy
  1. #ifndef CC_DIRECTOR_FPS_INTERVAL  
  2. #define CC_DIRECTOR_FPS_INTERVAL (0.1f)  
  3. #endif  


6.設置遊戲的第一個場景

[java] view plaincopy
  1. [[CCDirector sharedDirector] runWithScene: [HelloWorldLayer scene]];  
這裏調用CCDirector的runWithScene:方法來設置遊戲的第一個場景,這個場景對象是通過[HelloWorldLayer scene]這個靜態方法創建。

因此,需要搞清楚屏幕上顯示的"Hello World"是怎麼弄出來的,還得查看HelloWorldLayer這個類


7.在發出內存警告時釋放資源

[java] view plaincopy
  1. - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {  
  2.     [[CCDirector sharedDirector] purgeCachedData];  
  3. }  

HelloWorldLayer解讀

打開HelloWorldLayer.h,可以發現HelloWorldLayer繼承了CCLayer,意爲圖層

[java] view plaincopy
  1. @interface HelloWorldLayer : CCLayer  
  2. {  
  3. }  
  4.   
  5. // returns a CCScene that contains the HelloWorldLayer as the only child  
  6. +(CCScene *) scene;  
  7.   
  8. @end  

那之前看到的場景(CCScene)和圖層(CCLayer)究竟有什麼聯繫呢?

下面我先詳細闡述場景(CCScene)和圖層(CCLayer)之間的關係:

一個遊戲裏面可能有很多個場景,比如闖關遊戲,可以一個關卡就是一個場景,一個遊戲設置界面也可以是一個場景。一個場景裏面又可以包含多個圖層。

拿捕魚達人來說,下面列出捕魚達人中的4個場景:

場景1
場景2

場景3
場景4

可以看出,不同的業務邏輯就放在不同的場景中。但是,如果一個場景過於複雜,場景裏面又可以分出多個圖層(CCLayer)。

比如場景4中的捕魚界面,根據圖層的功能,大致可以分爲3個圖層:


1> 底部的Background Layer是背景層,用來顯示背景圖片

2> 中間的Game Layer是遊戲層,用來顯示遊戲中的精靈(CCSprite,可以用來表示遊戲中的人物、道具等),這裏的魚就是精靈

3> 頂部的UI DOM Menu是菜單層,用來顯示一些菜單按鈕、控制按鈕


再查看HelloWorldLayer.m中scene方法的實現

[java] view plaincopy
  1. +(CCScene *) scene  
  2. {  
  3.     // 創建一個場景對象  
  4.     CCScene *scene = [CCScene node];  
  5.       
  6.     // 創建一個圖層對象  
  7.     HelloWorldLayer *layer = [HelloWorldLayer node];  
  8.       
  9.     // 添加圖層到場景中  
  10.     [scene addChild: layer];  
  11.       
  12.     // 返回場景  
  13.     return scene;  
  14. }  
CCScene和CCLayer都繼承自CCNode,可以通過CCNode的+(id) node方法快速創建一個自動釋放的對象。

這裏使用[CCScene node]創建了一個CCScene對象,使用[HelloWorldLayer node]創建了一個HelloWorldLayer對象。

最後通過[scene addChild: layer]將圖層layer添加到場景scene中,可以看出layer是scene的一個子節點。實際上addChild:方法也是屬於CCNode的,可以看出每個CCNode都有自己的子節點。


那"Hello World"這段文字是怎麼顯示到屏幕上的呢?看看HelloWorldLayer的init方法

[java] view plaincopy
  1. -(id) init  
  2. {  
  3.     // always call "super" init  
  4.     // Apple recommends to re-assign "self" with the "super" return value  
  5.     if( (self=[super init])) {  
  6.           
  7.         // create and initialize a Label  
  8.         CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];  
  9.   
  10.         // ask director the the window size  
  11.         CGSize size = [[CCDirector sharedDirector] winSize];  
  12.       
  13.         // position the label on the center of the screen  
  14.         label.position =  ccp( size.width /2 , size.height/2 );  
  15.           
  16.         // add the label as a child to this Layer  
  17.         [self addChild: label];  
  18.     }  
  19.     return self;  
  20. }  
首先初始化了一個CCLabelTTF標籤對象,標籤顯示的文字爲"Hello World",使用Marker Felt字體,字體大小爲64,CCLabelTTF繼承自CCSprite,屬於精靈,CCSprite又繼承自CCNode。

[java] view plaincopy
  1. CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];  
接着獲取屏幕的寬度和高度

[java] view plaincopy
  1. CGSize size = [[CCDirector sharedDirector] winSize];  
設置標籤在父節點中的位置(默認情況下,精靈的中心點會在position屬性所指定的位置上)

[java] view plaincopy
  1. label.position =  ccp( size.width /2 , size.height/2 );  
cpp其實是個宏定
[java] view plaincopy
  1. #define ccp(__X__,__Y__) CGPointMake(__X__,__Y__)  
最後添加標籤到圖層中

[java] view plaincopy
  1. [self addChild: label];  

就這樣,"Hello World"就顯示到我們的屏幕上

分析可得,我們這個遊戲裏面存在着3個節點:

CCScene裏面有個CCLayer,CClayer裏面有個CCLabelTTF


總結

說了這麼多內容,最後做個大總結:

1.要想利用cocos2d在屏幕上顯示點東西,就必須使用CCDirector運行一個場景(CCScene),比如

[java] view plaincopy
  1. [[CCDirector sharedDirector] runWithScene: [HelloWorldLayer scene]];  

2.場景(CCScene)可以添加多個圖層(CCLayer),每個圖層又可以添加其他子節點,比如精靈(CCSprite)

3.cocos2d會利用OpenGL將場景(CCScene)中的所有內容渲染到UIKit視圖上(這裏用的是EAGLView),EAGLView被添加到UIWindow中,最終顯示在屏幕上

4.大部分情況下,都是直接面向cocos2d進行開發,即直接用cocos2d的CCSprite、CCLayer、CCScene等類進行開發,不需要關心OpenGL與UIKit之間的轉換

5.cocos2d的基本元素是節點(CCNode),屏幕上的任何東西都可以稱之爲節點,像我們常用的CCSprite、CCLayer、CCScene,都是CCNode的子類,因此,它們都是節點。一個節點又可以包含多個子節點



原文地址:http://blog.csdn.net/q199109106q/article/details/8591706

感謝作者~!

發佈了27 篇原創文章 · 獲贊 3 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章