三、第一个cocos2d程序的代码分析
http://blog.csdn.net/q199109106q/article/details/8591706
在第一讲中已经新建了第一个cocos2d程序,运行效果如下:

在这讲中我们来分析下里面的代码,了解cocos2d的工作原理,看看屏幕上的这个"Hello World"是如何显示出来的。
这是HelloWorld项目的代码结构:

下面,我们开始分析项目中的这些源文件:
从程序的入口点开始
这么多源文件在这里,究竟先看哪个呢?有些人可能会先挑内容少的来看,觉得这样就可以轻松解决掉一个源文件了,其实这是不对的,这样看起来更加是一头雾水,根本搞不清楚每个源文件之间的联系。正确的做法应该是从程序的入口开始看起,慢慢理顺各个源文件之间的关系。
有过iOS开发经验的朋友都知道,一个iOS程序的入口是main函数,这个main函数存在于main.m中。打开main.m文件,看看main函数的内容:
- 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:方法
提示:我在这个方法里面加了相应的中文注释
- - (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
这么多看不懂的代码,该怎么解读呢?有些人可能会硬着头皮一行一行按顺序往下解读,这样往往是事倍功半。
一般是先找自己能够看懂的代码,比如这几句:
- // 初始化控制器
- viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
- viewController.wantsFullScreenLayout = YES;
- // 设置控制器的视图
- [viewController setView:glView];
- // 添加控制器的视图到window中
- [window addSubview: viewController.view];
- // 显示window
- [window makeKeyAndVisible];
可以看出,屏幕上显示的内容就是这个glView视图,这个glView是EAGLView类型的:
- // 创建一个视图对象
- 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]可以获取这个单例对象。
- // 获取CCDirector的单例对象
- CCDirector *director = [CCDirector sharedDirector];
CCDirector的主要用途:
* 运行、替换、推入和弹出场景(即场景过渡)
* 访问正在运行的场景
* 暂停、恢复、终止游戏
* 在UIKit和OpengGL之间转换坐标
* 获取窗口的尺寸
下面这句代码非常关键:
- // 关联这个视图对象到CCDirector
- [director setOpenGLView:glView];
给CCDirector设置视图后,CCDirector才知道场景(CCScene)上的内容要渲染到哪个视图上面。因此,说CCDirector是UIKit和OpenGL之间的桥梁,一点也不为过
cocos2d提供了4种类型的CCDirector,不同类型的CCDirector有不同的更新游戏状态的方式,这些更新方式会对游戏的性能、与UIKit视图的兼容性产生很大影响。
下面这句代码就是设置CCDirector的类型:
- // 设置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来设置屏幕方向
- // 设置屏幕方向
- #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.设置游戏帧率
大家都知道,游戏界面上的内容是需要经常刷新的,比如一个子弹打出去,需要经常刷新子弹的位置。刷新速度就取决于游戏帧率。
- [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.显示游戏帧率
- // 设置是否要显示FPS
- [director setDisplayFPS:YES];
设置了显示帧率后,屏幕左下角显示的数字就是游戏的帧率,当前是59.9fps。

cocos2d会隔一段时间就更新这个数值,通过修改ccConfig.h的CC_DIRECTOR_FPS_INTERVAL值可以调整刷新数值的时间间隔,默认是0.1,即1秒钟更新10次。ccConfig.h在项目的libs/cocos2d文件夹中。
- #ifndef CC_DIRECTOR_FPS_INTERVAL
- #define CC_DIRECTOR_FPS_INTERVAL (0.1f)
- #endif
6.设置游戏的第一个场景
- [[CCDirector sharedDirector] runWithScene: [HelloWorldLayer scene]];
这里调用CCDirector的runWithScene:方法来设置游戏的第一个场景,这个场景对象是通过[HelloWorldLayer scene]这个静态方法创建。
因此,需要搞清楚屏幕上显示的"Hello World"是怎么弄出来的,还得查看HelloWorldLayer这个类
7.在发出内存警告时释放资源
- - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
- [[CCDirector sharedDirector] purgeCachedData];
- }
HelloWorldLayer解读
打开HelloWorldLayer.h,可以发现HelloWorldLayer继承了CCLayer,意为图层
- @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是菜单层,用来显示一些菜单按钮、控制按钮
再查看HelloWorldLayer.m中scene方法的实现
- +(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方法
- -(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。
- CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];
接着获取屏幕的宽度和高度
- CGSize size = [[CCDirector sharedDirector] winSize];
设置标签在父节点中的位置(默认情况下,精灵的中心点会在position属性所指定的位置上)
- label.position = ccp( size.width /2 , size.height/2 );
cpp其实是个宏定义
- #define ccp(__X__,__Y__) CGPointMake(__X__,__Y__)
最后添加标签到图层中
- [self addChild: label];
就这样,"Hello World"就显示到我们的屏幕上了
分析可得,我们这个游戏里面存在着3个节点:

CCScene里面有个CCLayer,CClayer里面有个CCLabelTTF
总结
说了这么多内容,最后做个大总结:
1.要想利用cocos2d在屏幕上显示点东西,就必须使用CCDirector运行一个场景(CCScene),比如
- [[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的子类,因此,它们都是节点。一个节点又可以包含多个子节点
三、第一个cocos2d程序的代码分析的更多相关文章
- cocos2D(一)----第一个cocos2D程序
简单介绍 我们这个专题要学习的是一款iOS平台的2D游戏引擎cocos2d.严格来说叫做cocos2d-iphone,由于cocos2d有非常多个版本号.我们学习的是iphone版本号的.既然是个游戏 ...
- 一个cocos2d程序的完整人生(从环境到代码全过程)
今天我的打砖块小游戏Beta0.1终于完成了,比较开心,写一下这个程序从出生到长大的全过程把. 这是个博客集合帖,具体的操作细节我都在其它博文中有详细说明,下面会给出链接 首先,我想我还是要介绍一 ...
- Spring学习之路三——第一个Spring程序(体会IoC)
体会IoC:Spring通过一种称作控制反转(IoC)的技术促进了松耦合.当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象.你可以认为IoC与JN ...
- cocos2D(三)---- 第一cocos2d的程序代码分析
在第一讲中已经新建了第一个cocos2d程序,执行效果例如以下: 在这讲中我们来分析下里面的代码,了解cocos2d的工作原理,看看屏幕上的这个"Hello World"是怎样显示 ...
- Exp4 恶意代码分析 20154320 李超
恶意代码 概述 恶意代码是指故意编制或设置的.对网络或系统会产生威胁或潜在威胁的计算机代码.最常见的恶意代码有计算机病毒(简称病毒).特洛伊木马(简称木马).计算机蠕虫(简称蠕虫).后门.逻辑炸弹等. ...
- Win32 程序开发:创建一个应用程序窗口
一.创建一个应用程序窗口 代码如下: // 头文件 #include <windows.h> // 全局变量 WCHAR g_lpszClassName[] = L"CLASSN ...
- 第一个java程序
完成自己的第一个java程序 1.新建一个文本文档,在文本文档中编写自己第一个java程序的代码,代码如下; class hello { public static void main(String[ ...
- 使用OCLint和Sonar对iOS代码分析和质量管理
OCLint 是一个强大的静态代码分析工具,可以用来提高代码质量,查找潜在的bug,主要针对c,c++和Objective-c的静态分析. Sonar 是一个用于代码质量管理的开放平台.通过插件机制, ...
- 【C语言】03-第一个C程序代码分析
前面我们已经创建了一个C程序,接下来分析一下里面的代码. 项目结构如下: 一.代码分析 打开项目中的main.c文件(C程序的源文件拓展名为.c),可以发现它是第一个C程序中的唯一一个源文件,代码如下 ...
随机推荐
- Git-历史穿梭
图形工具:gitk gitk是最早实现的一个图形化的Git版本库浏览器软件,基于tcl/tk实现,因此gitk非常简洁,本身就是一个1万多行的tcl脚本写成的.gitk的代码已经和Git的代码放在同一 ...
- Android 时间计算工具 通用类TimeUtil
1.整体分析 1.1.源代码如下,可以直接Copy. public class TimeUtil { private static final String TAG = "TimeUtil& ...
- 110Balanced Binary Tree
问题:判断二叉树是否为平衡二叉树分析:树上的任意结点的左右子树高度差不超过1,则为平衡二叉树. 搜索递归,记录i结点的左子树高度h1和右子树高度h2,则i结点的高度为max(h1,h2 ...
- miniui IE对省略号即text-overflow:ellipsis显示不一样的问题
做miniui项目中发现,IE对文本以英文或数字结尾的是英文的省略号,以汉字结尾的就是中文的省略号.只要将字体变为统一宋体即可解决.即 .mini-grid-cell-inner { ...
- 开启虚拟机所报的错误:VMware Workstation cannot connect to the virtual machine. Make sure you have rights to run the program, access all directories the program uses, and access all directories for temporary fil
当我们开启虚拟机时出现错误: VMware Workstation cannot connect to the virtual machine. Make sure you have rights t ...
- python里面list()函数
1. list() 方法用于将元组转换为列表. 元组与列表是非常类似的,区别在于元组的元素值不能修改,元组是放在括号中,列表是放于方括号中. 2. python里面的变量不用提前声明.
- laravel5.5用户认证源码分析
目录 1. 生成相关文件和配置 2. 分析路由文件 3. 以登陆开始为例,分析auth到底是怎么工作的 3.1 分析登录文件 3.2 分析门面Auth. 1. 生成相关文件和配置 快速生成命令 php ...
- centos使用--vim配置和推荐插件使用
目录 1.vimrc的配置内容 2.Vundle使用 简介 安装vundle 配置vundle插件: 安装需要的插件 移除不需要的插件 其他常用命令 3 使用插件 3.1 NERDTree 3.2 e ...
- 《Cracking the Coding Interview》——第12章:测试——题目3
2014-04-24 23:28 题目:玩象棋游戏,你要设计一个bool型的方法来检测一个棋子能否移动到指定位置. 解法:不同的棋子有不同的移动规则,那么应该采取棋子基类实现接口,各个棋子子类来实现的 ...
- 《Cracking the Coding Interview》——第9章:递归和动态规划——题目4
2014-03-20 03:08 题目:给定一个集合,返回其幂集. 解法:DFS. 代码: // 9.4 Return all subsets of a set #include <cstdio ...







