【吼吼睡cocos2d学习笔记】第四章 - 第一个游戏
2011-12-12 14:47
295 查看
来让我们开始第一个游戏的制作。
这个过程可能有点艰辛,但是只要坚持下来,第一个游戏往往能给我们带来巨大的收益(当然这个收益不是经济上的:-P)
先上截图:
iPad中:
游戏构思
角色
在屏幕的上方,有一定数量的敌人(蜘蛛),屏幕下方有一只玩家控制的熊猫。
游戏流程
每间隔一段时间,会有一只蜘蛛爬下来袭击熊猫,熊猫通过移动来躲避攻击。随着游戏的进行,蜘蛛下降的速度会越来越快,出动的频率会越来越高。
胜负判定
熊猫躲避过一定数目的蜘蛛以后获胜,在此之前玩家用完所有生命则失败。
游戏展示动画,其实是有音效的,可能屏幕录像的软件不能捕捉来自模拟器的声音:展示动画
分析
我们按照事务流的方式来对整个游戏进行简单分析:
1.启动游戏,加载主页面。本示例不做菜单,不做配置,直接进入游戏场景。
2.将游戏置为READY状态;初始化各种数据;根据屏幕的宽度计算蜘蛛的个数,初始化蜘蛛精灵;初始化熊猫精灵
3.当玩家触摸屏幕以后,游戏开始,游戏状态设置为PLAYING,启动以下计时器:
3a.播放蜘蛛帧动画的计时器
3b.搜索下一个出动的蜘蛛计时器
3c.碰撞检测的计时器
4.玩家用手指控制熊猫在屏幕下方移动,这里要注意的是,接收触摸事件的是熊猫,如果触摸点不在熊猫上,它是不能移动的。因此需要给熊猫精灵添加一个targetedTouchDelegate。同时要防止熊猫划出屏幕边界。
5.定时检测下一个出动的蜘蛛,找到以后,让蜘蛛下移的屏幕低部,然后复位,如果熊猫躲避过来此次攻击,加分。当出动的蜘蛛次数超过一定量的时候(本游戏中是8次),加快游戏速度,加快蜘蛛出动频率。同时判断是否已经满足胜利条件。
6.当碰撞检测计时器检测到碰撞后,停止出动蜘蛛计时器、碰撞检测计时器、动画播放计时器,生命减一,判断是否还有剩余生命。如果没有,Game Over,游戏状态设置为END;如果还有生命,游戏状态设置为DEAD。
7.当玩家触摸屏幕的时候:
7.a.如果游戏状态是READY,开始游戏。
7.b 如果游戏状态是DEAD,复位蜘蛛和熊猫,启动各个计时器。
7.c 如果游戏状态为其他状态,无视。
8.对熊猫精灵的事件分析:
当熊猫精灵接收到触摸事件以后,判断是否命中到了精灵范围内,如果是,吃掉该事件,否则让该事件继续下发给其他对象。
9.注意,为了无缝的向iPad设备上移植,需要注意:计量避免出现假设性代码,所有的尺寸都根据屏幕尺寸计算。
开工
XCode->New Project->IOS->cocos2d-name it->finish!
删掉默认的helloWorld层,按照下图,创建Group:
Sprites:盛放精灵类
Layers:盛放所有层
Scenes:盛放所有的场景
RootViewController.m
本游戏适合在竖屏模式下进行,因此需要做以下修改:
AppDelegate.m
因为删除了HelloWorld层,现在需要启动我们自己添加的GameScene场景,因此需要修改AppDelegate.m的相关代码:
找到applicationDidFinishLaunching方法中[CCDirector sharedDirector] runWithScene的代码,此代码的作用是让【导演】运行第一个游戏场景,将之修改成:
GameScene使我们自己设计的场景类,在Scenes文件组中,scene是该类的初始化方法,负责返回一个GameScene对象。
做完这些以后我们来实现主场景类:GameScene
首先在.h文件中添加静态scene方法的声明:
在.m文件中实现该方法,同时加上对资源的释放:
scene方法中构造了一个CCScene对象,并将GameLayer层作为子节点加入其中。
其实在IOS开发中,scene对象中的代码量往往非常少,代码大部分出现在层和精灵中。
在看关键的GameLayer以前,我们先来看一下熊猫精灵(PandaSprite)类
这个类继承自CCSprite,同时实现了CCTargetedTouchDelegate协议。这是.m中的代码
代码中已经注视的非常清楚了,这里不再赘述。需要说明的是,在CCTouchMoved方法中,有如下代码:
curLayer是在.h中声明的id类型的对象:
id是Objective-C中所有节点的父类,相当于c#中的Object类。该对象将来会传入一个GameLayer的对象。之所以这样做是因为在熊猫精灵并非在任何时候都被允许移动的,只有在游戏状态为PLAYING的时候才响应该事件。具体可以参看GameLayer.m中的getGameStatus方法。这是一种在精灵和层之间传递数据的方式。
好,现在看是来看重量级的GameLayer类
先看.h文件
有点多,但是每一行我的加上了注释,每个方法的实现都在.m中:
我想注释已经足够清楚了,有序考虑到了向iPad平台的兼容,所以有大量的代码用来计算尺寸和位置。千万不要认为这是在浪费时间,记住一句话:
程序员应该尽量少写基于假设的代码
比如spider.positon = CGPointMake(160,32)。
你写这行代码的本意可能是想将熊猫精灵放在屏幕的底部的中间,听起来似乎不错,因为当前你做的是iphone的开发,熊猫的高度是64px。但是这都基于两个假设:
假设一:屏幕宽度是320px,显然并非所有的IOS设备都是这样。
假设二:熊猫高度是64px。
事实上,我们很容易在游戏进行到一定的程度以后,要添加新的需求,比如移植到iPad上,比如说你想增加一个关卡,这次主角是一个蚂蚁或者一只大象。那么这些基于假设的代码就会成为让你加班的原因。也很有可能会耽误你和女儿的周末晚餐⋯⋯
资源
本例中,用到了以下资源:
bomb.caf:主角死亡时候播放的音效。
sprite.plist & sprite.png:精灵贴图列表,使用Zwoptex文件制作,这个工具使用起来非常简单,大家可以google之。
myfont.fnt & myfont.png:自定义字体类表和图像,使用hiero制作。游戏开发必备。
说得再多,不如自己动手写一遍。
奉上源码:cocos2d-蜘蛛人源码
回见。
这个过程可能有点艰辛,但是只要坚持下来,第一个游戏往往能给我们带来巨大的收益(当然这个收益不是经济上的:-P)
先上截图:
iPad中:
游戏构思
角色
在屏幕的上方,有一定数量的敌人(蜘蛛),屏幕下方有一只玩家控制的熊猫。
游戏流程
每间隔一段时间,会有一只蜘蛛爬下来袭击熊猫,熊猫通过移动来躲避攻击。随着游戏的进行,蜘蛛下降的速度会越来越快,出动的频率会越来越高。
胜负判定
熊猫躲避过一定数目的蜘蛛以后获胜,在此之前玩家用完所有生命则失败。
游戏展示动画,其实是有音效的,可能屏幕录像的软件不能捕捉来自模拟器的声音:展示动画
分析
我们按照事务流的方式来对整个游戏进行简单分析:
1.启动游戏,加载主页面。本示例不做菜单,不做配置,直接进入游戏场景。
2.将游戏置为READY状态;初始化各种数据;根据屏幕的宽度计算蜘蛛的个数,初始化蜘蛛精灵;初始化熊猫精灵
3.当玩家触摸屏幕以后,游戏开始,游戏状态设置为PLAYING,启动以下计时器:
3a.播放蜘蛛帧动画的计时器
3b.搜索下一个出动的蜘蛛计时器
3c.碰撞检测的计时器
4.玩家用手指控制熊猫在屏幕下方移动,这里要注意的是,接收触摸事件的是熊猫,如果触摸点不在熊猫上,它是不能移动的。因此需要给熊猫精灵添加一个targetedTouchDelegate。同时要防止熊猫划出屏幕边界。
5.定时检测下一个出动的蜘蛛,找到以后,让蜘蛛下移的屏幕低部,然后复位,如果熊猫躲避过来此次攻击,加分。当出动的蜘蛛次数超过一定量的时候(本游戏中是8次),加快游戏速度,加快蜘蛛出动频率。同时判断是否已经满足胜利条件。
6.当碰撞检测计时器检测到碰撞后,停止出动蜘蛛计时器、碰撞检测计时器、动画播放计时器,生命减一,判断是否还有剩余生命。如果没有,Game Over,游戏状态设置为END;如果还有生命,游戏状态设置为DEAD。
7.当玩家触摸屏幕的时候:
7.a.如果游戏状态是READY,开始游戏。
7.b 如果游戏状态是DEAD,复位蜘蛛和熊猫,启动各个计时器。
7.c 如果游戏状态为其他状态,无视。
8.对熊猫精灵的事件分析:
当熊猫精灵接收到触摸事件以后,判断是否命中到了精灵范围内,如果是,吃掉该事件,否则让该事件继续下发给其他对象。
9.注意,为了无缝的向iPad设备上移植,需要注意:计量避免出现假设性代码,所有的尺寸都根据屏幕尺寸计算。
开工
XCode->New Project->IOS->cocos2d-name it->finish!
删掉默认的helloWorld层,按照下图,创建Group:
Sprites:盛放精灵类
Layers:盛放所有层
Scenes:盛放所有的场景
RootViewController.m
本游戏适合在竖屏模式下进行,因此需要做以下修改:
#elif GAME_AUTOROTATION == kGameAutorotationUIViewController return ( UIInterfaceOrientationIsLandscape( interfaceOrientation ) );修改为
#elif GAME_AUTOROTATION == kGameAutorotationUIViewController return ( UIInterfaceOrientationIsLandscape( interfaceOrientation ) );
AppDelegate.m
因为删除了HelloWorld层,现在需要启动我们自己添加的GameScene场景,因此需要修改AppDelegate.m的相关代码:
找到applicationDidFinishLaunching方法中[CCDirector sharedDirector] runWithScene的代码,此代码的作用是让【导演】运行第一个游戏场景,将之修改成:
[[CCDirector sharedDirector] runWithScene: [GameScene scene]];
GameScene使我们自己设计的场景类,在Scenes文件组中,scene是该类的初始化方法,负责返回一个GameScene对象。
做完这些以后我们来实现主场景类:GameScene
首先在.h文件中添加静态scene方法的声明:
+(CCScene *)scene;
在.m文件中实现该方法,同时加上对资源的释放:
-(void)dealloc { [super dealloc]; } +(CCScene *)scene { CCScene *sc = [CCScene node]; [sc addChild:[GameLayer node]]; return sc; }
scene方法中构造了一个CCScene对象,并将GameLayer层作为子节点加入其中。
其实在IOS开发中,scene对象中的代码量往往非常少,代码大部分出现在层和精灵中。
在看关键的GameLayer以前,我们先来看一下熊猫精灵(PandaSprite)类
这个类继承自CCSprite,同时实现了CCTargetedTouchDelegate协议。这是.m中的代码
// // GameLayer.h // CH04 // // Created by 李庆辉 on 11-12-8. // QQ:66927785 // Blog:http://blog.csdn.net/redparty // Copyright 2011年 __MS__. All rights reserved. // #import "PandaSprite.h" @implementation PandaSprite @synthesize curLayer; //释放delegate -(void)onExit { [[CCTouchDispatcher sharedDispatcher] removeDelegate:self]; [super onExit]; } //当被node的时候,触发该事件,注册targetedDelegate -(void)onEnter { [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES]; [super onEnter]; } //获得自身的rect,用来进行命中判定 -(CGRect)rect { return CGRectMake(-rect_.size.width * 0.5, -rect_.size.height * 0.5, rect_.size.width, rect_.size.height); } //当touch开始的时候,判定是否命中了自身,如果是,吃掉该事件,反之忽略该事件 -(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event { if (CGRectContainsPoint([self rect], [self convertTouchToNodeSpaceAR:touch])) { return YES; } return NO; } //根据玩家的触摸,变换主角的位置。 -(void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event { //获得GameLayer中的gameStatus的值,如果不是PLAYING,则忽略当前触摸。 if ([curLayer getGameStatus] != @"PLAYING") { return; } CGSize sizeOfWin = [[CCDirector sharedDirector] winSize]; //获得自己的尺寸的一半,用来对左右两边缘的位置进行校正 CGSize halfOfMyself; halfOfMyself = CGSizeMake([self contentSize].width * 0.5, [self contentSize].height * 0.5); //根据自身的大小确定自己在x轴方向上的最小值和最大值 CGFloat minX = halfOfMyself.width; CGFloat maxX = sizeOfWin.width - halfOfMyself.width; CGPoint posOfTouch = [touch locationInView:touch.view]; CGPoint posForGL = [[CCDirector sharedDirector] convertToGL:posOfTouch]; //对越界情况进行校正 if (posForGL.x < minX) { posForGL.x = minX; } if (posForGL.x > maxX) { posForGL.x = maxX; } //坐标系转换 posForGL.y = [self contentSize].height * 0.5; self.position = posForGL; } @end
代码中已经注视的非常清楚了,这里不再赘述。需要说明的是,在CCTouchMoved方法中,有如下代码:
//获得GameLayer中的gameStatus的值,如果不是PLAYING,则忽略当前触摸。 if ([curLayer getGameStatus] != @"PLAYING") { return; }
curLayer是在.h中声明的id类型的对象:
// // GameLayer.h // CH04 // // Created by 李庆辉 on 11-12-8. // QQ:66927785 // Blog:http://blog.csdn.net/redparty // Copyright 2011年 __MS__. All rights reserved. // #import <Foundation/Foundation.h> #import "cocos2d.h" @interface PandaSprite : CCSprite<CCTargetedTouchDelegate> { id curLayer; } @property (nonatomic,retain)id curLayer; @end
id是Objective-C中所有节点的父类,相当于c#中的Object类。该对象将来会传入一个GameLayer的对象。之所以这样做是因为在熊猫精灵并非在任何时候都被允许移动的,只有在游戏状态为PLAYING的时候才响应该事件。具体可以参看GameLayer.m中的getGameStatus方法。这是一种在精灵和层之间传递数据的方式。
好,现在看是来看重量级的GameLayer类
先看.h文件
// // GameLayer.h // CH04 // // Created by 李庆辉 on 11-12-8. // QQ:66927785 // Blog:http://blog.csdn.net/redparty // Copyright 2011年 __MS__. All rights reserved. // #import <Foundation/Foundation.h> #import "cocos2d.h" #import "PandaSprite.h" #import "SimpleAudioEngine.h" @interface GameLayer : CCLayer { //窗口尺寸 CGSize sizeOfWin ; //蜘蛛的尺寸 CGSize sizeOfSpider; //熊猫的尺寸 CGSize sizeOfPanda; //盛放蜘蛛精灵的数组 CCArray *spiders; //盛放熊猫生命的数组 CCArray *LivesPandas; //主角精灵 PandaSprite *panda; //蜘蛛的个数 int spiderNumber; //控制游戏速度的一个因子,会被用在计算蜘蛛的下降速度和下降频率上 CGFloat speed; //当前的游戏状态,分为:READY,PLAYING,DEAD,END,OVER NSString *gameStatus; //显示分数的label CCLabelTTF *lblScoreShow; //显示游戏信息的label CCLabelTTF *lblInfo; //动画播放分数的label CCLabelBMFont *lblScoreAnimate; //没有什么具体含义,仅仅被用来控制分数计算的频率 int numSpidersMoved; int livesCount; //游戏得分 int score; } //播放蜘蛛动画 -(void)playSpiderAnimate; //重置蜘蛛们的位置 -(void)resetSpider; //寻找下一个行动的蜘蛛 -(void)checkSpider:(ccTime)dt; //将checkSpider寻找到的蜘蛛下坠并复位 -(void)downSpider:(CCSprite *)spider; //作为downSpider中action的回调函数,负责让到达屏幕底部的蜘蛛复位 -(void)makeSpiderBack:(CCSprite *)spider; //碰撞检测 -(void)checkCollision; //停止所有发生在蜘蛛和主角上的动作。 -(void)stopAllAction; //返回gameStatus的值,这个值会在PandaSprite中用到 -(NSString *)getGameStatus; //该方法根据speed来改变蜘蛛出动的频率。 -(void)changeCheckTimeout; //创建死亡label -(void)createLables; //创建蜘蛛数组 -(void)createSpiderArray; //创建显示生命的熊猫 -(void)createLivesPandas; //创建主角 -(void)createPanda; //初始化游戏 -(void)initGame; //用Action来显示实时分数 -(void)showAnimateScore; //当胜利的时候 -(void)whenWin; //当碰撞的时候 -(void)whenCollision; //根据当前剩余的生命显示对应个数的熊猫 -(void)showLives; //开始游戏相关的计时器 -(void)startSchedule; //停止游戏相关的计时器 -(void)stopSchedule; @end
有点多,但是每一行我的加上了注释,每个方法的实现都在.m中:
// // GameLayer.h // CH04 // // Created by 李庆辉 on 11-12-8. // QQ:66927785 // Blog:http://blog.csdn.net/redparty // Copyright 2011年 __MS__. All rights reserved. // #import "GameLayer.h" @implementation GameLayer #define SPRITETAG 100 #define LABLE_TAG 150 #define SCORE_HEIGHT 30 #define SCALE_SPIDER 0.5 #define SCALE_PANDA 0.8 #define FADE_SCORE 45 #define SPEED 2 #define DTSPEED 250 #define MAXLIVES 5 #define SCALE_LIVESPANDA 0.25 static int framIndex; //释放层用到的非autoRelease资源 -(void)dealloc { [super dealloc]; [spiders release]; spiders = nil; } -(id)init { if (self = [super init]) { [self initGame]; } return self; } -(void)initGame { //获得屏幕尺寸 sizeOfWin = [[CCDirector sharedDirector] winSize]; //创建label(分数、生命、死亡信息、win) [self createLables]; //创建蜘蛛数组 [self createSpiderArray]; //创建主角 [self createPanda]; //创建显示生命用的熊猫 [self createLivesPandas]; //重置蜘蛛位置 [self resetSpider]; //预加载音效文件,如果不予加载的话,第一次播放此音效的时候会卡至少一秒钟。 [[SimpleAudioEngine sharedEngine] preloadEffect:@"bomb.caf"]; numSpidersMoved = 1; score = 0; livesCount = MAXLIVES; framIndex = 1; speed = SPEED; gameStatus = @"READY"; [self setIsTouchEnabled:YES]; } -(void)createPanda { //添加主角 CCSpriteFrameCache *frameCache = [CCSpriteFrameCache sharedSpriteFrameCache]; [frameCache addSpriteFramesWithFile:@"sprite.plist"]; panda = [PandaSprite node]; [panda setDisplayFrame:[frameCache spriteFrameByName:@"panda.png"]]; sizeOfPanda = [panda contentSize]; sizeOfPanda.width *= SCALE_PANDA; sizeOfPanda.height *= SCALE_PANDA; [self addChild:panda z:3]; //将本层传入panda对象中,实现层和精灵的信息传递 panda.curLayer = self; } -(void)createLivesPandas { CCSpriteFrameCache *frameCache = [CCSpriteFrameCache sharedSpriteFrameCache]; [frameCache addSpriteFramesWithFile:@"sprite.plist"]; CCSprite *tmpPanda = [CCSprite spriteWithSpriteFrame:[frameCache spriteFrameByName:@"zz1.png"]]; //获得生命区熊猫的尺寸 CGSize sizeOfLivesPanda = [tmpPanda contentSize]; sizeOfLivesPanda.width *= SCALE_LIVESPANDA; sizeOfLivesPanda.height *= SCALE_LIVESPANDA; LivesPandas = [[CCArray alloc] initWithCapacity:MAXLIVES]; for (int i = 0; i < MAXLIVES; i++) { CCSprite * tmpPanda = [CCSprite spriteWithSpriteFrame:[frameCache spriteFrameByName:@"panda.png"]]; [LivesPandas addObject:tmpPanda]; tmpPanda.scale = SCALE_LIVESPANDA; tmpPanda.position = CGPointMake((i+1)*sizeOfLivesPanda.width, sizeOfWin.height - sizeOfLivesPanda.height - 5 ); [self addChild:tmpPanda]; } } -(void)createSpiderArray { //创建蜘蛛精灵表的帧缓存,并加载蜘蛛精灵动作的plist文件 CCSpriteFrameCache *frameCache = [CCSpriteFrameCache sharedSpriteFrameCache]; [frameCache addSpriteFramesWithFile:@"sprite.plist"]; //生成临时蜘蛛,获取缩放以后蜘蛛的尺寸。 CCSprite* spider = [CCSprite spriteWithSpriteFrame:[frameCache spriteFrameByName:@"zz1.png"]]; spider.scale = SCALE_SPIDER; sizeOfSpider = [spider contentSize]; sizeOfSpider.width *= SCALE_SPIDER; sizeOfSpider.height *= SCALE_SPIDER; //根据蜘蛛的尺寸计算可以放置的蜘蛛的个数,根据个数初始化蜘蛛数组 spiderNumber = sizeOfWin.width/sizeOfSpider.width; spiders = [[CCArray alloc] initWithCapacity:spiderNumber]; for (int i = 0; i < spiderNumber; i++) { CCSprite *tmpSpider = [CCSprite spriteWithSpriteFrame:[frameCache spriteFrameByName:@"zz1.png"]]; [spiders addObject:tmpSpider]; tmpSpider.scale = SCALE_SPIDER; [self addChild:tmpSpider z:0 tag:SPRITETAG + i]; } } -(void)createLables { //信息 lblInfo = [CCLabelTTF labelWithString:@"" fontName:@"Arial" fontSize:22]; lblInfo.position = CGPointMake(sizeOfWin.width * 0.5, sizeOfWin.height * 0.5); [self addChild:lblInfo z:100 tag:LABLE_TAG]; [lblInfo setVisible:NO]; [lblInfo setOpacity:125]; //分数 CCLabelTTF *lblScore = [CCLabelTTF labelWithString:@"分数:" fontName:@"Arial" fontSize:14]; lblScore.anchorPoint = CGPointMake(1, 1); lblScore.position = CGPointMake(sizeOfWin.width - 60 - [lblScore contentSize].width/2, sizeOfWin.height - 10); [self addChild:lblScore]; lblScoreShow = [CCLabelTTF labelWithString:@"0000000" fontName:@"Arial" fontSize:14]; lblScoreShow.anchorPoint = CGPointMake(1, 1); lblScoreShow.position = CGPointMake(sizeOfWin.width - [lblScore contentSize].width/2,sizeOfWin.height - 10); [self addChild:lblScoreShow]; //实时显示当前得分的标签,用到了BMFont,使用Hiero制作 lblScoreAnimate = [CCLabelBMFont labelWithString:@"" fntFile:@"myfont.fnt"]; lblScoreAnimate.scale = 0; [lblScoreAnimate setOpacity:FADE_SCORE]; lblScoreAnimate.position = CGPointMake(sizeOfWin.width * 0.5, sizeOfWin.height * 0.5); [self addChild:lblScoreAnimate]; //生命 } -(void)resetSpider { //将蜘蛛们复位 CGSize halfSize = CGSizeMake(sizeOfSpider.width * 0.5, sizeOfSpider.height * 0.5); CGFloat leftMargin = (sizeOfWin.width - sizeOfSpider.width * spiderNumber) * 0.5; for (int i = 0; i < spiderNumber; i++) { CCSprite *spider = (CCSprite *)[self getChildByTag:SPRITETAG + i]; spider.position = CGPointMake((i+1)*sizeOfSpider.width - halfSize.width+leftMargin , sizeOfWin.height - halfSize.height - SCORE_HEIGHT); [spider stopAllActions]; } //将熊猫复位 panda.position = CGPointMake(sizeOfWin.width * 0.5, sizeOfPanda.height * 0.5); } -(void)playSpiderAnimate { CCSpriteFrameCache *frameCache = [CCSpriteFrameCache sharedSpriteFrameCache]; [frameCache addSpriteFramesWithFile:@"sprite.plist"]; if (++framIndex >2) { framIndex = 1; } for (int i = 0; i<spiderNumber; i++) { CCSprite *tmpspider = [spiders objectAtIndex:i]; if ([tmpspider numberOfRunningActions] == 0) { //为了让蜘蛛的动画产生不一致,避免所有的蜘蛛播放相同的纹理,将i也加入了计算中,最终得到的是一个介于1-2的整数 [tmpspider setDisplayFrame:[frameCache spriteFrameByName:[NSString stringWithFormat:@"zz%d.png",(framIndex + i)%2 +1]]]; } } } -(void)changeCheckTimeout { [self unschedule:@selector(checkSpider:)]; [self schedule:@selector(checkSpider:) interval:0.25 * speed]; } //寻找下一个出动的蜘蛛 -(void)checkSpider:(ccTime)dt { for (int i = 0; i<20; i++) { int checkIndex = CCRANDOM_0_1() * spiderNumber; CCSprite *spider = [spiders objectAtIndex:checkIndex]; //如果找到了一个本身没有动作的蜘蛛,说明该蜘蛛还没有出动,出动之。 if ([spider numberOfRunningActions] == 0) { //出动蜘蛛 [self downSpider:spider]; break; } } } -(void)whenWin { [lblInfo setString:@"You Win!"]; [lblInfo setVisible:YES]; [self stopAllAction]; [self unschedule:@selector(checkSpider:)]; [self unschedule:@selector(checkCollision)]; gameStatus = @"END"; } //计算分数,用动画的方式现在在屏幕中间,同时累加到分数变量,显示在右上角。 -(void)showAnimateScore { //根据当前speed计算当前得分,原则上是:速度越快,单位得分越高 int scoreBySpeed = ((SPEED+0.1)-speed) * DTSPEED; score += scoreBySpeed; [lblScoreAnimate setString:[NSString stringWithFormat:@"%d",scoreBySpeed]]; [lblScoreShow setString:[NSString stringWithFormat:@"%07d",score]]; //播放动画前,将label透明度调大,尺寸缩小到0 lblScoreAnimate.scale = 0; [lblScoreAnimate setOpacity:FADE_SCORE]; //创建一个放大动作和一个隐出动作 CCAction *acS = [CCScaleTo actionWithDuration:0.2 scale:3]; CCAction *acE= [CCFadeTo actionWithDuration:0.2 opacity:0]; //用CCSpawn的方式同步执行两个动作 [lblScoreAnimate runAction:[CCSpawn actions:acS,acE, nil]]; } //出动蜘蛛 -(void)downSpider:(CCSprite *)spider { //蜘蛛移动的目标位置 CGPoint targetPos = CGPointMake(spider.position.x, [spider contentSize].height * 0.5); CCAction *ac = [CCMoveTo actionWithDuration:speed position:targetPos]; //当蜘蛛执行玩ac动作以后,回来执行callBack指向的回调函数:makeSpiderBack CCCallFuncN *callBack = [CCCallFuncN actionWithTarget:self selector:@selector(makeSpiderBack:) ]; //用CCSequence的方式执行ac和callBack [spider runAction:[CCSequence actions:ac,callBack, nil]]; } //回调函数,让蜘蛛复位 -(void)makeSpiderBack:(CCSprite *)spider { CGPoint backPos = CGPointMake(spider.position.x, sizeOfWin.height - sizeOfSpider.height * 0.5 - SCORE_HEIGHT); CCAction *back = [CCMoveTo actionWithDuration:1 position:backPos]; [spider runAction:back]; //播放加分动画,加分 [self showAnimateScore]; //每有一只蜘蛛被躲避开,就加快游戏速度,同时进行获胜判定 numSpidersMoved++; if (numSpidersMoved %5 == 0) { //在本游戏中,如果速度快到0.7,认为玩家获胜。 if (speed < 0.7) { [self whenWin]; return; } speed -= 0.02; [self changeCheckTimeout]; numSpidersMoved = 1; } } //当用户点击屏幕的时候,根据不同的情景改变游戏状态。 -(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { //如果当前是死亡状态,继续游戏 if (gameStatus == @"DEAD") { [panda stopAllActions]; [panda setVisible:YES]; [lblInfo setVisible:NO]; [self resetSpider]; gameStatus = @"PLAYING"; [self changeCheckTimeout]; [self startSchedule]; } //如果当前是READY状态,开始游戏 if (gameStatus == @"READY") { gameStatus = @"PLAYING"; [self changeCheckTimeout]; [self startSchedule]; } } //开始游戏相关的计时器 -(void)startSchedule { //让蜘蛛动起来 [self unschedule:@selector(playSpiderAnimate)]; [self schedule:@selector(playSpiderAnimate) interval:0.4]; //启动碰撞检测 [self unschedule:@selector(checkCollision)]; [self schedule:@selector(checkCollision) interval:0.02]; } //停止游戏相关的计时器 -(void)stopSchedule { [self unschedule:@selector(playSpiderAnimate)]; [self unschedule:@selector(checkCollision)]; } //碰撞检测 -(void)checkCollision { //计算熊猫和蜘蛛的最大相距半径,大于此值认为发生了碰撞。 float maxDistance = sizeOfSpider.width * 0.45 +sizeOfPanda.width * 0.45; //依次判定每一个蜘蛛是否与熊猫发生了碰撞 for (int i = 0; i < spiderNumber; i++) { CCSprite *spider; spider = [spiders objectAtIndex:i]; //忽略没有出动的蜘蛛 if ([spider numberOfRunningActions] == 0) { continue; } //得到当前蜘蛛和熊猫的距离 float actualDistance = ccpDistance(spider.position, panda.position); if (actualDistance < maxDistance) { [self whenCollision]; break; } } } //当碰撞发生的时候,进行处理 -(void)whenCollision { //播放音频 [[SimpleAudioEngine sharedEngine] playEffect:@"bomb.caf"]; [self unschedule:@selector(checkSpider:)]; [self stopSchedule]; [self stopAllAction]; gameStatus = @"DEAD"; //blink the spider CCAction *blink = [CCBlink actionWithDuration:0.5 blinks:3]; [panda runAction:blink]; livesCount--; [lblInfo setVisible:YES]; if (livesCount == 0) { [lblInfo setString:@"GAME OVER!"]; gameStatus = @"OVER"; return; } [self showLives]; [lblInfo setString:@"你挂了!点击屏幕重新来过!"]; } -(void)showLives { for (int i = livesCount; i<MAXLIVES; i++) { CCSprite *tmpSprite = [LivesPandas objectAtIndex:i]; [tmpSprite setVisible:NO]; } } //停止所有蜘蛛和主角的动作 -(void)stopAllAction { for (int i = 0; i < spiderNumber; i++) { CCSprite *spider = [spiders objectAtIndex:i]; [spider stopAllActions]; } [panda stopAllActions]; } //该方法用在向panda精灵中传递游戏状态,实现:只有在PLAYING的时候才可以移动主角。 -(NSString *)getGameStatus { return gameStatus; } @end
我想注释已经足够清楚了,有序考虑到了向iPad平台的兼容,所以有大量的代码用来计算尺寸和位置。千万不要认为这是在浪费时间,记住一句话:
程序员应该尽量少写基于假设的代码
比如spider.positon = CGPointMake(160,32)。
你写这行代码的本意可能是想将熊猫精灵放在屏幕的底部的中间,听起来似乎不错,因为当前你做的是iphone的开发,熊猫的高度是64px。但是这都基于两个假设:
假设一:屏幕宽度是320px,显然并非所有的IOS设备都是这样。
假设二:熊猫高度是64px。
事实上,我们很容易在游戏进行到一定的程度以后,要添加新的需求,比如移植到iPad上,比如说你想增加一个关卡,这次主角是一个蚂蚁或者一只大象。那么这些基于假设的代码就会成为让你加班的原因。也很有可能会耽误你和女儿的周末晚餐⋯⋯
资源
本例中,用到了以下资源:
bomb.caf:主角死亡时候播放的音效。
sprite.plist & sprite.png:精灵贴图列表,使用Zwoptex文件制作,这个工具使用起来非常简单,大家可以google之。
myfont.fnt & myfont.png:自定义字体类表和图像,使用hiero制作。游戏开发必备。
说得再多,不如自己动手写一遍。
奉上源码:cocos2d-蜘蛛人源码
回见。
相关文章推荐
- 【吼吼睡cocos2d学习笔记】第四章 - 第一个游戏
- 【麦可网】Cocos2d-X跨平台游戏开发学习笔记---第二十一课:Cocos2D-X网格特效1-3
- 【Cocos2d-html5游戏引擎学习笔记(13)】ProgressAction进度计时器
- 【麦可网】Cocos2d-X跨平台游戏开发学习笔记---第二十二课:Cocos2D-X地图系统1-8
- 【麦可网】Cocos2d-X跨平台游戏开发学习笔记---第十一课:Cocos2D-X坐标系统2-3
- 【麦可网】Cocos2d-X跨平台游戏开发学习笔记---第十二课:Cocos2D-X内存管理1-3
- 【吼吼睡cocos2d学习笔记】第一章 - 介绍cocos2d
- cocos2d-x 3.0游戏实例学习笔记 《跑酷》移植到android手机
- cocos2d-x 3.0游戏实例学习笔记 《跑酷》 第六步--金币&岩石加入而且管理
- cocos2d-x 3.0游戏实例学习笔记 《跑酷》移植到android手机
- 【Cocos2d-X开发学习笔记】第30期:游戏中数据的存储(下)
- 【麦可网】Cocos2d-X跨平台游戏开发学习笔记---第二十三课:Cocos2D-X音频系统1-3
- 【麦可网】Cocos2d-X跨平台游戏开发学习笔记---第七课: Cocos2D-X引擎框架2
- cocos2d-x 3.0游戏实例学习笔记 《跑酷》 第三步---主角开跑&同一时候带着刚体
- cocos2d-x 3.0游戏实例学习笔记《卡牌塔防》第五步---着手打造游戏界面
- cocos2d-x学习笔记(18)--游戏打包(windows平台)
- cocos2d-x 3.0游戏实例学习笔记 《跑酷》 第五步--按钮控制主角Jump&Crouch
- Cocos2d-x学习笔记三之飞机游戏详解之GameManager和GameMenu类的讲解。
- cocos2d-x学习笔记(14)笨木头游戏系列LittleRunner
- 【麦可网】Cocos2d-X跨平台游戏开发学习笔记---第四课:Cocos2D-X跨平台开发环境搭建(win32)