为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

(译)如何使用cocos2d来制作一个塔防游戏:第一部分

2012-03-05 11页 pdf 386KB 25阅读

用户头像

is_144099

暂无简介

举报
(译)如何使用cocos2d来制作一个塔防游戏:第一部分 版权属于:子龙山人 首发于:泰然论坛 (译)如何使用 cocos2d 来制作一个塔防游戏:第一部分 整理:风云永杰(泰然论坛管理组) 著作权声明:本文由 子龙山人 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声 明和作者博客链接,谢谢!首发于泰然论坛 原文链接地址: http://www.iphonegametutorials.com/2011/04/11/cocos2d-game-tutorial-how-to -build-a-tower-...
(译)如何使用cocos2d来制作一个塔防游戏:第一部分
版权属于:子龙山人 首发于:泰然论坛 (译)如何使用 cocos2d 来制作一个塔防游戏:第一部分 整理:风云永杰(泰然论坛管理组) 著作权声明:本文由 子龙山人 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声 明和作者博客链接,谢谢!首发于泰然论坛 原文链接地址: http://www.iphonegametutorials.com/2011/04/11/cocos2d-game-tutorial-how-to -build-a-tower-defense-game-for-the-iphone-part-1-creep-waves/ 教程截图: 我们做到了!在第一篇教程中,我们已经花时间讨论了最终要完成一个什么样的作品。现在, 让我们开始写代码吧。对任何塔防游戏来说,第一步就是创建“爬行怪(Creeps)”。这些怪物 会入侵你的塔防世界,你需要把它们击退。因此,我们将在这个教程里学些什么东西呢?因为, 这只是教程的第一部分,所以不可能全部涉及到,下面是我们将要实现的功能列表:  怎么制作 waypoint,其实就是敌人沿着固定路径的点.  如何加载一个 tile map,并能从中加载对象,而不是硬编码那些对象。  怎么创建爬行怪(Creep)/坏家伙(Bad Guy))/敌人(Enemy)对象。  怎样使敌人沿着我们预先设定好的路径行走。  怎样平滑地滚动 iphone 屏幕。 如果没有上述这些功能特性,这个游戏就不能称之为一个合格的塔防游戏。首先,我将向大 家展示,如何创建一个 enemy,并让它沿着预先设定好的路径行走,具体方式就是沿着一系列 的 waypoint 前进。到这个系列教程结束的时候,你将拥有制作塔防游戏的全部知识了,尽情 发挥,去创造更加好玩的 TD 吧! 这里有本教程的完整源代码。 一个“waypoint”在维基百科里面被定义为“一组坐标集合,它标识了物理空间的一个点”。 我喜欢这个定义,所以我就使用它啦。我们可以把一个舞台想像成一个物理空间,而 waypoint 的位置就是舞台上面的 x,y 值。 我们会在舞台上创建一系列的 waypoint,然后让敌人沿着这些 waypoint 移动,直到行进 至终点!听起来很复杂?其实并不是很复杂。现在,为了让我们的塔防游戏起点更高一些,我们 将使用 Tiled 地图来做舞台,你可以从 http://www.mapeditor.org 下载 tile 地图编辑器。 版权属于:子龙山人 首发于:泰然论坛 当然,我们也需要下载 cocos2d,从 http://www.cocos2d-iphone.org/可以下载。这两个 工具都会帮助我们完成一些让人感到很自豪的事,并且可以充分利用 iphone 的一些特性。 好,你之前已经下载本教程源代码了,你可能看到我们写了一大堆的类。大部分都比较清楚 明了,下面是它们的列表以及功能说明:  TowerDefenseTutorialAppDelegate – 创建窗口,加载 CCDirector,同时加载第一 个 Scene。  RootViewController – 从 UIViewController 继承而来,我们可以使用它方便地改变 视图的朝向(orientation)  GameConfig – 目前只定义了和视图朝向相关的一些变量。  TutorialScene – 我们主要的视图对象,负责加载地图并且设置 creep 的位置  DataModel –一个简单的数据接口,存储了游戏的主要数据,方便查找用。  Creep – 游戏中的坏蛋,刚开始有两个,随着游戏的进行,会越来越多。  Waypoint –可以和 tile map 编辑器发生联系的类,跟预定义路径有关。  Wave – 控制某一个时刻 Creep 出现的顺序的类。 看起来好像有好多类,但是,1,2,3 都是 cocos2d 自带的类,而 Waypoint 和 Wave 目前为止,它们的功能也非常简单。实际上,Waypoint 只是从 CCNode 继承而来,它们只需 要 x,y 值就行了,从 tile map 中读取。 如果你们不相信我的话,可以打开 Waypoint 类的头文件和实现文件看一看,下面是它们 的定义: Waypoint.h: #import "cocos2d.h" @interface WayPoint : CCNode { } @end Waypoint.m: #import "WayPoint.h" @implementation WayPoint - (id) init { if ((self = [super init])) { } return self; } @end 对于 DataModel 类,只要是对 NSMutableArray 熟悉的人都会觉得很简单,让我们直接 看看代码吧: #import "cocos2d.h" 版权属于:子龙山人 首发于:泰然论坛 @interface DataModel : NSObject { CCLayer *_gameLayer; NSMutableArray *_targets; NSMutableArray *_waypoints; NSMutableArray *_waves; UIPanGestureRecognizer *_gestureRecognizer; } @property (nonatomic, retain) CCLayer *_gameLayer; @property (nonatomic, retain) NSMutableArray * _targets; @property (nonatomic, retain) NSMutableArray * _waypoints; @property (nonatomic, retain) NSMutableArray * _waves; @property (nonatomic, retain) UIPanGestureRecognizer *_gestureRecognizer;; + (DataModel*)getModel; @end 所以,这里大部分代码都是很直白的。DataModel 是一个单例的类,符合 NSCoding 协议。 我们这样做有两个原因:其一,我们这样做的目的是用来保存之后游戏的状态,其二,我们把它 做成单例是因为整个游戏中,我们只想让一个 DataModel 对象存在。我们可以从任何类中访问 DataModel,只需要包含相应的头文件,然后调用下面的方法就行了: DataModel *m = [DataModel getModel]; 下面是单例的具体实现: +(DataModel*)getModel { if (!_sharedContext) { _sharedContext = [[self alloc] init]; } return _sharedContext; } 我们也保存了游戏里面所有的主要角色--“targets”是我们的缓慢爬行的敌人, “waypoints”是敌人要沿着走的路径点,而“waves“则存储 wave 类,wave 类包含了已经出了 多少个敌人了,出现敌人的速度是多少等等。 那么 UIPanGestureRecognizer 和 CCLayer 对象呢?呃,CCLayer 是指向 game layer 的一个引用,所有的游戏逻辑都在这个层里面发生。这里保存一个引用的话,你在其它类中可以 非常方便地访问到主 GameScene。而 UIPanGestureRecognizer 类是用来实现平滑地滚动 iphone 屏幕用的。因为塔防游戏不能局限于 480×320 的范围,经常需要滑动地图。有了这个 类,我们就可以定义任何大小的地图了。 现在,我们已经消除了对上面给出的这么多类的恐惧了。那么具体代码看起来怎么样呢。首 先,让我们来看看”坏人“吧!我们已经知道”Wave“和”DataModel“类是干嘛用的了,这两个类 对大家来说应该不会陌生了。先看看 Creep 的代码: 版权属于:子龙山人 首发于:泰然论坛 #import "cocos2d.h" #import "DataModel.h" #import "WayPoint.h" @interface Creep : CCSprite { int _curHp; int _moveDuration; int _curWaypoint; } @property (nonatomic, assign) int hp; @property (nonatomic, assign) int moveDuration; @property (nonatomic, assign) int curWaypoint; - (Creep *) initWithCreep:(Creep *) copyFrom; - (WayPoint *)getCurrentWaypoint; - (WayPoint *)getNextWaypoint; @end @interface FastRedCreep : Creep { } +(id)creep; @end @interface StrongGreenCreep : Creep { } +(id)creep; @end 我们创建了一个 creep 类,里面定义了生命值,移动速度和当前处于地图上的哪个点。这 里包含了我们目前为止需要了解的全部信息。我们还定义了其他两种类型的 creep,因为,哪个 塔防游戏没有不同类型的敌人呢?有一个快速移动的红色 creep 和一个行动缓慢,但是生命值 很多的 creep---我们还可以添加更多其它类型的 creep 类型,但是,这里为了简单,我们只实 现这 3 种。 现在,因为我们已经看到头文件了,我想你肯定想知道实现文件是什么样的。但是,也是考 虑简单的因素,目前我只向你展示那些对我们来说比较重要的内容。首先,让我们看看,具体 creep 类是怎么实现的: @implementation FastRedCreep + (id)creep { FastRedCreep *creep = nil; if ((creep = [[[super alloc] initWithFile:@"Enemy1.png"] autorelease])) { 版权属于:子龙山人 首发于:泰然论坛 creep.hp = 10; creep.moveDuration = 4; creep.curWaypoint = 0; } return creep; } 这就是我们怎么实现 creep 的-我们只定义了一个类方法,可以用 “[FastRedCreep creep]”的方式来调用,调有之后会返回一个 creep 对象,然后我们就可以把它加到 scene 里 面去,并让它工作了。因为,Creep是从CCSprite派生出来的,所以我们可以自动获得 CCSprite 的所有好处。当然,你也可以从 CCNode 派生,然后里面包含一个 CCSprite 的引用。具体是 从 CCSprite 派生还是 CCNode,这两者都各有利弊。(我本人喜欢从 CCNode 继承,因为符 合”优先使用组合而不是继承“的面向对象原则,但是,有时候,为了使之能加到 CCSpriteBatchNode 里面去,而选择继承 CCSprite,反正各有好处,大家自己去权衡)。 接下来,在 Creep 类中,我们要用到 DataModel 类和 WayPoint 类,先看下面代码: - (WayPoint *)getCurrentWaypoint{ DataModel *m = [DataModel getModel]; WayPoint *waypoint = (WayPoint *) [m._waypoints objectAtIndex:self.curWaypoin t]; return waypoint; } - (WayPoint *)getNextWaypoint{ DataModel *m = [DataModel getModel]; int lastWaypoint = m._waypoints.count; self.curWaypoint++; if (self.curWaypoint > lastWaypoint) self.curWaypoint = lastWaypoint - 1; WayPoint *waypoint = (WayPoint *) [m._waypoints objectAtIndex:self.curWaypoin t]; return waypoint; } 这里定义了 creep 的获得当前位置点的方法,还有得到下一个行进点的方法。你可以看到 两处同样的 “WayPoint *waypoint = (WayPoint *) [m._waypoints objectAtIndex:self.curWaypoint];” 调用,它调用 DataModel 类来查找并返回一 个”curWayPoint“所指示的 WayPoint 对象。当我们想走下一步的时候,我们就递 增”curWaypoint”的值,然后看它是否超过数组的最大值。如果是,则减 1.然后从 DataModel 类中查找出具体的 WayPoint。这样子可以循环获得 waypoint。这样的话,在还没有塔的情况 下,我们的 creep 会一波接一波的循环进攻。 creep 创建之后并能够前进的代码在 TutorialScene 类中,如下所示: -(void)FollowPath:(id)sender { 版权属于:子龙山人 首发于:泰然论坛 Creep *creep = (Creep *)sender; WayPoint * waypoint = [creep getNextWaypoint]; int moveDuration = creep.moveDuration; id actionMove = [CCMoveTo actionWithDuration:moveDuration position:waypoint.p osition]; id actionMoveDone = [CCCallFuncN actionWithTarget:self selector:@selector(Fol lowPath:)]; [creep stopAllActions]; [creep runAction:[CCSequence actions:actionMove, actionMoveDone, nil]]; } 基于我们前面所讨论过的,这里面的代码应该比较容易懂。但是,这里面执行动画的方法, 如果你之前没有看到一些教程的话,可能会觉得有点陌生。在 AddTarget 方法被调用之后,一 个 creep 对象被创建了。上面这个函数会重复地调用自身。它不断地判断“sender”参数,这个 参数在任何情况下都等于 creep 对象,因为是 creep 对象 run 的 action。得到 creep 对象之 后,就计算得到下一个 waypoint。这时,我们让 creep 运行两个 action,从当前点移动到下 一点,并且在移动结束后又递归调自身。“MoveTo”action 把精灵从一个(x,y)点变换到目 标点的(x,y)处。 我们将要涉及到的大部分内容都在“TutorialScene”类中。它的头文件目前还比较干净,但 是,它需要和我们的 txm 文件地图系统关联起来,因此,定义成下面的样子: #import "cocos2d.h" #import "Creep.h" #import "WayPoint.h" #import "Wave.h" // Tutorial Layer @interface Tutorial : CCLayer { CCTMXTiledMap *_tileMap; CCTMXLayer *_background; int _currentLevel; } @property (nonatomic, retain) CCTMXTiledMap *tileMap; @property (nonatomic, retain) CCTMXLayer *background; @property (nonatomic, assign) int currentLevel; + (id) scene; - (void)addWaypoint; @end 版权属于:子龙山人 首发于:泰然论坛 现在,如果你从来没有使用过 tile,那么因特网有已经有很多优秀的教程教你怎么用了。我 推荐大家看我翻译 Ray 的 tile map 教程。同时,还请查看 Ray 的 “Tom the Turret”教程, 你可以从这里找到。 下面是 TutorialScene 的 init 方法: // on "init" you need to initialize your instance -(id) init { if((self = [super init])) { self.tileMap = [CCTMXTiledMap tiledMapWithTMXFile:@"TileMap.tmx"]; self.background = [_tileMap layerNamed:@"Background"]; self.background.anchorPoint = ccp(0, 0); [self addChild:_tileMap z:0]; [self addWaypoint]; [self addWaves]; // Call game logic about every second [self schedule:@selector(update:)]; [self schedule:@selector(gameLogic:) interval:1.0]; self.currentLevel = 0; //Center the tile layer so we get the best possible starting view self.position = ccp(-228, -122); } return self; } 我们加载并保存了新创建的“CCTMXTiledMap”对象,然后在第四步的时候加到游戏层里面 去了。然后,调用“addWayPoint”方法,下面会有详细说明。同时,还调用了“addWaves”方 法,这里我们设定的游戏总共有 2 波。 然后,我们使用 scheduler 来做游戏主循环,更新游戏逻辑,下面有阐述。最后,我们把 当前的关卡设置为 0,并且把 layer 的位置移动到一个位置好的视角。 现在,我们需要看看真正有意思的代码了。打开下载工程 resource 文件夹下面的.tmx 文 件。记住,你可以从 mapeditor.org 下载地图编辑器。(你也可以从我的博客翻译的 Ray 的 tiled map 教程里面找到下载链接,我上传的是 java 版的,qt 版的貌似下载不到了) 好,开始工作---我们不能把教程搞得 60 页长。所以,上面这个图我特意把它缩小了,这 样我们的教程看起来就很短啦:)。这里面定义了我们的 creep 将要行走的路径。因为,它太 版权属于:子龙山人 首发于:泰然论坛 小了,我们可能看不出什么东西来,所以直接打开.tmx 文件,看看里面有些什么对象。(就是 上图中的灰色矩形,很小的,在路径的每个拐弯处) 现在,这里坐标点倒底能干什么呢?在解释之前,先让我们看一看“addWaypoint”方法: -(void)addWaypoint { DataModel *m = [DataModel getModel]; CCTMXObjectGroup *objects = [self.tileMap objectGroupNamed:@"Objects"]; WayPoint *wp = nil; int wayPointCounter = 0; NSMutableDictionary *wayPoint; while ((wayPoint = [objects objectNamed:[NSString stringWithFormat:@"Waypoin t%d", spawnPointCounter]])) { int x = [[wayPoint valueForKey:@"x"] intValue]; int y = [[wayPoint valueForKey:@"y"] intValue]; wp = [WayPoint node]; wp.position = ccp(x, y); [m._waypoints addObject:wp]; wayPointCounter++; } NSAssert([m._waypoints count] > 0, @"Waypoint objects missing"); wp = nil; } 我们将遍历 TMX 文件中所有的对象,然后把相应的数据拿出来!每一个对象都被命名为 “WayPoint#”,因为这个顺序,所以加载进行非常方便。然后,我们创建一个 WayPoint 类, 并且设置它的位置,然后把它加到 DataModel 的_waypoints 数组中去,方便后来查找。 好,那你又是怎么加载 creep 的呢?容易吗?你看看吧: -(void)addTarget { DataModel *m = [DataModel getModel]; Wave * wave = [self getCurrentWave]; if (wave.totalCreeps < 0) { 版权属于:子龙山人 首发于:泰然论坛 return; //[self getNextWave]; } wave.totalCreeps--; Creep *target = nil; if ((arc4random() % 2) == 0) { target = [FastRedCreep creep]; } else { target = [StrongGreenCreep creep]; } WayPoint *waypoint = [target getCurrentWaypoint ]; target.position = waypoint.position; waypoint = [target getNextWaypoint ]; [self addChild:target z:1]; int moveDuration = target.moveDuration; id actionMove = [CCMoveTo actionWithDuration:moveDuration position:waypoint.p osition]; id actionMoveDone = [CCCallFuncN actionWithTarget:self selector:@selector(Fol lowPath:)]; [target runAction:[CCSequence actions:actionMove, actionMoveDone, nil]]; // Add to targets array target.tag = 1; [m._targets addObject:target]; } 当 addTarget 被调用的时候,我们首先获得当前的波数,然后判断是否结束。然后,我们 随机产生一个“Fast Creep”或者是一个“Strong Creep”,然后基于第一个 waypoint 来设置它 的位置。(你应该记得,如果 curWayPoint 是 0 的话,那么就会得到 tmx 文件中的 Waypoint0 所代表的位置)。最后,我们把对象 tag 设置为 1,然后把它添加到 DataModel 里去。 但是,谁来调 addTarge 方法呢?好吧,在下面的 scheduler 方法中调用: -(void)gameLogic:(ccTime)dt { DataModel *m = [DataModel getModel]; Wave * wave = [self getCurrentWave]; static double lastTimeTargetAdded = 0; double now = [[NSDate date] timeIntervalSince1970]; if(lastTimeTargetAdded == 0 || now - lastTimeTargetAdded >= wave.spawnRate) { [self addTarget]; lastTimeTargetAdded = now; } 版权属于:子龙山人 首发于:泰然论坛 } - (void)update:(ccTime)dt { // Doesn't do anything... for now... } 因此,目前”gameLogic“决定什么时候添加一个新的 target,考虑的因素就 是”spawnRate“,也就是怪物出现的频率。我们的 update 方法这里只是列出来,并没有实现, 因为暂时还不需要用到。 最后,我们已经完成很多东西了,把本教程的大部分内容也涉及到了,但是,并没有列出所 有的代码。还有一些事情得交待清楚。。。对,是 UIPanGestureRecognizer。 刚开始,我想把这个教程的屏幕限制在 480×320 的范围内,但是,后来一想,有哪个塔 防游戏,它的屏幕是不动的呢?那样玩起来还有什么意思呢?你可以自己再制作一些 tile map, 只要比屏幕大就行。滚动屏幕的代码如下所示: - (CGPoint)boundLayerPos:(CGPoint)newPos { CGSize winSize = [CCDirector sharedDirector].winSize; CGPoint retval = newPos; retval.x = MIN(retval.x, 0); retval.x = MAX(retval.x, -_tileMap.contentSize.width+winSize.width); retval.y = MIN(0, retval.y); retval.y = MAX(-_tileMap.contentSize.height+winSize.height, retval.y); return retval; } boundLayerPos防止移动屏幕的时候超出地图边界。如果你把 tileMap的 size从27×20 改成 50×50 的话,代码还是一样可以工作的。我的意思是,你可以改变你的 waypoints,这 样的话,creeps 就不仅仅出现在屏幕地图的中间,还可以出现在别的地方。下面是识别手势的 代码: - (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateBegan) { //Not used, but included for now CGPoint touchLocation = [recognizer locationInView:recognizer.view]; touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation]; touchLocation = [self convertToNodeSpace:touchLocation]; } else if (recognizer.state == UIGestureRecognizerStateChanged) { // We have recognized a change in the gesture on the screen. CGPoint translation = [recognizer translationInView:recognizer.view]; translation = ccp(translation.x, -translation.y); CGPoint newPos = ccpAdd(self.position, translation); self.position = [self boundLayerPos:newPos]; [recognizer setTranslation:CGPointZero inView:recognizer.view]; 版权属于:子龙山人 首发于:泰然论坛 } else if (recognizer.state == UIGestureRecognizerStateEnded) { // We have finished the gesture - run a CCMoveTo action based on the velocity o f the swipe float scrollDuration = 0.2; CGPoint velocity = [recognizer velocityInView:recognizer.view]; CGPoint newPos = ccpAdd(self.position, ccpMult(ccp(velocity.x, velocity.y * - 1), scrollDuration)); newPos = [self boundLayerPos:newPos]; [self stopAllActions]; CCMoveTo *moveTo = [CCMoveTo actionWithDuration:scrollDuration position:newPo s]; [self runAction:[CCEaseOut actionWithAction:moveTo rate:1]]; } } 最后这一部分的代码就是告诉你如何识别 swipe 手势的开始,改变和结束。如果你不使 用”boundLayerPos“方法的话,你可能会很轻松地把 layer 划出屏幕之外去。目前,我们并没 有使用 UIGestureRecognizerStateBegan 状态,但是,我们用了 UIGestureRecognizerStateChanged 状态来确保 layer 在制定边界范围之内。最后,在 UIGestureRecognizerStateEnded 状态里面,我们使用手势的速度和 cocos2d 的 CCMoveTo 方法,就可以很容易地滚动屏幕了。 因此,我们学到了什么呢?  怎么制作 waypoint,其实就是敌人沿着固定路径的点.  如何加载一个 tile map,并能从中加载对象,而不是硬编码那些对象。  怎么创建爬行怪(Creep)/坏家伙(Bad Guy))/敌人(Enemy)对象。  怎样使敌人沿着我们预先设定好的路径行走。  怎样平滑地滚动 iphone 屏幕。 我们还没有涉及到的有:  我们怎么处理 creep 的旋转,主要是在它行进过程中改变面朝方向。  当我们到达 waypoints 的尾部的时候,该怎么做呢? 上面这两个问题,会在下一个教程中予以解答。 译者的话:本人水平有限,翻译有误或不准的地方,望不吝指出,谢谢! 著作权声明:本文由 http://www.cnblogs.com/andyque 翻译,欢迎转载分享。请尊重 作者劳动,转载时保留该声明和作者博客链接,谢谢!
/
本文档为【(译)如何使用cocos2d来制作一个塔防游戏:第一部分】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索