在第一讲中已经新建了第一个cocos2d程序,执行效果例如以下:
在这讲中我们来分析下里面的代码,了解cocos2d的工作原理,看看屏幕上的这个"Hello World"是怎样显示出来的。
这是HelloWorld项目的代码结构:
以下,我们開始分析项目中的这些源文件:
从程序的入口点開始
这么多源文件在这里,到底先看哪个呢?有些人可能会先挑内容少的来看,认为这样就能够轻松解决掉一个源文件了。事实上这是不正确的,这样看起来更加是一头雾水,根本搞不清楚每一个源文件之间的联系。正确的做法应该是从程序的入口開始看起,慢慢理顺各个源文件之间的关系。
有过iOS开发经验的朋友都知道。一个iOS程序的入口是main函数。这个main函数存在于main.m中。
打开main.m文件。看看main函数的内容:
plaincopy
- int main(int argc, char *argv[]) {
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
- int retVal = UIApplicationMain(argc, argv, nil, @"AppDelegate");
- [pool release];
- return retVal;
- }
重点是这句代码:UIApplicationMain(argc, argv, nil, @"AppDelegate"),UIApplicationMain函数会依据第4个參数传入的类名创建一个应用程序的代理对象,这里创建的是AppDelegate对象,因此AppDelegate对象就是整个应用程序的代理。
那应用程序的代理有什么作用呢?
UIApplicationMain函数创建好应用程序的代理之后,会开启事件循环。一旦监听到系统事件,就会通知代理对象,并调用代理对象相关的生命周期方法来处理事件。
比方:
* ios设备的内存极其优先。假设一个应用程序占用了太多内存,操作系统会发出内存警告。在应用程序接收到这个事件后它会调用代理的applicationDidReceiveMemoryWarning方法。代理在这种方法内能够进行释放内存的操作以防止操作系统强制终止应用程序的执行
* 当应用程序成功载入完成后,会调用代理的application:didFinishLaunchingWithOptions:方法。通常会在这种方法里面初始化应用程序的第一个界面
AppDelegate解读
从上面的分析可知,通常会在AppDelegate的application:didFinishLaunchingWithOptions:方法中初始化应用程序的第一个界面。是的,开发人员会在该方法中加入cocos2d的全部初始化代码
打开AppDelegate.m。查看application:didFinishLaunchingWithOptions:方法
提示:我在这种方法里面加了对应的中文凝视
plaincopy
- - (void) applicationDidFinishLaunching:(UIApplication*)application
- {
- // 初始化窗体
- window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- // 设置CCDirector的类型
- if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
- [CCDirector setDirectorType:kCCDirectorTypeDefault];
- // 获取CCDirector的单例对象
- CCDirector *director = [CCDirector sharedDirector];
- // 初始化控制器
- viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
- viewController.wantsFullScreenLayout = YES;
- // 创建一个视图对象
- EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
- pixelFormat:kEAGLColorFormatRGB565
- depthFormat:0];
- // 关联这个视图对象到CCDirector
- [director setOpenGLView:glView];
- // 设置屏幕方向
- #if GAME_AUTOROTATION == kGameAutorotationUIViewController
- // 假设是使用UIViewController来实现旋转,就设置竖屏
- [director setDeviceOrientation:kCCDeviceOrientationPortrait];
- #else
- // 其它情况,就设置横屏
- [director setDeviceOrientation:kCCDeviceOrientationLandscapeLeft];
- #endif
- // 设置刷新间隔时间
- [director setAnimationInterval:1.0/60];
- // 设置是否要显示FPS
- [director setDisplayFPS:YES];
- // 设置控制器的视图
- [viewController setView:glView];
- // 加入控制器的视图到window中
- [window addSubview: viewController.view];
- // 显示window
- [window makeKeyAndVisible];
- // 设置纹理格式
- [CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA8888];
- // 消除启动时的闪烁
- [self removeStartupFlicker];
- // 设置第一个显示的屏幕
- [[CCDirector sharedDirector] runWithScene: [HelloWorldLayer scene]];
- }
看完这种方法后。你可能会一头雾水。看到一大堆没见过的API,这些就是cocos2d的API。
1.UIKit与OpenGL
这么多看不懂的代码,该怎么解读呢?有些人可能会硬着头皮一行一行按顺序往下解读,这样往往是事倍功半。
通常是先找自己可以看懂的代码。比方这几句:
plaincopy
- // 初始化控制器
- viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
- viewController.wantsFullScreenLayout = YES;
- // 设置控制器的视图
- [viewController setView:glView];
- // 加入控制器的视图到window中
- [window addSubview: viewController.view];
- // 显示window
- [window makeKeyAndVisible];
能够看出。屏幕上显示的内容就是这个glView视图。这个glView是EAGLView类型的:
plaincopy
- // 创建一个视图对象
- EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
- pixelFormat:kEAGLColorFormatRGB565
- depthFormat:0];
cocos2d利用OpenGL将内容都渲染到了这个glView上。最后呈现到屏幕上给用户看。因此。cocos2d的本质是在UIKit和OpenGL之间进行了转换。
2.CCDirector
cocos2d中以节点(CCNode)为基本元素,整个游戏都是由节点构成的,事实上一个非常重要的节点元素就是场景(CCScene类。继承自CCNode)。一个游戏里面可能有非常多个场景。比方闯关游戏,能够一个关卡就是一个场景,一个游戏设置界面也能够是一个场景。如何才干正常显示一个场景呢?那么就须要CCDirector这个类,没有CCDirector,就不能显示场景。
CCDirector类是整个cocos2d游戏引擎的核心,全局仅仅有一个实例,通过[CCDirector sharedDirector]能够获取这个单例对象。
plaincopy
- // 获取CCDirector的单例对象
- CCDirector *director = [CCDirector sharedDirector];
CCDirector的主要用途:
* 执行、替换、推入和弹出场景(即场景过渡)
* 訪问正在执行的场景
* 暂停、恢复、终止游戏
* 在UIKit和OpengGL之间转换坐标
* 获取窗体的尺寸
以下这句代码很关键:
plaincopy
- // 关联这个视图对象到CCDirector
- [director setOpenGLView:glView];
给CCDirector设置视图后,CCDirector才知道场景(CCScene)上的内容要渲染到哪个视图上面。因此,说CCDirector是UIKit和OpenGL之间的桥梁。一点也不为过
cocos2d提供了4种类型的CCDirector,不同类型的CCDirector有不同的更新游戏状态的方式。这些更新方式会对游戏的性能、与UIKit视图的兼容性产生非常大影响。
以下这句代码就是设置CCDirector的类型:
plaincopy
- // 设置CCDirector的类型,假设iOS<3.1,就不支持kCCDirectorTypeDisplayLink,使用kCCDirectorTypeDefault
- if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
- [CCDirector setDirectorType:kCCDirectorTypeDefault];
能够供设置的4种CCDirector:
kCCDirectorTypeNSTimer \ kCCDirectorTypeDefault | 最慢 |
kCCDirectorTypeMainLoop | 比NSTimer快,可是与UIKit视图存在兼容性问题 |
kCCDirectorTypeThreadMainLoop | 比NSTimer快。可是与UIKit视图存在兼容性问题 |
kCCDirectorTypeDisplayLink | 最快,最有用。但iOS版本号至少是3.1 |
3.设置屏幕方向
通过CCDirector来设置屏幕方向
plaincopy
- // 设置屏幕方向
- #if GAME_AUTOROTATION == kGameAutorotationUIViewController
- // 假设是使用UIViewController来实现旋转。就设置竖屏
- [director setDeviceOrientation:kCCDeviceOrientationPortrait];
- #else
- // 其它情况,就设置横屏
- [director setDeviceOrientation:kCCDeviceOrientationLandscapeLeft];
- #endif
GAME_AUTOROTATION是一个宏定义。能够用来推断设备对旋转的支持情况。iOS第1和2代设备上面的旋转是十分耗性能的,但从iOS第3代设备開始,能够使用UIViewController来轻松实现自己主动旋转。
以下列出设备支持的全部方向:
kCCDeviceOrientationPortrait | |
kCCDeviceOrientationPortraitUpsideDown | |
kCCDeviceOrientationLandscapeLeft | |
kCCDeviceOrientationLandscapeRight |
4.设置游戏帧率
大家都知道。游戏界面上的内容是须要常常刷新的,比方一个子弹打出去。须要常常刷新子弹的位置。刷新速度就取决于游戏帧率。
plaincopy
- [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.显示游戏帧率
plaincopy
- // 设置是否要显示FPS
- [director setDisplayFPS:YES];
设置了显示帧率后。屏幕左下角显示的数字就是游戏的帧率,当前是59.9fps。
cocos2d会隔一段时间就更新这个数值,通过改动ccConfig.h的CC_DIRECTOR_FPS_INTERVAL值能够调整刷新数值的时间间隔,默认是0.1,即1秒钟更新10次。ccConfig.h在项目的libs/cocos2d目录中。
plaincopy
- #ifndef CC_DIRECTOR_FPS_INTERVAL
- #define CC_DIRECTOR_FPS_INTERVAL (0.1f)
- #endif
6.设置游戏的第一个场景
plaincopy
- [[CCDirector sharedDirector] runWithScene: [HelloWorldLayer scene]];
这里调用CCDirector的runWithScene:方法来设置游戏的第一个场景,这个场景对象是通过[HelloWorldLayer scene]这个静态方法创建。
因此,须要搞清楚屏幕上显示的"Hello World"是怎么弄出来的,还得查看HelloWorldLayer这个类
7.在发出内存警告时释放资源
plaincopy
- - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
- [[CCDirector sharedDirector] purgeCachedData];
- }
HelloWorldLayer解读
打开HelloWorldLayer.h,能够发现HelloWorldLayer继承了CCLayer,意为图层
plaincopy
- @interface HelloWorldLayer : CCLayer
- {
- }
- // returns a CCScene that contains the HelloWorldLayer as the only child
- +(CCScene *) scene;
- @end
那之前看到的场景(CCScene)和图层(CCLayer)到底有什么联系呢?
以下我先具体阐述场景(CCScene)和图层(CCLayer)之间的关系:
一个游戏里面可能有非常多个场景,比方闯关游戏,能够一个关卡就是一个场景,一个游戏设置界面也能够是一个场景。一个场景里面又能够包括多个图层。
拿捕鱼达人来说,以下列出捕鱼达人中的4个场景:
场景1 | |
场景2 | |
场景3 | |
场景4 |
能够看出,不同的业务逻辑就放在不同的场景中。可是。假设一个场景过于复杂,场景里面又能够分出多个图层(CCLayer)。
比方场景4中的捕鱼界面。依据图层的功能,大致能够分为3个图层:
1> 底部的Background Layer是背景层,用来显示背景图片
2> 中间的Game Layer是游戏层,用来显示游戏中的精灵(CCSprite。能够用来表示游戏中的人物、道具等),这里的鱼就是精灵
3> 顶部的UI DOM Menu是菜单层。用来显示一些菜单button、控制button
再查看HelloWorldLayer.m中scene方法的实现
plaincopy
- +(CCScene *) scene
- {
- // 创建一个场景对象
- CCScene *scene = [CCScene node];
- // 创建一个图层对象
- HelloWorldLayer *layer = [HelloWorldLayer node];
- // 加入图层到场景中
- [scene addChild: layer];
- // 返回场景
- return scene;
- }
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方法
plaincopy
- -(id) init
- {
- // always call "super" init
- // Apple recommends to re-assign "self" with the "super" return value
- if( (self=[super init])) {
- // create and initialize a Label
- CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];
- // ask director the the window size
- CGSize size = [[CCDirector sharedDirector] winSize];
- // position the label on the center of the screen
- label.position = ccp( size.width /2 , size.height/2 );
- // add the label as a child to this Layer
- [self addChild: label];
- }
- return self;
- }
首先初始化了一个CCLabelTTF标签对象,标签显示的文字为"Hello World",使用Marker Felt字体,字体大小为64,CCLabelTTF继承自CCSprite,属于精灵。CCSprite又继承自CCNode。
plaincopy
- CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];
接着获取屏幕的宽度和高度
plaincopy
- CGSize size = [[CCDirector sharedDirector] winSize];
设置标签在父节点中的位置(默认情况下。精灵的中心点会在position属性所指定的位置上)
plaincopy
- label.position = ccp( size.width /2 , size.height/2 );
cpp事实上是个宏定义
plaincopy
- #define ccp(__X__,__Y__) CGPointMake(__X__,__Y__)
最后加入标签到图层中
plaincopy
- [self addChild: label];
就这样,"Hello World"就显示到我们的屏幕上了
分析可得。我们这个游戏里面存在着3个节点:
CCScene里面有个CCLayer,CClayer里面有个CCLabelTTF
总结
说了这么多内容。最后做个大总结:
1.要想利用cocos2d在屏幕上显示点东西,就必须使用CCDirector执行一个场景(CCScene)。比方
plaincopy
- [[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
感谢作者~。