(译)如何使用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"];

 

  编译并运行,心情享受打地鼠的乐趣吧!

何去何从

  这里有本教程的完整源代码

  这个系列的教程到此基本上就结束了,为什么不往工程里添加更多的东西呢?我确定你可以往这个游戏添加一些更加好玩的元素。

  如果你们有什么好的想法,或者好的建议,可以在下面留言。

  译者的话:希望对大家有帮助。

 

著作权声明:本文由http://www.cnblogs.com/andyque翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

posted on 2011-05-20 20:37  子龙山人  阅读(9917)  评论(17编辑  收藏  举报