cocos2d是一个免费开源的ios游戏开发引擎,而且彻底采用object-c进行编写,这对于已经用惯object-c进行ios应用开发的童鞋来讲很是容易上手。这些也是我推荐使用cocos2d进行ios游戏开发的缘由,固然从字面上已经能够开出来,这是一款专一于“2d”游戏的开发引擎,您也能够本身编写3d渲染代码或者使用第三方的解决方案,在cocos2d里加载显示3d模型。此外对于3d,也能够选用cocos3d来进行游戏开发。好了,废话很少说,仍是先专一在cocos2d。
下面我会主要经过最近正在着手开发的一款小游戏来讲一下cocos2d:(关于如何安装cocos2d,网上有大把的资料可查,这里就不详细说了,安装推荐下这篇http://hi.baidu.com/784500515/item/402e6d100d7b8df6dceecaf1,我亲身实践过是靠谱的。此外网上还有不少关于cocos2d的教程也不错的,能够去看看,像Learn iPhone and iPad cocos2d game development和知易cocos2d-iphone开发教程。)
大概介绍下这款游戏:经过iphone的三轴陀螺仪来操纵游戏主角,避开沿路的炸弹,并获取随机散落的金币来得分,我只说整个游戏的骨架。固然你也能够有更多的拓展,好比让沿路的炸弹有多种随机类型,有的炸弹吃到会变让游戏主角变大,有的炸弹会把主角沿前进的方向炸飞使其加速等等。最寻常的炸弹就是仅仅炸到主角使其生命值减小。此外也能够对主角进行选择,好比让不一样的主角类型拥有不一样的横向移动速度和不一样的满格生命值。。。
游戏主界面的截图主要包括:得分(左上角),主角生命值(右上角),游戏速度控制按钮(右上角),炸弹和金币,游戏主角(屏幕底部)以及一直滚动的背景图
—————————————下面是游戏主界面截图—————————————
首先,咱们须要先建立一个cocos2d的工程,在xcode的新建工程界面选择cocos2d分类下的cocos2d Application,键入工程名称(我把它命名为DropBomb)后,点击“save”。若是你已经安装成功cocos2d,xcode会帮助建立一个基本的cocos2d Application工程,里面会包含一些基本的文件和代码,包括“Helloworld”。咱们须要对这个基本的工程进行一些修改,以使其符合这个游戏的须要。
咱们先分别添加两个继承CCSprite的类myBomb和myCoin,这两个类用于控制炸弹和金币,注意添加的时候同时勾选“Also create XXX.h”,这样添加完成后,xcode会自动将该类的.m文件和.h头文件都添加到当前工程下。而后删除xcode帮咱们自动生成的Helloworld.m和相应的.h头文件,添加游戏的主场景,新建一个继承CCLayer的类GameScene。此外还须要添加一个结束呈现得分以及从新开始的界面EndScene(继承自CCLayer)以及一个控制跳转和载入过程的界面LoadingScene(继承自CCScene)。另外我对系统提供的CCAnimation类进行了扩展,使其能够更方便地用于CCSprite播放帧动画,这个类我命名为Helper。
好了,这样工程的基本文件结构就完成了,整个工程文件的结构以下(其余一些不是很重要的图片资源文件不是很影响整个工程,因此就没有截全)
—————————————下面是整个工程文件截图—————————————
因为咱们删除了系统给添加的“Helloworld”,那么就须要来修改启动页面事后进入到的游戏主场景界面。在“Groups & Files”中进入DropBombAppDelegate.m,对applicationDidFinishLaunching函数中的runWithScene进行修改,将游戏主场景设为GameScene:
[[CCDirector sharedDirector] runWithScene: [GameScene scene]];
另外,cocos2d默认的屏幕显示方向是横向的,这里须要纵向的屏幕来进行游戏,找到shouldAutorotateToInterfaceOrientation函数,对GAME_AUTOROTATION == kGameAutorotationUIViewController条件下的return值进行修改,将UIInterfaceOrientationIsLandscape改成UIInterfaceOrientationIsPortrait
#elif GAME_AUTOROTATION == kGameAutorotationUIViewController
return ( UIInterfaceOrientationIsPortrait( interfaceOrientation ) );
好了,暂时没必要理会DropBombAppDelegate.m中的其余代码,咱们还不用动到他们。此时运行项目,在启动页面事后会出现空白页面,那是由于咱们尚未给GameScene主场景中添加内容。没关系,如今立刻切换到GameScene中。
1. GameScene类
咱们须要在GameScene中以静态方法定义场景节点和层节点,在GameScene.h中添加一个静态方法:
+(id)scene;
而后在GameScene.m中实现这个静态方法:
+(id)scene {
CCScene *scene = [CCScene node];
CCLayer *layer = [GameScene node];
[scene addChild:layer];
return scene;
}
此时,咱们的GameScene中就包含了一个CCScene的场景节点和GameScene的层节点(不要忘了GameScene是继承自CCLayer的)。下面就经过实现GameScene的init方法来为主场景添加点内容吧:咱们在GameScene的init方法中对游戏的时间、得分和生命值进行了初始化以保证每次切换到主场景时,这些数值都是新的;而后咱们对游戏主角、炸弹、金币、游戏背景、加速按钮、得分显示、生命值显示等进行了初始化(注意此处对游戏主角的初始化用到了咱们对系统的CCAnimation扩展的方法,须要实现Helper类后才可使用,稍后再来看Helper的实现方法);此外咱们还初始化了炸弹的初始下落速度,并设定了一个循环播放的背景音乐。因为须要对主场景中的变量传递到其余的层中,咱们还在init方法中将self赋值给了GameScene的一个静态对象(cocos2d中将这种方法称为“伪单例”)。详细的GameScene的init方法以下,对代码我进行详细的标注,能够进行对照:
-(id)init {
if (self == [super init]) {
NSLog(@"%@,%@",NSStringFromSelector(_cmd),self);
totalTime = 0.0f;
score = 0;
life = 3;
self.isAccelerometerEnabled = YES; //启用加速计
player = [CCSprite spriteWithFile:@"bomber0.png"]; //用bomber图片初始化player精灵
[self addChild:player z:2 tag:1]; //将player精灵添加到场景中
CGSize screenSize = [[CCDirector sharedDirector] winSize]; //获取屏幕大小
float imageHeight = [player texture].contentSize.height; //获取精灵中图片内容的大小
player.position = CGPointMake(screenSize.width*0.5f, imageHeight*0.5f); //初始化player的位置
CCAnimation *anim = [CCAnimation animationWithFile:@"bomber" frameCount:2 delay:0.5f]; //运用给CCAnimation扩展的方法来生成一个动画对象anim
CCAnimate *animate = [CCAnimate actionWithAnimation:anim];
CCRepeatForever *repeate = [CCRepeatForever actionWithAction:animate]; //生成一个重复动做的repeate对象
[player runAction:repeate]; //让player运行这个重复动做
//初始化bomb
[self initBombs];
//初始化coin
[self initCoins];
[self scheduleUpdate]; //用于检测加速度,以随时改变player的位置
//设置一个不断向前滚动的背景
CCSprite *background = [CCSprite spriteWithFile:@"background.png"];
background.position = ccp(0,screenSize.height*3);
background.anchorPoint = ccp(0,1);
[self addChild:background z:-1 tag:2];
CCMoveTo *moveTo = [CCMoveTo actionWithDuration:8 position:CGPointMake(0, screenSize.height)];
CCCallFunc *func = [CCCallFunc actionWithTarget:self selector:@selector(onCallFunc)];
CCSequence *bgSequence = [CCSequence actions:moveTo,func,nil];
CCRepeatForever *bgRepeat = [CCRepeatForever actionWithAction:bgSequence];
[background runAction:bgRepeat];
//设置加速按钮
CCSprite *normal1 = [CCSprite spriteWithFile:@"speed1.png"];
CCSprite *normalSelected = [CCSprite spriteWithFile:@"speed1.png"];
normalSelected.color = ccGRAY;
CCMenuItemSprite *itemNormal = [CCMenuItemSprite itemFromNormalSprite:normal1 selectedSprite:normalSelected];
CCSprite *speed1 = [CCSprite spriteWithFile:@"speed2.png"];
CCSprite *speedSelected = [CCSprite spriteWithFile:@"speed2.png"];
speedSelected.color = ccGRAY;
CCMenuItemSprite *itemSpeed = [CCMenuItemSprite itemFromNormalSprite:speed1 selectedSprite:speedSelected];
CCMenuItemToggle *speedToggle = [CCMenuItemToggle itemWithTarget:self selector:@selector(speedTouched) items:itemNormal,itemSpeed,nil];
CCMenu *menu = [CCMenu menuWithItems:speedToggle,nil];
menu.position = CGPointMake(screenSize.width - [speed1 texture].contentSize.width * 0.5f - 5, screenSize.height - [speed1 texture].contentSize.height * 0.5f - 5);
[self addChild:menu z:102];
//初始化scoreLabel
scoreLabel = [CCLabelTTF labelWithString:@"SCORE:0" fontName:@"Marker Felt" fontSize:23];
scoreLabel.color = ccGREEN;
//scoreLabel.opacity = 160;
scoreLabel.position = CGPointMake(10, screenSize.height - 10);
scoreLabel.anchorPoint = CGPointMake(0, 1.0f);
[self addChild:scoreLabel z:100];
//初始化lifeLabel
lifeLabel = [CCLabelTTF labelWithString:@"LIVES:3" fontName:@"Marker Felt" fontSize:23];
//lifeLabel.color = ccGREEN;
lifeLabel.position = CGPointMake(screenSize.width * 0.6f, screenSize.height - 10);
lifeLabel.anchorPoint = CGPointMake(0, 1.0f);
[self addChild:lifeLabel z:101];
life = 3;
//myBomb *test = [[myBomb alloc] init];
//[self addChild:test z:112 tag:111];
//test.position = ccp(screenSize.width/2,screenSize.height/2);
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"star.mp3" loop:YES]; //播放背景音乐
dropSpeed = 1.0f; //设置默认下落速度为1倍速
sharedAllInMe = self; //将self赋值给共享本层的对象
}
return self;
}
这里的sharedAllInMe是一个指向当前GameScene的静态对象,对于其的定义在GameScene.m中,以下:
static GameScene *sharedAllInMe; //生成一个当前层的静态对象
固然为了在其余的类中能够访问这个静态对象,咱们还须要设置一个返回它的静态方法,在GameScene.h中定义:
+(GameScene *)getShared; //生成一个当前层的静态访问方法
并在GameScene.m中实现这个方法:
+(GameScene *)getShared { //返回当前层
return sharedAllInMe;
}
为了防止在其余的类中score被修改,咱们须要在GameScene.h中将其定义为只读:
@property (assign,readonly) int score;
咱们在设置背景不断向前滚动时采用的方法是建立一个动做序列bgSequence,在这个动做序列的末尾调用函数onCallFunc,将背景的位置从新挪到第一屏的位置(须要保证这个背景的第一屏图像与最后一屏图像一致)。onCallFunc函数的详细以下:
//当背景图片滚动到头时,将其从新放回初始位置
-(void)onCallFunc {
CCNode *node = [self getChildByTag:2]; //获取tag为2的对象做为一个CCNode对象,就是背景图
if ([node isKindOfClass:[CCSprite class]]) {
CCSprite *back1 = (CCSprite *)node;
CGSize size = [[CCDirector sharedDirector] winSize];
back1.position = ccp(0,size.height*3); //将该图片的位置重置为初始位置
}
}
在init中,咱们设定了点击加速按钮将会调用的方法speedTouched,对于这个方法咱们做一个简单的实现,假设咱们有一个二级的速度控制(三级或者更多级别仅须要增长判断):
//点击加速按钮改变下落速度
-(void)speedTouched {
if (dropSpeed == 1.0f) {
dropSpeed = 0.5f;
}
else {
dropSpeed = 1.0f;
}
}
init中还用[self scheduleUpdate]预约了一个每秒调用的函数,以随时经过加速计修正的速度来调整主角的位置,并进行碰撞测试。这个函数的实现以下:
-(void)update:(ccTime)delta {
//获取player的当前位置
CGPoint pos = player.position;
//将player的当前位置根据目前方向的速度增长
pos.x += playerVelocity.x;
//获取player可移动的边界
CGSize screenSize = [[CCDirector sharedDirector] winSize];
float imageWidthHelv = [player texture].contentSize.width * 0.5f;
float leftBorderLimit = imageWidthHelv;
float rightBorderLimit = screenSize.width - imageWidthHelv;
//判断player的当前位置是否超过了边界,并进行相应处理
if (pos.x < leftBorderLimit) {
pos.x = leftBorderLimit;
playerVelocity = CGPointZero;
}
else if (pos.x > rightBorderLimit) {
pos.x = rightBorderLimit;
playerVelocity = CGPointZero;
}
//将处理后的位置信息赋值到player上以改变其位置
player.position = pos;
[self checkForCollision]; //进行bomb和bomber或者coin和bomber的碰撞测试
}
对于碰撞测试的实现函数checkForCollision稍后再做说明,此外这里须要用到三轴陀螺仪来返回x轴方向上的速度,相应的实现以下:
//利用加速计改变player的位置
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
float deceleration = 0.3f; //控制减速的速率,值越小能够越快地改变方向
float sensitivity = 8.0f; //控制对加速计输入的敏感度,值越大对加速计输入越敏感
float maxVelocity = 100; //控制最大的速度
playerVelocity.x = playerVelocity.x * deceleration + acceleration.x * sensitivity; //基于当前加速计的加速度调整速度
if (playerVelocity.x > maxVelocity) { //控制player的速度在最大速度以内
playerVelocity.x = maxVelocity;
}
else if (playerVelocity.x < -maxVelocity) {
playerVelocity.x = -maxVelocity;
}
}
其中playerVelocity是在GameScene.h中定义的一个CGPoint变量,咱们在此仅对其进行改变以影响主角的位置。
下面来看在GameScene中对炸弹的控制,首先是对炸弹进行初始化,咱们要设定炸弹的初始移动速度,将生成的炸弹放入一个炸弹数组以便于后续的控制,而后将炸弹放置到场景中。initBombs的详细实现以下:
//初始化bombs
-(void)initBombs {
bombMoveDuration = 1.8f;
CGSize screenSize = [[CCDirector sharedDirector] winSize];
CCSprite *tempBomb = [CCSprite spriteWithFile:@"bomb0.png"];
//NSLog(@"bombs------%@",tempBomb);
float imageWidth = [tempBomb texture].contentSize.width; //获取bomb图片的宽度
int numBombs = screenSize.width / imageWidth; //计算在当前屏幕宽度下,可容纳多少个bomb
bombs = [[CCArray alloc] initWithCapacity:numBombs]; //用可容纳的bomb数量来初始化bombs数组
for (int i = 0; i < numBombs; i++) { //在当前场景中添加这些bomb,并将其放入bombs数组
myBomb *bomb1 = [[[myBomb alloc] init] autorelease];
//NSLog(@"bombs------%@",bomb1);
[self addChild:bomb1 z:1 tag:3];
//bomb1.position = CGPointMake([tempBomb texture].contentSize.width*i + [tempBomb texture].contentSize.width*0.5f, screenSize.height + [tempBomb texture].contentSize.height);
[bombs addObject:bomb1];
}
[self resetBombs];
}
resetBombs用于将炸弹移到屏幕外的初始位置,并中止其动做:
-(void)resetBombs { //重设bomb的位置,将bomb的位置所有设置在屏幕顶部之外
CGSize screenSize = [[CCDirector sharedDirector] winSize];
myBomb *tmpBomb = [bombs lastObject]; //生成临时bomb对象
CGSize size = tmpBomb.bombSize;
int numBombs = [bombs count];
for (int i = 0; i < numBombs; i++) {
myBomb *bomb1 = [bombs objectAtIndex:i]; //获取bombs数组中的每一个bomb对象
bomb1.position = CGPointMake(size.width*i + size.width*0.5f, screenSize.height + size.height); //将各个bomb对象的位置设置到屏幕顶部之外
//NSLog(@"bombs------%f,%f",bomb1.position.x,bomb1.position.y);
[bomb1 stopAllActions];
}
//将预定的方法取消掉
[self unschedule:@selector(bombUpdates:)];
//用指定的时间间隔调用bomb的更新方法
[self schedule:@selector(bombUpdates:) interval:0.7f];
}
每次resetBombs以后,都会预定一个每隔0.7s执行一次的函数bombUpdates:,并将以前的预定取消。bombUpdates:的做用是在炸弹数组中随机寻找一个没有开始移动的炸弹,并让它开始向屏幕底部移动:
-(void)bombUpdates:(ccTime)delta {
//寻找一只不动的bomb,并让其执行动做序列
for (int i = 0; i < 10; i++) {
//获取bombs数组中的一个bomb对象
int randomBombIndex = CCRANDOM_0_1()*[bombs count];
myBomb *bomb1 = [bombs objectAtIndex:randomBombIndex];
//检测该bomb对象是否没有执行任何动做,若是是则让其执行动做序列
if ([bomb1 numberOfRunningActions] == 0) {
[self runBombMoveSequence:bomb1];
//NSLog(@"update-----%@",bomb1);
break; //保证一次仅有一个bomb执行动做
}
}
}
控制炸弹的移动动做则经过runBombMoveSequence:函数来实现,该函数会在判断炸弹没有与主角碰撞到以后(判断经过isCollision标志位来进行),将炸弹向屏幕底部以设定的bombMoveDuration进行移动:
-(void)runBombMoveSequence:(myBomb *)bomb { //控制bomb的执行动做
if (bomb.isCollision == NO) {
bombMoveDuration = 1.8f * dropSpeed;
//设置bomb的移动终点
CGPoint belowScreenPosition = CGPointMake(bomb.position.x, -bomb.bombSize.height);
//bomb移动动做
CCMoveTo *move = [CCMoveTo actionWithDuration:bombMoveDuration position:belowScreenPosition];
//bomb移动到终点,即屏幕底部之外时,将其从新放回屏幕顶部之外的初始位置
CCCallFuncN *call = [CCCallFuncN actionWithTarget:self selector:@selector(onCallFuncN:)];
//生成动做序列并执行动做
CCSequence *seq = [CCSequence actions:move,call,nil];
[bomb runAction:seq];
//NSLog(@"update-----%f,%f",bomb.position.x,bomb.position.y);
}
}
为了防止炸弹移动到屏幕底部之外仍然向下移动,在炸弹移动的动做序列最后调用函数
onCallFuncN:将炸弹从新放回到屏幕顶部的初始位置:
-(void)onCallFuncN:(id)sender {
if ([sender isKindOfClass:[myBomb class]]) { //检测sender是不是myBomb类,若是是则将其位置重置到屏幕顶部之外的初始位置
myBomb *bomb1 = (myBomb *)sender;
//NSLog(@"update-----%f,%f",bomb1.position.x,bomb1.position.y);
CGPoint pos = bomb1.position;
CGSize screenSize = [[CCDirector sharedDirector] winSize];
pos.y = screenSize.height + bomb1.bombSize.height;
bomb1.position = pos;
}
else {
NSLog(@"sender is not a CCSprite");
}
}
到这里,GameScene中对于炸弹的控制就差很少了。而对于金币的控制,原理与炸弹的控制基本一致,就不详细说了,具体的代码以下:
//初始化coins
-(void)initCoins {
coinMoveDuration = 1.6f; //设置coin的移动速度
CGSize screenSize = [[CCDirector sharedDirector] winSize];
CCSprite *tempCoin = [CCSprite spriteWithFile:@"coin0.png"];
//NSLog(@"coins------%@",tempCoin);
float imageWidth = [tempCoin texture].contentSize.width; //获取coin图片的宽度
int numCoins = screenSize.width / imageWidth; //计算在当前屏幕宽度下,可容纳多少个coin
coins = [[CCArray alloc] initWithCapacity:numCoins]; //用可容纳的coin数量来初始化coins数组
for (int i = 0; i < numCoins; i++) { //在当前场景中添加这些coin,并将其放入coins数组
myCoin *coin1 = [[[myCoin alloc] init] autorelease];
//NSLog(@"coins------%@",coin1);
[self addChild:coin1 z:0 tag:4];
//coin1.position = CGPointMake([tempCoin texture].contentSize.width*i + [tempCoin texture].contentSize.width*0.5f, screenSize.height + [tempCoin texture].contentSize.height);
[coins addObject:coin1];
}
[self resetCoins];
}
-(void)resetCoins { //重设coin的位置,将coin的位置所有设置在屏幕顶部之外
CGSize screenSize = [[CCDirector sharedDirector] winSize];
myCoin *tmpCoin = [coins lastObject]; //生成临时coin对象
CGSize size = tmpCoin.coinSize;
int numCoins = [coins count];
for (int i = 0; i < numCoins; i++) {
myCoin *coin1 = [coins objectAtIndex:i]; //获取coins数组中的每一个coin对象
coin1.position = CGPointMake(size.width*i + size.width*0.5f, screenSize.height + size.height); //将各个coin对象的位置设置到屏幕顶部之外
//NSLog(@"coins------%f,%f",coin1.position.x,coin1.position.y);
[coin1 stopAllActions];
}
//将预定的方法取消掉
[self unschedule:@selector(coinUpdates:)];
//用指定的时间间隔调用coin的更新方法
[self schedule:@selector(coinUpdates:) interval:0.7f];
}
-(void)coinUpdates:(ccTime)delta {
//寻找一只不动的coin,并让其执行动做序列
for (int i = 0; i < 10; i++) {
//获取coins数组中的一个coin对象
int randomCoinIndex = CCRANDOM_0_1()*[coins count];
myCoin *coin1 = [coins objectAtIndex:randomCoinIndex];
//检测该coin对象是否没有执行任何动做,若是是则让其执行动做序列
if ([coin1 numberOfRunningActions] == 0) {
[self runCoinMoveSequence:coin1];
//NSLog(@"update-----%@",coin1);
break; //保证一次仅有一个coin执行动做
}
}
}
-(void)runCoinMoveSequence:(myCoin *)coin { //控制coin的执行动做
if (coin.isGet == NO) {
coinMoveDuration = 1.6f * dropSpeed;
//设置coin的移动终点
CGPoint belowScreenPosition = CGPointMake(coin.position.x, -coin.coinSize.height);
//coin移动动做
CCMoveTo *move = [CCMoveTo actionWithDuration:coinMoveDuration position:belowScreenPosition];
//coin移动到终点,即屏幕底部之外时,将其从新放回屏幕顶部之外的初始位置
CCCallFuncN *call = [CCCallFuncN actionWithTarget:self selector:@selector(coinCallFuncN:)];
//生成动做序列并执行动做
CCSequence *seq = [CCSequence actions:move,call,nil];
[coin runAction:seq];
//NSLog(@"update-----%f,%f",coin.position.x,coin.position.y);
}
}
-(void)coinCallFuncN:(id)sender {
if ([sender isKindOfClass:[myCoin class]]) { //检测sender是不是myCoin类,若是是则将其位置重置到屏幕顶部之外的初始位置
myCoin *coin1 = (myCoin *)sender;
//NSLog(@"update-----%f,%f",coin1.position.x,coin1.position.y);
CGPoint pos = coin1.position;
CGSize screenSize = [[CCDirector sharedDirector] winSize];
pos.y = screenSize.height + coin1.coinSize.height;
coin1.position = pos;
}
else {
NSLog(@"sender is not a CCSprite");
}
}
接下来,主要来看一下GameScene中对主角和炸弹以及金币的碰撞测试部分。首先要获取主角、炸弹以及金币的尺寸,这里采用简单的径向测试,所以仅取图片宽度。而后经过图片宽度,计算出主角和炸弹、主角和金币的最大碰撞距离。若是检测二者间的距离小于最大碰撞距离,即认为发生了碰撞。与炸弹发生碰撞后,生命值减小1,播放炸弹爆炸的音效并呈现爆炸效果;与金币发生碰撞后,则播放吃金币的音效并呈现得分+1的效果。在碰撞后均须要将碰撞标志位(isCollision和isGet)设为已碰撞。具体以下:
//碰撞测试
-(void)checkForCollision {
//获取player和bomb、coin的半径
float playerImageSize = [player texture].contentSize.width;
myBomb *tmpBomb = [bombs lastObject];
float bombImageSize = tmpBomb.bombSize.width;
myCoin *tmpCoin = [coins lastObject];
float coinImageSize = tmpCoin.coinSize.width;
float playerImageRadius = playerImageSize * 0.4f;
float bombImageRadius = bombImageSize * 0.4f;
float coinImageRadius = coinImageSize * 0.4f;
float maxCollisionDistance = playerImageRadius + bombImageRadius; //这里的最大碰撞距离和图片形状基本一致
float maxCoinGetDistance = playerImageRadius + coinImageRadius; //这里获取coin和bomber的最大碰撞距离
int numBombs = [bombs count];
int numCoins = [coins count];
for (int i = 0; i < numBombs; i++) {
myBomb *bomb1 = [bombs objectAtIndex:i];
if ([bomb1 numberOfRunningActions] == 0) { //若是此bomb未移动,咱们就跳过对其的碰撞测试
continue;
}
float actualDistance = ccpDistance(player.position, bomb1.position); //获得bomb和bomber间的距离
if (actualDistance < maxCollisionDistance && bomb1.isCollision == NO) { //检查是否已碰撞,由于重置这个bomb的位置在延迟0.1s后的函数里进行,而这段时间里碰撞测试函数是一直在执行的,为防止对同一个bomb重复检测碰撞,增长了isCollision的标志位
[bomb1 stopAllActions]; //先中止碰撞的bomb的向下掉落动做
[bomb1 explosionEffect]; //让该bomb呈现爆炸的效果
[[SimpleAudioEngine sharedEngine] playEffect:@"bomb3.wav"];
if (life > 0) {
life--;
[lifeLabel setString:[NSString stringWithFormat:@"LIVES:%i",life]];
}
//NSLog(@"%i",life);
[self schedule:@selector(collisionReset) interval:0.1f]; //延迟执行碰撞后的重置操做
}
//NSLog(@"%@",bomb1.isCollision?@"yes":@"no");
}
for (int i = 0; i < numCoins; i++) {
myCoin *coin1 = [coins objectAtIndex:i];
if ([coin1 numberOfRunningActions] == 0) { //若是此coin未移动,咱们就跳过对其的碰撞测试
continue;
}
float actualCoinBomberDistance = ccpDistance(player.position, coin1.position); //获得coin和bomber间的距离
if (actualCoinBomberDistance < maxCoinGetDistance && coin1.isGet == NO) { //检查是否已碰撞,由于重置这个coin的位置在延迟0.5s后的函数里进行,而这段时间里碰撞测试函数是一直在执行的,为防止对同一个coin重复检测碰撞,增长了isGet的标志位
[coin1 stopAllActions]; //先中止碰撞的coin的向下掉落动做
[coin1 getEffect]; //让该coin呈现获取的效果
[[SimpleAudioEngine sharedEngine] playEffect:@"coinsound.mp3"];
score++;
[scoreLabel setString:[NSString stringWithFormat:@"SCORE:%i",score]];
//NSLog(@"%i",life);
[self schedule:@selector(getCoinReset) interval:0.5f]; //延迟执行碰撞后的重置操做
}
//NSLog(@"%@",coin1.isGet?@"yes":@"no");
}
}
发生碰撞后,须要对炸弹和金币进行设置,包括位置重置、动做效果重置以及碰撞标志位的设置,都在相应的设置函数里进行(collisionReset和getCoinReset)。collisionReset主要用于将已碰撞的炸弹放回屏幕顶部,移去爆炸效果,重置已碰撞标志位,并在生命值为0时结束游戏跳转到得分呈现和从新开始界面;getCoinReset主要用于将已吃到的金币+1效果移去,增长得分,重置金币位置以及已碰撞标志。具体以下:
-(void)collisionReset {
//重置bomb位置
for (int i = 0; i < [bombs count]; i++) {
myBomb *bomb1 = [bombs objectAtIndex:i];
//检测该bomb对象是否没有执行任何动做,若是是则让其回到屏幕顶部之外
if ([bomb1 numberOfRunningActions] == 0) {
CGPoint pos = bomb1.position;
CGSize screenSize = [[CCDirector sharedDirector] winSize];
pos.y = screenSize.height + bomb1.bombSize.height;
bomb1.position = pos;
[bomb1 removeExplosion];
}
}
if (life <= 0) {
[[SimpleAudioEngine sharedEngine] stopBackgroundMusic];
[[CCDirector sharedDirector] replaceScene:[CCTransitionFadeTR transitionWithDuration:1.0f scene:[LoadingScene sceneWithTargetScene:TargetSceneSecondScene]]]; //调用载入页面
}
[self unschedule:_cmd];
}
-(void)getCoinReset { //获取金币后重置coin
for (int i = 0; i < [coins count]; i++) {
myCoin *coin1 = [coins objectAtIndex:i];
//检测该coin对象是否没有执行任何动做,若是是则让其回到屏幕顶部之外
if ([coin1 numberOfRunningActions] == 0) {
CGPoint pos = coin1.position;
CGSize screenSize = [[CCDirector sharedDirector] winSize];
pos.y = screenSize.height + coin1.coinSize.height;
coin1.position = pos;
[coin1 removeEffect];
}
}
[self unschedule:_cmd];
}
在GameScene的最后,实现dealloc,将非自动释放的对象释放:
-(void)dealloc {
NSLog(@"%@,%@",NSStringFromSelector(_cmd),self);
[bombs release];
bombs = nil;
[coins release];
coins = nil;
sharedAllInMe = nil;
[super dealloc];
}
在GameScene.m中用到的一些函数和变量须要在GameScene.h中声明,GameScene.h具体以下:
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "SimpleAudioEngine.h"
#import "LoadingScene.h"
#import "Helper.h"
#import "myBomb.h"
#import "myCoin.h"
@interface GameScene : CCLayer {
CCSprite *player;
CGPoint playerVelocity;
CCArray *bombs;
float bombMoveDuration;
CCArray *coins;
float coinMoveDuration;
CCLabelTTF *scoreLabel;
float totalTime;
int score;
CCLabelTTF *lifeLabel;
int life;
float dropSpeed;
}
@property (assign,readonly) int score;
+(id)scene;
+(GameScene *)getShared; //生成一个当前层的静态访问方法
-(void)onCallFunc; //背景图移动到头后将其重置
-(void)initBombs;
-(void)resetBombs;
-(void)bombUpdates:(ccTime)delta;
-(void)runBombMoveSequence:(myBomb *)bomb;
-(void)onCallFuncN:(id)sender;
-(void)initCoins;
-(void)resetCoins;
-(void)coinUpdates:(ccTime)delta;
-(void)runCoinMoveSequence:(myCoin *)coin;
-(void)coinCallFuncN:(id)sender;
-(void)checkForCollision;
-(void)collisionReset;
-(void)getCoinReset;
-(void)speedTouched;
@end
到这里游戏的主场景GameScene就基本上完成了。接下来,须要实如今GameScene中用到的几个类:myBomb、myCoin、Helper以及两个场景LoadingScene和EndScene。
2. myBomb类
myBomb类中主要实现了炸弹的初始化、爆炸动做以及移除爆炸动做。爆炸动做采用了cocos2d的粒子效果来实现,关于粒子效果网上有不少资料说明,另外也有第三方提供的粒子效果生成器可直接用,这里就不展开了。炸弹的初始化也用到了咱们对CCAnimation的扩展方法来播放炸弹在飞行下落过程当中的动做。具体实现以下:
-(id)init {
if (self == [super init]) {
bomb = [CCSprite spriteWithFile:@"bomb0.png"]; //用bomber图片初始化player精灵
bombSize = [bomb texture].contentSize;
isCollision = NO;
[self addChild:bomb z:0 tag:1]; //将player精灵添加到场景中
bomb.position = CGPointMake(0,0); //初始化player的位置
CCAnimation *anim = [CCAnimation animationWithFile:@"bomb" frameCount:2 delay:0.5f]; //运用给CCAnimation扩展的方法来生成一个动画对象anim
CCAnimate *animate = [CCAnimate actionWithAnimation:anim];
CCRepeatForever *repeate = [CCRepeatForever actionWithAction:animate]; //生成一个重复动做的repeate对象
[bomb runAction:repeate]; //让player运行这个重复动做
}
return self;
}
-(void)explosionEffect {
CCParticleSystem *system;
system = [CCParticleSun node];
[bomb addChild:system z:0 tag:1];
system.position = ccp(bombSize.width/2,bombSize.height/2);
system.startSize = 38.0f;
system.startSizeVar = 80.0f;
system.endSize = 88.0f;
system.endSizeVar = 80.0f;
isCollision = YES;
}
-(void)removeExplosion {
isCollision = NO;
[bomb removeAllChildrenWithCleanup:YES];
}
-(void)dealloc {
[super dealloc];
}
3. myCoin类
myCoin类与myBomb类类似,不一样的是没有采用粒子效果,而是在碰撞时播放一个+1的渐隐动画:
-(id)init {
if (self == [super init]) {
coin = [CCSprite spriteWithFile:@"coin0.png"]; //用coin0图片初始化coin精灵
coinSize = [coin texture].contentSize;
isGet = NO;
[self addChild:coin z:0 tag:1]; //将coin精灵添加到myCoin中
coin.position = CGPointMake(0, 0); //初始化coin的位置
CCAnimation *anim = [CCAnimation animationWithFile:@"coin" frameCount:2 delay:0.5f]; //运用给CCAnimation扩展的方法来生成一个动画对象anim
CCAnimate *animate = [CCAnimate actionWithAnimation:anim];
CCRepeatForever *repeate = [CCRepeatForever actionWithAction:animate]; //生成一个重复动做的repeate对象
[coin runAction:repeate]; //让coin运行这个重复动做
}
return self;
}
-(void)getEffect { //碰撞金币后的效果
[coin stopAllActions]; //先暂停coin自己的全部动做
CCTexture2D * texture =[[CCTextureCache sharedTextureCache] addImage:@"plusone.png"]; //新建一个贴图对象
[coin setTexture:texture]; //将coin的贴图更换为texture贴图对象
CCFadeOut *getAnim = [CCFadeOut actionWithDuration:0.5f]; //+1图片渐隐
[coin runAction:getAnim]; //coin运行+1渐隐的动做
//NSLog(@"%@",coin);
isGet = YES; //将该coin的已获取标志位置为yes
}
-(void)removeEffect { //+1动做完成后对该coin的重置操做
[coin stopAllActions]; //先暂停当前coin执行的全部动做
coin.opacity = 255; //将透明度重置为彻底不透明
CCTexture2D * texture =[[CCTextureCache sharedTextureCache] addImage:@"coin0.png"]; //新建一个贴图对象
[coin setTexture:texture];
CCAnimation *initAnim = [CCAnimation animationWithFile:@"coin" frameCount:2 delay:0.5f]; //运用给CCAnimation扩展的方法来生成一个动画对象anim
CCAnimate *initAnimate = [CCAnimate actionWithAnimation:initAnim];
CCRepeatForever *initRepeate = [CCRepeatForever actionWithAction:initAnimate]; //生成一个重复动做的repeate对象
[coin runAction:initRepeate]; //coin运行这个重复动做
isGet = NO;
}
-(void)dealloc {
[super dealloc];
}
4. Helper类
对系统CCAnimation扩展的方法在Helper里实现,cocos2d容许采用类别功能(category)对其系统提供的类进行方法扩展,但仅能添加方法,不能添加类的成员变量。首先在Helper.h里进行类别扩展的声明:
@interface CCAnimation(Helper)
+(CCAnimation *)animationWithFile:(NSString *)name frameCount:(int)frameCount delay:(float)delay; //给CCAnimation类扩展的一个方法,经过动画名称,帧数和每帧延迟生成一个动画对象
@end
在Helper.m里对扩展的类别方法的实现:
@implementation CCAnimation(Helper)
//使用单个文件生成动画
+(CCAnimation *)animationWithFile:(NSString *)name frameCount:(int)frameCount delay:(float)delay {
//把动画帧做为贴图进行加载,而后生成精灵动画帧
NSMutableArray *frames = [NSMutableArray arrayWithCapacity:frameCount];
for (int i = 0; i < frameCount; i++) {
//假设全部动画帧的名字均为“名字+数字.png”的格式
NSString *file = [NSString stringWithFormat:@"%@%i.png",name,i];
CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage:file];
//假设老是使用整个动画帧文件
CGSize textureSize = texture.contentSize;
CGRect textureRect = CGRectMake(0, 0, textureSize.width, textureSize.height);
CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:texture rect:textureRect];
[frames addObject:frame];
}
//使用全部的精灵动画帧,返回一个动画对象
return [CCAnimation animationWithFrames:frames delay:delay];
}
@end
这样咱们就能够经过单张的图片来直接造成myBomb和myCoin的精灵动画了。
5. LoadingScene类
因为在场景之间切换会须要加载资源,消耗时间,所以设置一个载入界面无疑能够减弱加载过程对用户体验的影响。这里的载入界面,咱们经过LoadingScene类来实现,经过维护这一个界面,便可实现向各个界面的跳转。首先咱们在LoadingScene.h中声明一个枚举类型TargetScenes来列举咱们须要涉及载入的界面,同时咱们在此保存须要在各个界面间传递的变量,如score。另外,声明一个静态方法sceneWithTargetScene:以供其余需载入的场景调用。具体声明以下:
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "GameScene.h"
#import "EndScene.h"
typedef enum {
TargetSceneINVALID = 0,
TargetSceneFirstScene, //有任何新的须要载入的场景只须要在此增长新的场景枚举类型就可,如TargetSceneSecondScene。。。
TargetSceneSecondScene,
TargetSceneMAX,
} TargetScenes;
@interface LoadingScene : CCScene {
TargetScenes targetScene_;
int endScore;
}
+(id)sceneWithTargetScene:(TargetScenes)targetScene;
-(id)initWithTargetScene:(TargetScenes)targetScene;
-(void)loadingUpdate:(ccTime)delta;
@end
在LoadingScene.m中,实现sceneWithTargetScene:方法以及LoadingScene的初始化方法。initWithTargetScene:方法中经过在GameScene中设定的静态方法获取到当前的GameScene对象。以后经过targetScene_进行条件判断需载入的目标场景,详细以下:
+(id)sceneWithTargetScene:(TargetScenes)targetScene { //此处的targetScene参数即为想要加载进来的下一个场景
return [[[self alloc] initWithTargetScene:targetScene] autorelease]; //生成一个当前类的自动释放对象,self便是LoadingScene
}
-(id)initWithTargetScene:(TargetScenes)targetScene {
if (self == [super init]) {
targetScene_ = targetScene; //将要加载的目标场景给到全局的枚举变量中,以在update函数中判断并加载对应的场景
GameScene *tmpScene = [GameScene getShared];
endScore = tmpScene.score;
//生成并添加一个载入的文本标签
CCLabelTTF *label = [CCLabelTTF labelWithString:@"Loading..." fontName:@"Marker Felt" fontSize:38];
CGSize size = [[CCDirector sharedDirector] winSize];
label.position = CGPointMake(size.width * 0.5f, size.height * 0.5f);
[self addChild:label];
[self schedule:@selector(loadingUpdate:) interval:2.3f]; //必须在下一帧加载目标场景
}
return self;
}
-(void)loadingUpdate:(ccTime)delta {
[self unscheduleAllSelectors];
switch (targetScene_) { //经过targetScene_这个枚举类型决定加载哪一个scene
case TargetSceneFirstScene:
[[CCDirector sharedDirector] replaceScene:[GameScene scene]];
break;
case TargetSceneSecondScene:
[[CCDirector sharedDirector] replaceScene:[EndScene endScene:endScore]];
break;
default:
NSAssert2(nil,@"%@:unsupported TargetScene %i",NSStringFromSelector(_cmd),targetScene_); //使用未指定的枚举类型时发出的警告信息
break;
}
}
这样,咱们就能够在任意场景中经过CCDirector的replaceScene方法先切换到LoadingScene,再由LoadingScene过渡到目标场景了。
6. EndScene类
这个层须要呈现的内容并很少,咱们只是简单在这里呈现用户的得分,并提供用户一个从新开始的按钮。这里的初始化方法须要带入一个用户得分的参数,由LoadingScene类传递过来:
+(id)endScene:(int)myScore {
return [[[self alloc] initScene:myScore] autorelease];
}
-(id)initScene:(int)myScore {
if (self == [super init]) {
//生成一个分数标签
CCLabelTTF *label = [CCLabelTTF labelWithString:[NSString stringWithFormat:@"Your Score is: %i",myScore] fontName:@"Marker Felt" fontSize:38];
CGSize size = [[CCDirector sharedDirector] winSize];
label.position = CGPointMake(size.width * 0.5f, size.height * 0.5f);
label.anchorPoint = ccp(0.5f,0);
[self addChild:label];
//生成一个从新开始的按钮
[CCMenuItemFont setFontName:@"STHeitiJ-Light"];
[CCMenuItemFont setFontSize:26];
CCMenuItemFont *item1 = [CCMenuItemFont itemFromString:@"Retry!" target:self selector:@selector(redirectToGameScene)];
item1.color = ccGREEN;
CCMenu *menu = [CCMenu menuWithItems:item1,nil];
menu.position = CGPointMake(size.width * 0.5f, size.height * 0.5f - 88.0f);
menu.anchorPoint = ccp(0.5f,1.0f);
[self addChild:menu];
}
return self;
}
-(void)redirectToGameScene {
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0f scene:[LoadingScene sceneWithTargetScene:TargetSceneFirstScene]]]; //调用载入页面
}
到这里,整个工程就基本上完成了,剩下的就是添加须要的资源文件了,好比:主角、炸弹、金币等游戏元素在各类状态下的图片,游戏中用到的音效等等。这部分就再也不罗嗦了,固然是否能设计出引人入胜的图片和音效是游戏成败的关键,我一直这么以为~真正的独立游戏开发者应该是一个全才(像韩国开发亡灵杀手的那位大神),继续努力~~~加油!node