本章要学到什么?
当你阅读文本章以后,将能创建一个基于cocos2d的Hello World,这是一件令人兴奋的事情。本章还会涉及到一些内存管理的知识,如果你是直接关了visual studio投身IOS的,那么最好翻阅相关资料好好学习一下。
我们需要准备一些什么东西?
1.需要一台运行Mac OS X 10.6 (Snow Leopard)或者更高版本操作系统的电脑,建议2G以上内存,如果你没有MAC电脑,可以考虑安装虚拟机或者黑苹果。这不是一篇讲解这些内容的文章,你可以到远景论坛自行搜索。
2.任何一种IOS设备:Iphone、iPod touch、iPad中的至少一个。这是必需的,但不是急需的,你可能要等到3个月以后才会不得不需要他们。
注册成为 iOS 开发者
如果你想把你的游戏在appStore上发售,你需要向苹果提交申请成为IOS开发者,这同样是必需的,但不急需。需要缴纳99美元的年费,这是一个问题,也不是一个问题,嘿嘿。
注册过程参看:http://developer.apple.com/iphone
Xcode和SDK
大约有2G大,相当于vistual studio或者eclipse。
就像装完VS会自动安装framework一样,Xcode安装的时候会自动安装SDK。
建议在苹果系统下从Appstore上下载最新版本的Xcode,他会自动安装。
下载和安装 cocos2d
你可以通过以下网址下载:http://www.cocos2d-iphone.org/download
下载完成以后解压之,进入解压后的目录,找到一个叫做然后进入终端工具(Terminal)。打开 Finder 窗口,在 cocos2d 文件夹中找到 install-templates.sh,将其拖拽到终端窗口。这将把 文件的路径和文件名直接添加到当前命令行,如图所示:
按下回车键,因为脚本要求 root 权限才能运行安装程序,所以终端会要求你输 入系统密码。如果没有出错的话,你将看到终端窗口会打印出几行反馈文字。 大多数文字以“...copying”开头。
HelloCocos2d!
XCode-Create a new Xcode Project ,在IOS下面会看到多出了cocos2d的选项,选中他以后右边出现了3个模板,分别是常规cocos2d项目、box2d物理引擎和chipmunk物理引擎模板。这里选择cocos2d模板,点next进入下一步,填写项目名,公司名,选择存放路径,Over。
Alt+R编译运行,模拟器启动,如下所示:
至此,cocos2d的第一个程序成功执行!
工程结构
下面我们来了解一下cocos2d的工程目录,这有助于我们把上图中的Hello Wrold改成Hello cocos2d。如下图所示:
HelloCocos2d是我们的项目名称,他包含了3个目录:
HelloCocos2d:这是我们的代码目录,其中包括:
Resource:这是资源目录,游戏中所用到的声音、图片、地图等数据都会被放在这里,可以这样说:“凡是不是代码的东东,都是放在这里的。”,这个目录名是不能更改的,至少据我现在所知,是不能更改的。
Libs:顾名思义,这里应该放的是一些库文件,其实这是cocos2d的地盘。是的,正如你看到的那样,开源的。
supporting:这里是大家的老朋友,main.m;还有一个是编译相关的文件Prefix.pch,后面会提到,其实里面很简单 :)
FrameWorks:
自己的代码和cocos2d中所用到的头文件,相当于VS中的【引用】目录
Products:
相当于VS中的bin目录,mac下的可执行程序后缀名是app。
开始分析代码:
华丽的main函数
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, @"AppDelegate");
[pool release];
return retVal;
}
main函数的返回值,参数不再赘述,和C C#完全一样。
NSAutoreleasePool类负责维护一个供当前程序使用的内存自动释放池,直到[pool release]被调用。
AppDelegate是一个全局委托,该委托定义了一系列app级别的事件,这些事件将来都会被我们用到。
预编译前缀头文件
Prefix.pch的作用是加快编译的过程,其中放置了整个工程中所有类都会用到的头文件。
这就相当于C语言中的stdio.h,c#中的System。
有了预编译前缀头文件以后,编译工程的时候首先会编译这些头文件,本工程中所有的类都可以使用他们。
但是需要注意的是如果Prefix.pch中的某个头文件发生了变化,那么所有的类都会被重新编译,这就提醒我们,在此文件中只应该放置一成不变的.h!
本工程中的Prefix文件中如下:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
这些是所有基于cocos2d的类都必须要引用的。不建议开发者修改Prefix,除非必需。
三个牛逼类
这工程中出现了三个牛逼的类,这些类被自动创建,但是我们很有必要了解一下他们,建议读者们仔细阅读其中的每一行代码,这里不再赘述:
AppDelegate类,他负责application级别的事件委托,比如:applicationDidFinishLaunching事件,该事件在application被载入完成以后触发。
RootViewController类,继承自UIViewController类,实现了View级别的事件委托,比如屏幕翻转、内存警报等,例如:didReceiveMemoryWarning,改事件在系统内存低于预警值以后会被触发。
HelloWorldLayer类,此类继承自cocos2d的CCLayer,表示【层】,关于层的概念,后续会有专门的讲解,他和Photoshop中的层有些相像,现在你可以暂时这样理解他。
层的init事件
在HelloWroldLayer层中,有一个事件至关重要:
-(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;
}
这个事件负责初始化自己,相当于构造函数。
从[super init]可以看出,本方法重写了父类的init。核心代码出现在if语句中,这是一种【防守型】的的编码方式,因为super的init有可能返回一个nil。
第一行代码创建了一个CCLabelTTF对象(此类远非我们相像的那么简单,他不只是一个Label),注意,所有的cocos2d类都以CC开头。
第二行代码获得了当前设备的window尺寸到size中。
第三行代码将CCLabelTTF对象的位置设置到屏幕的中心点。
第四行代码将CCLabelTTF对象作为一个node(节点)放入当前层的节点集合。
显而易见,我们可以通过修改第一行代码来让模拟器用不一样的字体显示不同大小的自定义文本。试一下,记住屏幕右上角的时间,等你的游戏在Appstore上收到第一个0.99的时候回想一下这个时间,是一种不错的感觉:-)
一些改变
以下三个设置你可能会感兴趣:
1. 在RootViewController.m中,有如下代码让游戏以横屏模式启动:
如果你想以竖屏的模式启动的话,改成如下的代码即可:#elif GAME_AUTOROTATION == kGameAutorotationUIViewController return ( UIInterfaceOrientationIsLandscape( interfaceOrientation ) );
#elif GAME_AUTOROTATION == kGameAutorotationUIViewController // return ( UIInterfaceOrientationIsLandscape( interfaceOrientation ) );
2.在AppDelegate.m中有如下代码:
作用是设置系统动画频率(FPS)为60.[director setAnimationInterval:1.0/60];
3.在AppDelegate.m中有如下代码:
[director setDisplayFPS:YES];
作用是在屏幕的左下角显示当前的FPS,这个主要用来对游戏性能进行测试,发布游戏以前一般都会去掉改代码,或者作为选项让玩家选择是否显示。
关于帧率
保持游戏运行在一个高帧率上是每一个开发人员的责任,但是有时候我们为了达到这个目的,会降低帧率。
这听起来有点莫名奇妙,事实上这是一个文字游戏,“高帧率”真的是实际运行时的帧率,这个帧率除了受代码本身影响以外,还受用户的硬件影响。
“降低帧率”中的帧率,指的是动画时钟频率,对于复杂的游戏而言,他们的帧率可能会在20-60帧之间上下浮动的很厉害。
设置一个低一点的帧率对这样的游戏是有好处的。
一个稳定的可以接受的帧率比一个晃动的忽高忽低的帧率要好得多,当然该值的设定需要一定的技巧,我们可以通过编程的手段来动态的设定之。一个五子棋的程序很明显不需要60的帧率,我们应该对用户的电池负责。
但是有一点需要注意,IOS是不支持超过60的帧率的。一个超过60的帧率可能带来灾难性的后果。
人的感觉,是很复杂的东西,有时候并非越好便越舒服。
cocos2d 的内存管理
这是一个重要的东西,但是本章只讲解基本的内存管理和自动释放信息(autorelease message)。
通常情况下,我们通过调用alloc方法在Objective-C中生成一个对象,用alloc方法生成的对象必需在你不需要他的时候被释放掉。看这个例子:
// 生成一个NSObject实例 NSObject* myObject = [[NSObject alloc] init]; // 用myObject做些事情... // 释放myObject占用的内存 // 如果你不释放的话,此对象占用的内存将泄漏,并且那部份内存将永远都不会被释放 [myObject release];
这是一个典型的生成-初始化-释放的内存管理流程。再看以下的例子:
//生成一个NSObject实例 NSObject* myObject = [[[NSObject alloc] init] autorelease]; //用myObject做些事情... // 不需要调用释放,实际上你不应该在这里发送释放信息,因为会让程序崩溃。
这是一个自动释放的例子,事实上这相当于c#中的using(object){}语句,将资源托管给系统是一个很明智的选择,这能帮我们避免很多错误。但是需要注意的是,对象一旦被托管,你就不要再去调用[myObject release]方法了,这会让程序因为一个鼎鼎大名的错误而崩溃:EXC_BAD_ACCESS,你以后对他会非常熟悉。
很明显,我们建议采用第二种方法。
Retain
需要注意的是,如果我们简单把对象当做自己的类成员变量的话,可能会引发一个问题:如果你想在下一帧中访问自动释放的对象的话,你必须Retain(保留)它。这是Objective-C特有的。
这听起来很麻烦,事实上我们有好的解决方案:
-(void) init { myNode = [CCNode node]; myNode.tag = 123; // 将此实例作为子节点添加给self(假设self继承自CCNode) [self addChild:myNode]; } -(void) update:(ccTime)delta { // 之后我们就可以在这里访问和使用myNode对象了。 CCNode* myNode = [self getChildByTag:123]; //用myNode做些事情 ... }
cocos2d初学者可能对update这个方法比较陌生,你现在可以简单的将他理解成一个计时器,类似于c#中timer_ticker方法,他会被反复定时执行以生成帧。myNode通过addChild方法被添加到了self的Nodes集合中,这样当新的一帧开始的时候,SDK会给该集合中的每一帧发送Retain消息以保留他在下一帧可以被访问到。
当该对象被从集合中删除的时候,SDK会自动向他发送release消息释放之。
很明显,这也是我们推荐的方法。
有的人会告诉你自动释放不好,或者速度很慢,你不该被他们说服,他们的说法是没有依据的。
注:苹果开发者文档建议减少使用自动释放对象。但是大多数 cocos2d 对象都是自动释放对象。这样做的好处是让内存管理变的简单。
如果你用传统的 alloc/init 和 release 来管理每一个 cocos2d 对象的话,那将是自找麻烦,而且得不到任何好处。这也不是说你永远都不会用到 alloc/init;有时候它们可能是必须的。但是对于 cocos2d 对象来说,你应该依赖于静态自动释放初始化方法(static autorelease initializers)。
自动释放对象只有一个缺点,那就是直到游戏进入下一帧,这些对象将一直占用内存。这就意味着,如果你在每一帧都生成许多很快就要被丢弃的自动释放对象,你可能会浪费很多内存。不过这样的情况很少发生。
好了,今天到此结束,回见。