ipad 打地鼠游戏

kuailehapp 贡献于2012-12-23

作者 Administrator  创建于2012-08-08 06:21:00   修改者Administrator  修改于2012-08-08 06:23:00字数21854

文档摘要: 在iOS Programming 101课程中,我的一个学生提议,让我写一个教程,关于如何使用cocos2d来制作一个打地鼠的游戏。1.目前,我的博客上有许多关于cocos2d的教程了,但是,却没有一个教程把制作一个游戏相关的所有细节都放在一起来开发一个游戏。因此,这个教程应运而生了。 2.这是一个非常好的机会来介绍一个新的话题:如何制作一个游戏,让它同时在iphone、ipad和支持高清(retina)显示的iphone上面运行。
关键词:

ipad打地鼠游戏 子龙山人 Learning,Sharing,Improving! (译)如何使用cocos2d来制作一个打地鼠的游戏:第一部分 免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!   原文链接地址:http://www.raywenderlich.com/2560/how-to-create-a-mole-whacking-game-with-cocos2d-part-1 教程截图:   在iOS Programming 101课程中,我的一个学生提议,让我写一个教程,关于如何使用cocos2d来制作一个打地鼠的游戏。    我认为这是一个非常好的主意,原因有三: 1. 目前,我的博客上有许多关于cocos2d的教程了,但是,却没有一个教程把制作一个游戏相关的所有细节都放在一起来开发一个游戏。因此,这个教程应运而生了。 2. 这是一个非常好的机会来介绍一个新的话题:如何制作一个游戏,让它同时在iphone、ipad和支持高清(retina)显示的iphone上面运行。 3. 当然,最重要的原因是--打地鼠本来就很有趣!   这个教程建立在下列教程的基础之上: · 如何使用cocos2d来制作一个简单的iphone游戏 · 如何在cocos2d里面使用动画和spritesheet · 在cocos2d里面如何使用Texture Packer和像素格式来优化spritesheet  · 在cocos2d里面如何拖拽精灵   如果你还没有阅读上面这些教程,我强烈建议你先读一读。   这是一个两部分的教程。在第一部分中,我们将会创建基本的游戏框架--有一些可爱的地鼠从洞里面钻出来。我们将会花费大量的时候来讨论如何组织图片资源和坐标计算,以便于你的游戏能够同时运行在iPhone3GS、ipad和高清显示的iphone(其实是就是iphone4)上面,同时还要保证游戏性能! 规划图片资源: 总览   因为,我们想让这个应用能够同时运行在iphone、retina-display iphone和ipad上面,所以,我们需要花点时间来仔细考虑一下,如何制作图片,主要是图片的尺寸!   为了理解到底多大的尺寸合适,首先,我们要先讨论下面三个主题: · Retina Display and UIKit · Retina Display and Cocos2D · iPad, iPhone, and Aspect Ratios Retina Display and UIKit   普通的iphone和支持高清显示的iphone之间的差别就是支持高清显示的iphone能够显示2倍的像素。因此(在landscape模式下),普通的iphone显示480×320个像素,而支持高清的iphone则可以显示960×640个像素。   “但是,请等一下”,你可能会问,“如果显示2倍数量的像素,那么不会破坏那些已经写好的假设屏幕大小只有480×320的应用程序吗?” 没错,特别是当你使用UIKit编程的时候,通常都是硬编码frame的大小。但是,如果你在UIKit里面指定frame大小的时候,实际的单位是点,而不是像素。   在一个普通的iphone上面(不支持retina display),一个点就等于一个像素。但是,在支持retina的iphone上面,一个点等于2个像素。因此,当你指定(10,10),在普通的iphone上面也是(10,10),而在支持retina的iphone上面,则是(20,20),因此,那会是同样的相对偏移量。(因为,像素加倍了,相对于图片来说,相对偏移量相等)。   当你使用apple的控件或者Core Graphics,Apple已经写了一些代码来确保在Retina显示的时候没有问题。(因为你写代码指定大小都是point为单位)   唯一的需要注意的就是使用图片的时候。比如你在一个iphone程序中使用了一张200×200的图片,如果你什么也不做,那么retina显示会把这张图的尺寸放大2倍--那样会很难看,如下图所示,会模糊。   因此,你所需要做的就是为所有的图片提供两种版本,一种普清的,一种高清的。如果在高清图片名字后面增加一个@2x后缀(比如sample@2x.png),那么不管什么时候,你只要调用[UIImage imageNamed:...]或者一些更简单的api,当程序编译到支持高清设备的时候,就会自动加载后缀为@2x的图片。   因此,制作一个支持Retina显示的UIKit程序是非常容易的,只需要为每一张普清图片同时提供一个高清版本(2倍像素),并且指定为@2x后缀即可。   那么cocos2d里面该如何做呢? Retina Display and Cocos2D   告诉你们一个好消息--最新版本的cocos2d完全支持retina显示,而且使用这个功能只需要下列1-2-3步: 1. 调用CCDirector的函数enableRetinaDisplay来开启retina显示支持。如果你使用cocos2d模板,那么只需要把app delegate里面对这句函数调用的注释去掉即可! 2. 往你的程序中添加2倍大小的精灵图片,但是,这里命名不是增加@2x后缀,而是使用-hd(如:sample-hd.png)。当你加载精灵的时候,使用不带-hd后缀的图片名字,当cocos2d被部署到支持retina显示的设备上时,就会自动加载高清的图片。 3. 当你在cocos2d里面设置精灵坐标的时候,使用的是点,而不是像素。注意:有些api使用的是像素,这些方法中都包含pixel字样,比如position和positionInPixel。InPixels;   因此,你只需要让美工提供给你高清格式的图片就行了,你可以很容易地使用软件把这些高清图片变得普清图片。比如TexturePacker就可以做这样的事。   你可能会奇怪,为什么需要准备两种不同格式的图片--为什么不每次都加载高清的图片,然后当需要普清图片的时候,用程序来缩放?好吧,因为把纹理(texture)加载到内存里面是非常消耗内存的操作,如果某个程序被安装在不支持高清显示的设备上面(比如IPOD2),那么你就会占用两倍大小的内存,当然是相比于只加载普清的图片的程序。(因为高清图片像素个数多一倍,所以必须消耗的内存要大。正确的做法就是前面说的,提供两套图片。)   不用担心,你没有必要在photoshop里面不停地缩放图片的尺寸。TexturePaker里面有一个非常好的功能,可以很容易地通过高清图片得到对应的普清图片,这种方法在后面的教程中会讲到。 iPad, iPhone, and Aspect Ratio   好,现在处理iphone上面的高清和普清显示非常简单了(准备两套图片),但是,iPad呢?该怎么做呢?   呃,要想编写一个游戏,让它同时能够在iphone和ipad上面运行似乎是一件非常烦人的事--因为设备的纵横比(aspect ratio)不一样!   iphone支持480*320或者960*640---因此是1.5的纵横比。然后,iPad是768*1024的--纵横比等于1.33.   这意味着,如果你有一张图片,能够完全覆盖iPad的背景(768*1024),如果你也想在iPhone里面继续使用它。那恐怕不会匹配那么好。比如,你把它缩小,让宽度等于iphone的宽度(通过乘以0.9375):你会得到720*960,因此,还是会有一部分图像不能显示!   这让事情变得有点恼火了,因为,不只是背景图片的问题,因为纵横比的不同,原先设置节点使用的坐标就不能够再使用了。   目前有一些方法可以解决这个问题,下面列举了一些我看过的、听过的和使用过的(如果你们有更好的方法,可以留言): · 考虑一个“可玩区域”,这个区域的大小等于640*960,同时把它放置在iPad的屏幕中间。这会使得游戏区域外面有一些空白--但是,你还可以放置一张位置不动、颜色相同的背景图片,那么玩家在玩游戏的时候就很难发现了。这样会使你很容易地在不同的设备之间使用相同的坐标值,并且还可以重用图片资源。这个方法也是我们这个教程里所使用的方法。 · 你可以使你的iPad的程序的纵横比与iPhone的纵横比一样,在iPad屏幕的边界留出42个像素,把“主要内容”放置在685*1024的范围中。如果,你可以把你的应用中的内容放置在684*1024的“盒子”中,那么你就可以为每一个设备使用缩放图片了。 · 你可以为iPhone、iPad和Retina display的iPhone提供不同的图片和坐标值。这样的灵活性是最大的,但是,会产生更大的二进制程序文件,并且不同的设备上面的对象位置都需要重新计算。   另一个原因,cocos2d目前对iPad的支持并不是很友好。只是可以自动加载后缀为“-hd”的图片,转换坐标的工作还是得由你来做。 规划图片资源:结论   好,基于上面的讨论,下面是这篇教程的图片资源规划: · 图片被设计成一个960*640的“可玩区域”大小,在高清的iphone上面就是全屏的,在iPad上面就会居中显示。 · 高清图片可以通过TexturePacker转换成普清的,并且生成相应的spritesheet。 · 高清的图片将以“-hd”作为后缀,而普清的图片则没有。cocos2d会根据当前是否支持高清显示而加载相应的图片。 · 背景是一个特例,因为它总是需要全屏。背景将会做成1024*768的大小(因为iPad屏幕的大小是1024*768)。当这张图片用作iPhone上面的时候,会有一部分图片会超过屏幕边界,但是,在这个教程中,并没有关系。(后面将会看到,背景颜色很单一) · iPad版本版本将会包含一些代码,用来加载“-HD”后缀的图片,转换坐标并设置到“可玩区域”内,使用合适的字体大小等。   首先,下载这篇教程的图片资源,解压,然后看看里面有什么: · 在“foreground”文件夹中,前景大小为1024*768(也就是iPad的大小),但是,它实际上被划分为两个部分:上半部和下半部。因为我们将在这两个部分当中放置三个“地鼠洞“,这样就会看起来,地鼠真的是从地底下钻出来的。 · 在”background“文件夹中,iPad的纵横比是1.33,所以一半是512*384.这是因为,背景实际上是看不到的,只有通过3个地鼠洞才可以看到背景。这么小的范围,完全没有必要加载一张1024*1024的图片。相反,我们加载一张小图片,然后放大。 · 在"sprites”文件夹中,所有的精灵都被设置成可以放到960*640的“可玩区域”内。注意,还有一个地鼠图片,二个地鼠动画(一个笑的动画,一个被打中时的动画)。   好了,有足够的背景信息了--是时候开始动手了!、 Getting Started   打开XCode,选择“File\New Project...",然后选择“User Templates\cocos2d\cocos2d Application”,再点击”Choose...“。把工程命名为WhackAMole,单击Save。   接下来,把你刚刚下载下来的图片资源文件夹全部拷到工程目录下面。它的位置应该是Classes文件夹的兄弟,如下图所示:   接下来,确保你已经安装了TexturePacker。如果你还不知道怎么使用它,可以查看译者翻译的另一篇教程。   你可以打开TexturePacker工具,并为这个项目创建所有的sprite sheet。你也可以使用TexturePacker的命令行工具,它可以和XCode集成,并且处理起来更加高效!   右击Resources,选择”Add\New File...“,选择Mac OS X\Other\Shell Script,再点Next。取名为PackTextures.sh,然后点Finish。   然后把PackTextures.sh里面的内容替换成下面的代码: #!/bin/sh TP="/usr/local/bin/TexturePacker" if [ "${ACTION}"="clean" ] then echo "cleaning..." rm resources/background* rm resources/foreground* rm resources/sprites* else echo "building..." ${TP} --smart-update \ --format cocos2d \ --data resources/background-hd.plist \ --sheet resources/background-hd.pvr.ccz \ --dither-fs \ --opt RGB565 \ Art/background/*.jpg ${TP} --smart-update \ --format cocos2d \ --data resources/background.plist \ --sheet resources/background.pvr.ccz \ --dither-fs \ --scale 0.5 \ --opt RGB565 \ Art/background/*.jpg ${TP} --smart-update \ --format cocos2d \ --data resources/foreground-hd.plist \ --sheet resources/foreground-hd.pvr.ccz \ --dither-fs-alpha \ --opt RGBA4444 \ Art/foreground/*.jpg ${TP} --smart-update \ --format cocos2d \ --data resources/foreground.plist \ --sheet resources/foreground.pvr.ccz \ --dither-fs-alpha \ --scale 0.5 \ --opt RGBA4444 \ Art/foreground/*.jpg ${TP} --smart-update \ --format cocos2d \ --data resources/sprites-hd.plist \ --sheet resources/sprites-hd.pvr.ccz \ --dither-fs-alpha \ --opt RGBA4444 \ Art/sprites/*.jpg ${TP} --smart-update \ --format cocos2d \ --data resources/sprites.plist \ --sheet resources/sprites.pvr.ccz \ --dither-fs-alpha \ --scale 0.5 \ --opt RGBA4444 \ Art/sprites/*.jpg fi exit 0     这个脚本运行TexturePacker来为背景图片、前景图片和精灵图片创建相应的精灵表单(sprite sheet)--同时包含高清的和普清的。   注意,每一个图片都被保存为pvr.cca格式,因为,这是一种磁盘占用空间小,并且内存消耗也少的图片格式。当然,像素格式和抖动选项也被设置了,这是一个折中,综合考虑内存消耗和图片显示质量。   如果你对TexturePacker的这些选项的功能不是很清楚,你可以打开终端(Terminal),然后输入TexturePacker -help,那么就会有完整的选项功能列表。   接下来,你需要做一些设置,在你的工程每次编译的时候,可以运行这个shell脚本。右键点击Targets,选择“Add\New Target...",再选择"External Target”(注意不要选择Shell Script Target!),然后点击Next。把这个Target命名为TexturePacker并点击Finish。   然后右键点击刚刚建立的这个Target,修改它的设置,如下图所示:   最后一步,就是为你的程序设置target依赖。双击项目Target,然后找到General标签,点下面的+号,再从弹出的列表中选择TexturePacker Target,并点击Add Target。   编译你的工程,如果输出结果如下图所示,那么就证明你的配置OK。(注意,上面的这些全是针对XCode3所设置的,XCode4大同小异,只是界面不一样了,原理不变)   接下来,把刚刚生成的精灵表单和相应的属性列表增加到项目中。右击Resources,选择“Add\Existing Files...”,然后选择background,foreground和sprites文件(总共有12个文件),再点Add.如果做完后,那么看起来会是下图这个样子:   如果你愿意,你可以双击任何一个.pvr.ccz文件,打开看看,里面到底有什么。你就会发现TexturePacker有多么方便了! 设置背景   接下来,HelloWorldScene.m文件并找到init方法。删掉自动生成的那4行创建一个label的代码。同时,用下面的代码替换掉: // Determine names of sprite sheets and plists to load NSString *bgSheet =@"background.pvr.ccz"; NSString *bgPlist =@"background.plist"; NSString *fgSheet =@"foreground.pvr.ccz"; NSString *fgPlist =@"foreground.plist"; NSString *sSheet =@"sprites.pvr.ccz"; NSString *sPlist =@"sprites.plist"; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { bgSheet =@"background-hd.pvr.ccz"; bgPlist =@"background-hd.plist"; fgSheet =@"foreground-hd.pvr.ccz"; fgPlist =@"foreground-hd.plist"; sSheet =@"sprites-hd.pvr.ccz"; sPlist =@"sprites-hd.plist"; } // Load background and foreground [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:bgPlist]; [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:fgPlist]; // Add background CGSize winSize = [CCDirector sharedDirector].winSize; CCSprite *dirt = [CCSprite spriteWithSpriteFrameName:@"bg_dirt.jpg"]; dirt.scale =2.0; dirt.position = ccp(winSize.width/2, winSize.height/2); [self addChild:dirt z:-2]; // Add foreground CCSprite *lower = [CCSprite spriteWithSpriteFrameName:@"grass_lower.jpg"]; lower.anchorPoint = ccp(0.5, 1); lower.position = ccp(winSize.width/2, winSize.height/2); [self addChild:lower z:1]; CCSprite *upper = [CCSprite spriteWithSpriteFrameName:@"grass_upper.jpg"]; upper.anchorPoint = ccp(0.5, 0); upper.position = ccp(winSize.width/2, winSize.height/2); [self addChild:upper z:-1]; // Add more here later...     好,让我们一个一个来讲解,因为,这里面有许多新的东西。   1.决定加载的精灵表单和plist文件的名字。这个部分,把TexturePacker生成的精灵表单和plist文件,按照某种方式加载进来了。注意,在iPhone上面,cocos2d会自动地加载“-HD”和不带“HD”的版本,具体取决于是否激活Retina 显示。然后,在iPad上面,它并不会加载 "-hd"版本,所以你必须手动告诉它加载,因此,上面我们判断,如果是iPad,那么就加载带"-hd"版本的资源。   2.加载背景和前景。接下来就是把背景、前景还有精灵都加载到精灵帧缓冲(sprite frame cache)中,以便后面可以使用。注意,目前精灵并没有加载到CCSpriteBatchNode中。       3.添加背景。背景图片作为当前层的一个孩子被加进去。(Z=-2的话,就可以使任何对象都在它上面,除非其它对象的Z小于-2)。接下来,把这张图片放大两倍,因为,之前,我们为了节约内存,只使用了一半大小的图片。之后,再把图片放置到屏幕中间。   4.前加前景。前景是当作两个部分加上去的。这里用了一种非常简单的设置精灵图片坐标点的方式,通过设置精灵的锚点为图片的中点,或者底部,或者顶部。通过这种方式,假如你要把一张图片的左下角设置为屏幕的左下角,你只需要先把图片的锚点设置为(0,0),然后设置position为0,0就好了。非常方便!而且,这个坐标值,在所有的设备上都通用。注意,背景部分超出屏幕之外了,但是,这并没有关系,因为用户看不到。还要注意的是,图片增加的时候,使用了不同的Z值,最后面的图片使用最小的Z值。   编译并运行,你会看到如下的输出。你可以通过模拟器测试,也可以通过真机测试,看看效果到底如何^^。   如果你尝试在支持高清的设备上运行,你会注意到,图像有模糊,似乎还是使用普清的资源。   这是因为,我们还有一步没有做完:还需要调用CCDirector的enableRetinaDisplay方法能激活高清显示。   为了实现这个目的,打开WhackAMoleAppDelegate.m,然后在applicationDidFinishLaunching方法里面,删除那三句注释: if( ! [director enableRetinaDisplay:YES] ) CCLOG(@"Retina Display Not supported");     编译并运行,现在,再尝试使用支持高清的设备吧,这时就会加载高清图片资源了。 放置地鼠   对于这个游戏来说,你将会在场景中添加3只地鼠--每个洞一个地鼠。地鼠会躲在草的下面,但是,会突然从某个洞里冒出来,这样你就可以打它了。   首先,让我们为每个洞的下面添加一只地鼠。我们会先让它们在所有的图片上面,这样,可以确保位置对了,然后通过修改Z值,就能够把它们隐藏到草地后面去了。   打开HelloWorldScene.h,然后增加一个数组来保存当前关卡的地鼠,如下所示: // Inside @interface HelloWorld NSMutableArray *moles;   通过保存地鼠的数组,我们可以很容易地遍历每一只地鼠。   接下来,在init方法中添加一些代码来放置地鼠(添加位置位于 “Add more here later…”注释后面),如下所示: // Load sprites CCSpriteBatchNode *spriteNode = [CCSpriteBatchNode batchNodeWithFile:sSheet]; [self addChild:spriteNode z:999]; [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:sPlist]; moles = [[NSMutableArray alloc] init]; CCSprite *mole1 = [CCSprite spriteWithSpriteFrameName:@"mole_1.jpg"]; mole1.position = [self convertPoint:ccp(85, 85)]; [spriteNode addChild:mole1]; [moles addObject:mole1]; CCSprite *mole2 = [CCSprite spriteWithSpriteFrameName:@"mole_1.jpg"]; mole2.position = [self convertPoint:ccp(240, 85)]; [spriteNode addChild:mole2]; [moles addObject:mole2]; CCSprite *mole3 = [CCSprite spriteWithSpriteFrameName:@"mole_1.jpg"]; mole3.position = [self convertPoint:ccp(395, 85)]; [spriteNode addChild:mole3]; [moles addObject:mole3];   首先,为精灵创建一个CCSpriteBatchNode,这样就可以更加高效地渲染这三只地鼠了。(每次只需要一个OpenGL call),并把batchNode当作当前层的一个孩子加进去。注意,Z值被临时设置成999,因此,地鼠就会显示在所有的图片之前,那样我们就可以判断地鼠位置是否正确。   然后从精灵帧缓冲中加载所有的精灵。接着,为每一个地鼠创建一个精灵,把它们放置在场景中,同时添加到数组里面。注意,每个地鼠的坐标是在一个480*320的“可玩区域”内(iphone屏幕的大小)。对于iPad来说,这些点需要做一些转换,因此,我们定义了一个辅助函数convertPoint。接下来,在Init方法的上面加入下列代码: - (CGPoint)convertPoint:(CGPoint)point { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { return ccp(32+ point.x*2, 64+ point.y*2); } else { return point; } }   这个方法把“可玩区域”内的一个点转换成合适的iPad屏幕上面的点。记住: · 我们在iPad上面使用的是高清的图像,所有的像素点都要加倍。 · 我们把960*640的区域居中放在1024*768的iPad屏幕上,因此,左右两边要留下32个像素的margin,上下两边要留下64个像素的margin。   因此, 这个方法只是做了一些简单的数学计算,使得坐标在iPad上面不会出问题。   还有一件事---在忘记之前,我们需要做一些内存清理操作,找到dealloc方法,添加下列代码: [moles release]; moles = nil;     编译并运行,你将看到3只地鼠放置在正确的位置上了!你因此同时测试一下iPhone,Retina iPhone和iPad,确保每个设备上看起来都没问题。 弹出地鼠  现在,我们确定地鼠的位置没有问题了,接下来,让我们添加一些代码,让他们能够从地下钻出来吧!   第一件事,把Z值,从999改成0,这样的话,地鼠就会在地底下了。   改完之后,在init方法的后面添加下面的代码: [self schedule:@selector(tryPopMoles:) interval:0.5];   如果之前你没有见过这样的用法,你可以调用其它的方法,比如schedelUpdate。但是,这个例子中,我们只想让地鼠每隔0.5秒钻出一个。   接下来,增加tryPopMoles方法: - (void)tryPopMoles:(ccTime)dt { for (CCSprite *mole in moles) { if (arc4random() %3==0) { if (mole.numberOfRunningActions ==0) { [self popMole:mole]; } } } }     这个方法会每隔0.5秒调用一次,每一次它都会循环判断每一个地鼠,让它有1/3的机会从洞里面钻出来。但是,如果它已经钻出来,也就是运行的action的个数不等于0的话,那么也不会再钻了。 最后,实现popMole:方法: - (void) popMole:(CCSprite *)mole { CCMoveBy *moveUp = [CCMoveBy actionWithDuration:0.2 position:ccp(0, mole.contentSize.height)]; // 1 CCEaseInOut *easeMoveUp = [CCEaseInOut actionWithAction:moveUp rate:3.0]; // 2 CCAction *easeMoveDown = [easeMoveUp reverse]; // 3 CCDelayTime *delay = [CCDelayTime actionWithDuration:0.5]; // 4 [mole runAction:[CCSequence actions:easeMoveUp, delay, easeMoveDown, nil]]; // 5 }     这个代码,使用cocos2d的action来制作弹出洞的效果,先弹出来,然后暂停半秒,再钻回去。让我们一行一行地解释: 1. 创建一个action,让地鼠沿着Y轴钻洞来。因为,之前我们放置地鼠的位置是正确的,所以看起来,就像是从洞下面钻出来一样。 2. 为了使得移动更加真实,上面的action用一个CCEaseInOut action包装起来了。这会得钻洞的动作在开始和结束的时候速度比较慢,看起来,就好像地鼠在加速和减速一样。 3. 创建一个action,使地鼠能够钻回来。这里通过调用action的reverse方法,能得到相反的action. 4. 创建一个action,在地鼠钻出来以后,能够暂停1秒。 5. 现在,action可以运行了。通过用CCSequence把这3个action包装起来,那样它们就可以按顺序执行了。 就这么多!编译并运行代码,你将会看到3只地鼠交替地钻出洞来,cool吧? 何去何从?   本教程完整源代码。   期待这个教程的后续吧,在那个教程里,我们会添加很酷的动画,同时会添加一些游戏逻辑,使得你可以打地鼠并且得分。当然,还有非常好听的音乐。   译者的话:翻译如果有语法错误还忘指出,最近时间比较紧,校对工作没时间仔细做了,欢迎大家不吝指出! 子龙山人 Learning,Sharing,Improving! (译)如何使用cocos2d制作一个打地鼠的游戏:(第二部分。完) 免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作! 原文链接地址:http://www.raywenderlich.com/2593/how-to-create-a-mole-whacking-game-with-cocos2d-part-2 教程截图:   这篇文章是《如何使用cocos2d来制作一个打地鼠的游戏》的第二部分。打地鼠系列教程,里面用到的很多概念和方法是从这个博客的其它教程中拿来的,但是,同时,本系列教程还引入了一些新的概念。   在第一部分教程中,我们创建了一个游戏的基本框架--让可爱地地鼠从洞里面钻出来。我们花费了大量时间来讨论如何规划图片资源及其坐标,以便可以开发出一个游戏,让它同时能够在iPhone、iPad和Retina display的设备上运行--并且要保证尽可能地高效率!   在这篇教程中,我们将会增加一些很酷的动画效果,比如地鼠大笑和被打中时的动画。同时,会增加一些游戏逻辑,以便你能够打击地鼠并且获得相应的分数,当然,还会添加一些非常好听的音乐和音效。   如果你还没有上一个教程的工程,可以从这里下载一份工程拷贝。 定义动画:实用性   为了使游戏变得更有趣,我们将给地鼠增加两个动画。首先,当它从洞里钻出来的时候,它会笑一下(那笑声你绝对会忍不住想打它!)。然后,如果你打中它了,那么你会看到地鼠被打中时的面部表情。   但是,在我们开始之前,先讨论一下代码中如何组织动画。   回想我们之前的教程《如何在cocos2d里面使用spritesheet和动画》,其中,在创建动画过程中,有一个步骤是,创建一系列的精灵帧(sprite frames)。因此,对于你的动画效果中的每一张不同的图片,你必须为之增加精灵帧,如下所示: [animFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"myImage.jpg"]];   我们的地鼠笑的动画将会是下面的一些图片序列:  mole_laugh1.jpg, mole_laugh2.jpg mole_laugh3.jpg, mole_laugh2.jpg, mole_laugh3.jpg, mole_laugh1.jpg.   因此,我们可以硬编码来建立动画,如下所示: [animFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"mole_laugh1.jpg"]]; [animFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"mole_laugh2.jpg"]]; [animFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"mole_laugh3.jpg"]]; [animFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"mole_laugh2.jpg"]]; // And so on...   但是,那会使我们的代码变得非常难看。为了使代码变得更简洁,我们不是直接在代码里面定义这些图片,取而代之的是,我们把它们的名字都存到一个plist文件中。 Property List文件   如果你之前没有使用过plist文件,你需要知道,其实就是一种xml格式的文件。只不过是,它的后缀不一样,同时,它能够被Xcode直接识别,并且可以方便地存储数组,字典、字符串和数字等等。创建这种类型的文件非常方便,当然使用也一样很方便。   让我们看一下,它在Xcode里面是什么样子。右键点击Resources,选择“Add\New File...”,再选择 “Mac OS X\Resource\Property List”,再点击“Next”。把这个文件命名为“laughAnim.plist”,然后点Finish。这时,属性列表文件laughAnim.plist文件的结构应该如下图所示:   每一个属性列表文件(plist文件)有一个根元素。这个根元素要么是一个数组或者一个字典。这里的plist文件将会包含一个组成地鼠笑的动画的一系列图片名字的数组。因此,点击根元素的第二列,(当前默认是Dictionary),我们把它更改成Array。   接下来,点击最右边的小按钮(有三行的那个按钮)--这样会往数组里面增加一个新的实体。默认情况下,这个新添加的实体的类型是String--那也正是我们想要的数据类型。把Item0的名称改成“mole_laugh1.jpg”。   然后再点+号来添加更多的行,重复这个过程,最终的结果如下图所示:   接下来,重复上述的过程来创建地鼠被打中时的动画属性列表文件。按上面所说的,创建一个hitAnim.plist文件,然后把它建立成下图所示的结构:   现在,是时候添加一些代码来加载这些动画了。打开HelloWorldScene.h文件,然后为每一个动画定义一个成员变量,如下所示: // Inside @interface HelloWorld CCAnimation *laughAnim; CCAnimation *hitAnim;   这样做的目的主要是重用,因为可以在init函数里面初使化好这些动画效果,那么在其它的地方就直接可以使用这些动画效果了。(这里需要记住的一点是,游戏里面的任何对象都要事先分配好,在玩家玩游戏的过程中,只需要按照某种规则把它们拿出来即可)。   接下来,基于先前创建的plist文件来创建CCAnimation,如下所示: - (CCAnimation *)animationFromPlist:(NSString *)animPlist delay:(float)delay { NSString *plistPath = [[NSBundle mainBundle] pathForResource:animPlist ofType:@"plist"]; // 1 NSArray *animImages = [NSArray arrayWithContentsOfFile:plistPath]; // 2 NSMutableArray *animFrames = [NSMutableArray array]; // 3 for(NSString *animImage in animImages) { // 4 [animFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:animImage]]; // 5 } return [CCAnimation animationWithFrames:animFrames delay:delay]; // 6 }   理解这个方法非常重要,所以让我们一行一行代码来看。 1. 属性列表文件包含在工程当中,因此,它也在应用程序的“main bundle”中。这个方法会返回main bundle中的文件的完整路径,也就是我们需要读取的plist文件的完整路径。 2. 为了读取一个plist文件,我们调用NSArray的arrayWithContentsOfFile方法,然后把plist文件的完整路径传递给它。这样就会把plist文件中的内容初使化成一个数组。(本例中,这个数组就是一系列图片名字的数组)。注意,这个方法可行,是因为我们把根元素设置成了NSArray。如果我们把它设置成NSDictionary的话,那么我们就要用一个NSDictionary去初使化它。具体的方法就是调用 [NSDictionary dictionaryWithContentsOfFile...] 。 3. 创建一个空的数组来存储这些动画帧。 4. 从plist文件中循环遍历每一张图片并把它存到一个数组中去。 5. 为每一张图片创建一个精灵帧,同时把它加到 animFrames数组里面去。 6. 基于一个精灵帧数组,返回一个CCAnimation对象。   接下来,在init方法的末尾为每一个动画调用这个辅助函数来创建相应的动画: laughAnim = [self animationFromPlist:@"laughAnim" delay:0.1]; hitAnim = [self animationFromPlist:@"hitAnim" delay:0.02]; [[CCAnimationCache sharedAnimationCache] addAnimation:laughAnim name:@"laughAnim"]; [[CCAnimationCache sharedAnimationCache] addAnimation:hitAnim name:@"hitAnim"];     注意,在存储动画对象的引用之后,我们把它们加入到了动画缓存中(animation cache)。这个非常重要,因为我们可以在其他地方很容易地使用引用。(对于laughAnim和hitAnim,不用retain就可以使用了。因为,加入到动画缓存中的时候,CCAnimationCache已经帮你ratain了)。这样做还有一个好处就是,你可以通过CCAnimationCache来获得你想要的动画对象引用,只需要提供动画的名字即可,因为它内部实现是采用的字典。)   最后一步--让我们来使用动画(先只使用笑的动画)。修改popMole方法,如下所示: - (void) popMole:(CCSprite *)mole { CCMoveBy *moveUp = [CCMoveBy actionWithDuration:0.2 position:ccp(0, mole.contentSize.height)]; CCEaseInOut *easeMoveUp = [CCEaseInOut actionWithAction:moveUp rate:3.0]; CCAction *easeMoveDown = [easeMoveUp reverse]; CCAnimate *laugh = [CCAnimate actionWithAnimation:laughAnim restoreOriginalFrame:YES]; [mole runAction:[CCSequence actions:easeMoveUp, laugh, easeMoveDown, nil]]; }     这里唯一的差别就是,在钻出来和钻回去的action中间,我们不是延迟几秒,取而代之的是播放地鼠笑的动画。CCAnimate action使用之前已经创建好的laughAnim,同时设置restoreOriginalFrame为yes。这样的话,当动画结束的时候,它会回到播放动画之前的面貌。   编译并运行代码,现在,当地鼠从洞里钻出来的时候,它会朝着你大笑!是不是想打它?有木有!   是时候让这些地鼠的笑容消失了,让我们开始添加打击逻辑吧! 增加游戏逻辑   现在我们将往游戏中添加一些玩法逻辑。主要就是记录有多少个地鼠钻出来过,还有就是通过打地鼠,你能得到多少分。你会尝试尽可能多地获得分数。   因此,我们将保存分数,并且显示给用户看。当地鼠钻回去的时候,我们也要告诉用户。   所以,再打开HelloWorldScene.h文件,添加下面一些实例变量到HelloWord层中: CCLabelTTF *label; int score; int totalSpawns; BOOL gameOver;     这里保存了一个分数label,当前的分数值,总共钻出来的地鼠数目,以及游戏是否结束。   接下来,在你的init方法的结尾添加下列初始化代码: self.isTouchEnabled = YES; float margin =10; label = [CCLabelTTF labelWithString:@"Score: 0" fontName:@"Verdana" fontSize:[self convertFontSize:14.0]]; label.anchorPoint = ccp(1, 0); label.position = ccp(winSize.width - margin, margin); [self addChild:label z:10];     首先,设置层能够接收到touch事件,因为你想检查用户击打屏幕的消息。然后创建一个label来显示分数。注意,这里把label的锚点设置成右下角,那样可以非常方便地把它放置在屏幕的右下方。   你也要注意到,我们并不是直接传递字体大小,而是通过一个辅助函数来决定字体的大小。这是因为,在iPad上面,字体应该大一些,因为它的屏幕大一些。所以要实现一个convertFontSize方法,如下所示: - (float)convertFontSize:(float)fontSize { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { return fontSize *2; } else { return fontSize; } }     这个非常简单--在iPad上运行的时候,字体大小加倍,否则,就不变。   接下一,我们将添加touch检测代码,来检测用户是否击中一个地鼠。但是,在这之前,我们需要添加一个标记,标记地鼠是否可以击打。因为地鼠应该只有在它朝着你笑的时候才能够被击打,而在它笑完钻回去的时候,你是不能够击打它的。   我们可以创建CCSprite的一个子类来做这个事,但是,因为我们只需要存储一点点信息,所以,我们只需要使用CCSprite的userData属性即可。因此,添加两个辅助方法,并且修改popMole方法,如下所示: - (void)setTappable:(id)sender { CCSprite *mole = (CCSprite *)sender; [mole setUserData:TRUE]; } - (void)unsetTappable:(id)sender { CCSprite *mole = (CCSprite *)sender; [mole setUserData:FALSE]; } - (void) popMole:(CCSprite *)mole { if (totalSpawns >50) return; totalSpawns++; [mole setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"mole_1.jpg"]]; // Pop mole CCMoveBy *moveUp = [CCMoveBy actionWithDuration:0.2 position:ccp(0, mole.contentSize.height)]; CCCallFunc *setTappable = [CCCallFuncN actionWithTarget:self selector:@selector(setTappable:)]; CCEaseInOut *easeMoveUp = [CCEaseInOut actionWithAction:moveUp rate:3.0]; CCAnimate *laugh = [CCAnimate actionWithAnimation:laughAnim restoreOriginalFrame:YES]; CCCallFunc *unsetTappable = [CCCallFuncN actionWithTarget:self selector:@selector(unsetTappable:)]; CCAction *easeMoveDown = [easeMoveUp reverse]; [mole runAction:[CCSequence actions:easeMoveUp, setTappable, laugh, unsetTappable, easeMoveDown, nil]]; }   popMole方法做了如下一些变动: · 在地鼠大笑之前,它通过运行一个CCCallFunc action来调用一个方法setTappable。这个方法会把精灵的userData属性设置成True,表明当前地鼠是可以被击打的。 · 类似的,在地鼠笑完之后,同样运行一个CCCallFunc action来调用unsetTappable方法,把是否可击打的标记又设置回去。 · 只要超过50个地鼠从洞里钻出来后,这个方法就返回,因此,这个游戏的限制就是只出现50个地鼠。 · 在这个方法的开始部分,还把精灵的显示帧设置成初使图片(“mole.jpg”),因为,如果地鼠上一次被打中了,它下次再钻出来的时候,还会显示被打中。所以需要在它每次从洞里钻出来的时候,设置它的显示帧为初使图片。 · 好了,现在,这个精灵有一个userData标记,可以表明当前它是否可以被击打了。我们接下来,添加下面的击打检测代码: -(void) registerWithTouchDispatcher { [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:kCCMenuTouchPriority swallowsTouches:NO]; } -(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event { CGPoint touchLocation = [self convertTouchToNodeSpace:touch]; for (CCSprite *mole in moles) { if (mole.userData == FALSE) continue; if (CGRectContainsPoint(mole.boundingBox, touchLocation)) { mole.userData = FALSE; score+=10; [mole stopAllActions]; CCAnimate *hit = [CCAnimate actionWithAnimation:hitAnim restoreOriginalFrame:NO]; CCMoveBy *moveDown = [CCMoveBy actionWithDuration:0.2 position:ccp(0, -mole.contentSize.height)]; CCEaseInOut *easeMoveDown = [CCEaseInOut actionWithAction:moveDown rate:3.0]; [mole runAction:[CCSequence actions:hit, easeMoveDown, nil]]; } } return TRUE; }     这个registerWithTouchDispatcher方法会使得每一个touch事件到来的时候,都会先调用ccTouchBegan方法,如果ccTouchBegan返回yes,则有touch事件,否则没有。对于更多的细节信息,请查照第一篇教程《如何使用cocos2d来制作一个基于tiled地图的游戏》。   ccTouchBegan方法把touch坐标转换成相对于层的本地坐标,然后循环遍历每一个地鼠。如果地鼠不可以击打(它的userData属性是false),那么就直接看下一个地鼠。否则的话,就使用CGRectContainPoint来检测touch点是否在地鼠的精灵边框之内。   如果地鼠被击中了,就把它设置成不可击打的,同时增加分数。并且停止所有正在运行的action,然后播放“被打中”的动画,并且立马把地鼠缩回洞里去。   最后一步--添加一些代码来更新分数label以及检查关卡是否完成。 if (gameOver) return; [label setString:[NSString stringWithFormat:@"Score: %d", score]]; if (totalSpawns >=50) { CGSize winSize = [CCDirector sharedDirector].winSize; CCLabelTTF *goLabel = [CCLabelTTF labelWithString:@"Level Complete!" fontName:@"Verdana" fontSize:[self convertFontSize:48.0]]; goLabel.position = ccp(winSize.width/2, winSize.height/2); goLabel.scale =0.1; [self addChild:goLabel z:10]; [goLabel runAction:[CCScaleTo actionWithDuration:0.5 scale:1.0]]; gameOver =true; return; }     就这么多了!编译并运行,你现在可以尽情打地鼠赚分啦!你能得多少分呢? 免费的音效   和之前一样,让我们添加一些非常酷的音效。下载这些音效(它们是用Garage Band和Audacity制作的,这两个在iPad上面有),解压之,并把它们拖到Resource文件夹下面。同时,确保  “Copy items into destination group’s folder”被选中,再点Add。   然后,修改HelloWorldScene.m: // Add to top of file #import"SimpleAudioEngine.h" // Add at the bottom of your init method [[SimpleAudioEngine sharedEngine] preloadEffect:@"laugh.caf"]; [[SimpleAudioEngine sharedEngine] preloadEffect:@"ow.caf"]; [[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"whack.caf" loop:YES]; // Add at bottom of setTappable [[SimpleAudioEngine sharedEngine] playEffect:@"laugh.caf"]; // Add inside ccTouchBegan, inside the CGRectContainsPoint case [[SimpleAudioEngine sharedEngine] playEffect:@"ow.caf"];     编译并运行,心情享受打地鼠的乐趣吧! 何去何从   这里有本教程的完整源代码。   这个系列的教程到此基本上就结束了,为什么不往工程里添加更多的东西呢?我确定你可以往这个游戏添加一些更加好玩的元素。   如果你们有什么好的想法,或者好的建议,可以在下面留言。   译者的话:希望对大家有帮助。

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 6 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档