(译)如何使用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)。因此,对于你的动画效果中的每一张不同的图片,你必须为之增加精灵帧,如下所示:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"myImage.jpg"]];
我们的地鼠笑的动画将会是下面的一些图片序列: mole_laugh1.jpg, mole_laugh2.jpg mole_laugh3.jpg, mole_laugh2.jpg, mole_laugh3.jpg, mole_laugh1.jpg.
因此,我们可以硬编码来建立动画,如下所示:
[[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文件,然后为每一个动画定义一个成员变量,如下所示:
CCAnimation *laughAnim;
CCAnimation *hitAnim;
这样做的目的主要是重用,因为可以在init函数里面初使化好这些动画效果,那么在其它的地方就直接可以使用这些动画效果了。(这里需要记住的一点是,游戏里面的任何对象都要事先分配好,在玩家玩游戏的过程中,只需要按照某种规则把它们拿出来即可)。
接下来,基于先前创建的plist文件来创建CCAnimation,如下所示:
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
}
理解这个方法非常重要,所以让我们一行一行代码来看。
- 属性列表文件包含在工程当中,因此,它也在应用程序的“main bundle”中。这个方法会返回main bundle中的文件的完整路径,也就是我们需要读取的plist文件的完整路径。
- 为了读取一个plist文件,我们调用NSArray的arrayWithContentsOfFile方法,然后把plist文件的完整路径传递给它。这样就会把plist文件中的内容初使化成一个数组。(本例中,这个数组就是一系列图片名字的数组)。注意,这个方法可行,是因为我们把根元素设置成了NSArray。如果我们把它设置成NSDictionary的话,那么我们就要用一个NSDictionary去初使化它。具体的方法就是调用 [NSDictionary dictionaryWithContentsOfFile...] 。
- 创建一个空的数组来存储这些动画帧。
- 从plist文件中循环遍历每一张图片并把它存到一个数组中去。
- 为每一张图片创建一个精灵帧,同时把它加到 animFrames数组里面去。
- 基于一个精灵帧数组,返回一个CCAnimation对象。
接下来,在init方法的末尾为每一个动画调用这个辅助函数来创建相应的动画:
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方法,如下所示:
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层中:
int score;
int totalSpawns;
BOOL gameOver;
这里保存了一个分数label,当前的分数值,总共钻出来的地鼠数目,以及游戏是否结束。
接下来,在你的init方法的结尾添加下列初始化代码:
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方法,如下所示:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return fontSize *2;
} else {
return fontSize;
}
}
这个非常简单--在iPad上运行的时候,字体大小加倍,否则,就不变。
接下一,我们将添加touch检测代码,来检测用户是否击中一个地鼠。但是,在这之前,我们需要添加一个标记,标记地鼠是否可以击打。因为地鼠应该只有在它朝着你笑的时候才能够被击打,而在它笑完钻回去的时候,你是不能够击打它的。
我们可以创建CCSprite的一个子类来做这个事,但是,因为我们只需要存储一点点信息,所以,我们只需要使用CCSprite的userData属性即可。因此,添加两个辅助方法,并且修改popMole方法,如下所示:
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标记,可以表明当前它是否可以被击打了。我们接下来,添加下面的击打检测代码:
{
[[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以及检查关卡是否完成。
[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:
#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"];
编译并运行,心情享受打地鼠的乐趣吧!
何去何从
这里有本教程的完整源代码。
这个系列的教程到此基本上就结束了,为什么不往工程里添加更多的东西呢?我确定你可以往这个游戏添加一些更加好玩的元素。
如果你们有什么好的想法,或者好的建议,可以在下面留言。
译者的话:希望对大家有帮助。
著作权声明:本文由http://www.cnblogs.com/andyque翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!