游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

使用Cocos2D制作打鼹鼠游戏的教程(2)

发布时间:2012-06-28 15:31:53 Tags:,,,,

作者:Ray Wenderlich

在本系列文章的第一部分我们已经创造了游戏的基本要素,即可爱的小鼹鼠能够从洞口中窜出来。我们也投入了大量时间去思考如何整合图像和坐标,从而让游戏能够有效地呈现在iPhone,iPad以及Retina屏幕上。

Whack A Mole(from raywenderlich)

Whack A Mole(from raywenderlich)

而在本篇文章中我们将在鼹鼠身上添加一些可爱的动画,让它们不仅能够微笑还能够显现出被敲击的表情;同时还将添加一些游戏玩法让玩家能够敲打鼹鼠而获得分数,并且像其它游戏那样也会在此添加音效。

定义动画:实用性

为了让游戏看起来更加有趣,我们将在鼹鼠身上添加两个动画。首先当它窜出洞口时就会微笑(从而诱导玩家去敲打它!),而当你尝试着去敲打它时,它的脸上将会出现挨打的表情。

不过在正式开始前我想先谈谈如何使用代码去定义我们动画的实用性。

在cocos2d的动画教程中关于创造动画的一大步骤便是创造一列精灵帧。所以对于动画中的每幅不同图像,你都需要在其阵列中添加如下精灵帧:

[animFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@”myImage.png”]];

我们的鼹鼠微笑动画将基于这个顺序成形:mole_laugh1.png, mole_laugh2.png mole_laugh3.png, mole_laugh2.png, mole_laugh3.png以及mole_laugh1.png。

所以我们便能以此编写出几行代码去创造动画:

[animFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@”mole_laugh1.png”]];
[animFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@”mole_laugh2.png”]];
[animFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@”mole_laugh3.png”]];
[animFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@”mole_laugh2.png”]];
// And so on…

但是如此看来我们的代码稍显凌乱,所以为了让整体看起来更加整洁,我们将代码放置在属性列表中而不是在动画中定义图像代码。

属性列表

如果你从未使用过属性列表,你便可以基于Xcode创建一些特殊文件,分级罗列出阵列,信息库,字符和数字等数据。

让我们看看具体要怎么做。首先我们需要在“资源”文件中右击并选择“Add\New File…”,然后选择“Mac OS X\Resource\Property List”,点击“下一步”。将新文件命名为“laughAnim.plist”并点击“完成”。这时我们便能够看到 laughAnim.plist 的属性列表编辑器,如下所示:

Property-List-Editor(from raywenderlich)

Property-List-Editor(from raywenderlich)

每个属性列表都拥有一个根元素,它可以是一个阵列或者一个信息库。而我们所创建的属性列表则将包含一组构成微笑动画的图像名称,所以我们需要点击根元素(目前是信息库)的第二栏并将其改为阵列。

接下来点击最右边的小按钮(游戏邦注:如下图,看起来就像三条线的图标),从而在阵列中添加一个新的条目。默认的条目类型是字符,而这也正是我们想要的。将动画中的第一个条目数值改为“mole_laugh1.png”。

点击“+”按钮添加新的一行,并重复添加所有动画帧,如下:

Laugh-Anim(from raywenderlich)

Laugh-Anim(from raywenderlich)

接下来当鼹鼠遭到敲打时我们仍需要重复上述的动画制作过程,并创造出一个名为hitAnim.plist的新属性列表,如下:

Hit-Anim(from raywenderlich)

Hit-Anim(from raywenderlich)

现在,是时候添加代码去加载这些动画了。我们需要打开HelloWorldScene.h并为每个动画添加如下变量:

// Inside @interface HelloWorld
CCAnimation *laughAnim;
CCAnimation *hitAnim;

我们将以此作为一种便利的参考,从而让我们能够在代码中轻松找到并重复使用CCAnimation。

接下来我们需要基于属性列表中所定义的图像而添加一种方法去创造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”中。这种方法将能够帮助我们在主束中创建一个文件,并且之后你需要在属性列表中阅读这一文件内容。

2.阅读属性列表,你将会发现这与在NSArray上调用arrayWithContentsOfFile方法并传送到属性列表中一样简单。我们将重新获得NSArray的相关内容(在这里也就是一列关于动画图像名称的字符)。我们需要注意的是之所这种方法会奏效是因为我们将根元素设定为NSArray了。而如果我们将其设定为NSDictionary,我们可能就要使用[NSDictionary dictionaryWithContentsOfFile...]了。

3.创建一个空白阵列以储存动画帧。

4.在属性列表的阵列中浏览每个图像名称。

5.获得每个图像的精灵帧并将其添加到阵列中。

6.基于精灵帧阵列而重新回到CCAnimation。

接下来我们需要在初始代码的末尾添加以下代码而调用每个动画的帮助功能:

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”];

同时还需要注意的是当我们储存了动画参考后,我们还需要将其添加到动画缓存中。这一点非常重要,如此我们才能够有效地保存动画,并且我们也能够使用名称在动画中检索到我们想要的内容。

最后一步——让我们开始使用动画(目前只是关于微笑的动画)。如下修改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]];
}

这里所存在的不同在于,比起在鼹鼠钻回洞里前延迟1秒钟,反而是在此运行CCAnimate行动。CCAnimate行动较早使用laughAnim进行设置,并将resotreOriginalFrame设置为“YES”,从而让鼹鼠能够在动画结束后恢复到正常的表情。

编译并运行代码,现在你便可以看到鼹鼠窜出洞口后朝你微笑了!

Mole-Laugh(from raywenderlich)

Mole-Laugh(from raywenderlich)

接下来我们便需要让鼹鼠的微笑转变成挨打的表情了!

添加游戏逻辑

现在我们需要在游戏中添加游戏逻辑。游戏理念是关于一定数量的鼹鼠会窜出洞口,你可以通过敲击它们而获得分数。你可以努力去争取最高的分数。

所以我们就需要去追踪分数并将其呈现给玩家。而当鼹鼠结束了跳窜行动后我们也需要让玩家明确地知道这一点。

所以让我们打开HelloWorldScene.h,并在HelloWorld层上添加以下实例变量:

CCLabelTTF *label;
int score;
int totalSpawns;
BOOL gameOver;

这些代码将能够追踪分数标签,当前分数,目前位置窜出的鼹鼠数量以及游戏是否结束等信息。

接下来我们需要在初始方法的最末尾添加以下代码:

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];

首先我们赋予了层面可触摸的性质,因为我们总是希望能在玩家碰触屏幕时察觉到这一动作。然后我们创造了一个标签向玩家呈现分数变化。同时还需要注意的是我们在标签的右下角设置了一个定位点“o”,如此我们便能够将标签放置在屏幕的右下方处。

我们最好先使用一个帮助功能去转化字体大小。因为iPad的屏幕更大,所以我们需要较大的字体。所以我们需要如下执行convertFontSize:

- (float)convertFontSize:(float)fontSize {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return fontSize * 2;
} else {
return fontSize;
}
}

这个过程其实非常简单——我们只需要将iPad上的字体设置为2倍大便可。

接下来我们将要添加触碰检测代码以观察玩家的碰触是否击中了鼹鼠。但是在此之前我们需要在鼹鼠身上添加一个标记以帮助我们明确鼹鼠是否挨打了。当鼹鼠微笑时它只能窜出洞口,而当它钻回洞里时它也就“安全”了。

我们将为鼹鼠创造一个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.png”]];

// 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行动去调用一个特定的方法(setTappable)。这一方法能够在精灵上将userData属性设置为TRUE,而我们也将以此明确是否能够拍打鼹鼠。

同样地,在鼹鼠微笑后我们需要使用CCCAllFunc行动去调用unsetTappable,并将鼹鼠身上的标志再次设为FALSE。

如果鼹鼠窜出的次数达到了50次以上(游戏邦注:因为50次已经是这款游戏的极限了),那么这一方法便会立刻恢复之前的样子。

在方法的开始处将精灵显示帧重置为基础图像(“mole_1.png”),因为当鼹鼠最后一次遭到敲击时,我们将会看到“敲击”图像并将需要对此进行重置。

既然精灵带有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方法能够推动着ccTouchBegan方法调用每次碰触。

ccTouchBegan方法能够转变碰触在层面上的坐标轴,并依次通过每只鼹鼠。即如果玩家未碰到鼹鼠(即userData为false),它便会自己跳到下一只鼹鼠身上。另外,它也将使用CGRectContainsPoint去明确碰触点是否位于鼹鼠的边界框内。

如果鼹鼠遭到敲打,这一方法便能够让鼹鼠不再具有被敲打的属性,并提高玩家的分数。然后停止任何行动,播放“敲打”动画,并将鼹鼠迅速移回洞中。

最后一步,在tryPopMoles的开始处添加一些代码去更新分数并检查关卡的完成状态:

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;

编译并运动你的代码,这时候你便能够敲击鼹鼠并提高你的分数了!

High-Score(from raywenderlich)

High-Score(from raywenderlich)

音效

像往常一样我们也需要在此添加一些特别的音效而让游戏变得更加有趣。下载我通过使用Garage Band(这是一款由苹果电脑编写的数码音乐创作软件,是Macintosh电脑上的应用程序套装iLife的一部分)和Audacity(一款跨平台,免费开源的录音,编辑声音编辑器)所创造的音效,打开文件,并将声音拖到你的“资源”文件夹中。确保你选中了“将内容复制到你的目标群组”这一文件夹,然后点击“添加”。

然后在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”];

编译并运行代码,现在你便能够听到每秒的旋律了!

游戏邦注:原文发表于2011年1月17日,所涉事件和数据均以当时为准。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

How To Create A Mole Whacking Game with Cocos2D: Part 2/2

17 January 2011

This article is the second part of a two-part series on how to create a mole whacking game with Cocos2D. This series brings together a lot of concepts from other cocos2D tutorials on this site, and introduces some new concepts along the way as well.

In the first part of the series, we created the basics of the game – cute little moles popping out of holes. We spent a lot of time thinking about how to organize the art and coordinates so that the game would look good on the iPhone, iPad, and Retina display – and be efficient too!

In this article, we’ll add some cute animations to the mole as he laughs and gets whacked, add gameplay so you can do the whacking and earn points, and of course add some gratuitous sound effects as usual.

If you don’t have it already, grab a copy of the project where we left things off in the last tutorial.

Defining Animations: Practicalities

To make the game a little more fun, we’re going to give the mole two animations. First, he’ll laugh a little when he pops out of the hole (to make you really want to whack him!), then if you do manage to whack him he’ll make a “just got whacked” face.

But before we begin, let’s talk about the practicalities of defining our animations in code.

Recall from the cocos2d animations tutorial that one of the steps to create an animation is to create a list of sprite frames. So for each different image in your animation, you have to add the sprite frame for that sprite into an array like this:

Our mole’s laugh animation is going to be these images in this order: mole_laugh1.png, mole_laugh2.png mole_laugh3.png, mole_laugh2.png, mole_laugh3.png, mole_laugh1.png.

So we could hard-code a bunch of lines to set up our animation, like this:

But that would make our code kind of ugly. To make things a bit cleaner, instead of defining the images in the animation in code, we’ll bring them out to a property list instead.

Property Lists
If you haven’t used property lists before, they are special files you can create in XCode to contain data like arrays, dictionaries, strings, numbers, and so on in a hierarchial format. It’s extremely easy to create these, and just as easy to read them from code.

Let’s see what I mean by trying this out in XCode. Right click on Resources, choose “Add\New File…”, choose “Mac OS X\Resource\Property List”, and click “Next”. Name the new file “laughAnim.plist”, and click Finish. At this point the property list editor for laughAnim.plist should be visible, as shown below:

Every property list has a root element. This is usually either an array or a dictionary. This property list is going to contain an array of image names that make up the laugh animation, so click on the second column for the root element (Type, currently set to Dictionary), and change it to Array.

Next, click the small button to the far right that looks like three lines – this adds a new entry to the array. By default, the type of the entry is a String – which is exactly what we want. Change the value to “mole_laugh1.png” for the first entry in the animation.

Click the + button to add a new row, and repeat to add all of the frames of the animation, as shown below:

Next, repeat the process for the animation to play when the mole is hit. Follow the same steps as above to create a new property list named hitAnim.plist, and set it up as shown below:

Now, time to add the code to load these animations. Start by opening up HelloWorldScene.h and add a member variable for each animation, as shown below:

These will be used to keep a handy reference to each CCAnimation so it can be easily found and reused in the code.

Next add a method to create a CCAnimation based on the images defined in the property list, as follow:

This is important to understand, so let’s go through it line by line.

1.The property list is included in the project file, so it’s in the app’s “main bundle”. This helper method gives a full path to a file in the main bundle, which you’ll need to read in the property list.

2.To read a property list, it’s as easy as calling a method on NSArray called arrayWithContentsOfFile and passing in the path to the property list. It will return an NSArray with the contents (a list of strings for the image names in the animatoin, in this case). Note this works because we set the root element to be an NSArray. If we had set it to a NSDictionary, we could use [NSDictionary dictionaryWithContentsOfFile...] instead.

3.Creates an empty array that will store the animation frames.

4.Loops through each image name in the array read from the property list.

5.Gets the sprite frame for each image and adds it to the array.

6.Returns a CCAnimation based on the array of sprite frames.

Next, add the code to the end of your init method to call this helper function for each animation:

Note that after squirreling away a reference to the animation, it adds it to the animation cache. This is important to do so that the animations are saved off (and retained) somewhere. It’s also helpful since you could retrieve them from the animation cache by name if you wanted (but we dont’ need to since we’re keeping a reference ourselves).

One last step – let’s use the animations (just the laugh one for now). Modify the popMole method to read as the following:

The only difference here is that instead of delaying a second before popping down, it runs a CCAnimate action instead. The CCAnimate action uses the laughAnim set up earlier, and sets resotreOriginalFrame to YES so that when the animation is done, it reverts back to the normal mole face.

Compile and run your code, and now when the moles pop out, they laugh at you!

Time to wipe that smile off their faces and start whacking!

Adding Game Logic
We’re now going to add the gameplay logic into the game. The idea is a certain number of moles will appear, and you get points for each one you whack. You try to get the most number of points you can.

So we’ll need to keep track of the score, and also display it to the user. And when the moles are finished popping, we’ll need to tell the user about that as well.

So start by opening HelloWorldScene.h, and add the following instance variables to the HelloWorld layer:

These will keep track of the score label, the current score, the number of moles popped so far, and whether the game is over or not.

Next add the following to the end of your init method:

This first sets the layer as touch enabled, since you’ll want to detect when the player taps the screen. It then creates a label to show the score. Note that it sets the anchor point o the bottom right of the label so that it’s easy to place it in the lower right of the screen.

Also note that rather than pasing the font size directly, it goes through a helper function to convert the font size first. This is because the font size will need to be larger on the iPad, since it has a bigger screen. So implemenet convertFontSize next as the following:

This is very simple – on the iPad the font size is doubled, otherwise it’s left alone.

Next we want to add the touch detection code to see if a touch has hit a mole. But before we can do that, we need to add a flag to the mole to the game knows whether the mole is currently tappable or not. The mole should only be able to be tapped while it’s laughing – while it’s moving or underground it’s “safe.”

We could create a subclass of CCSprite for the mole to keep track of this, but because we only need to store this one piece of information, we’ll use the userData property on the CCSprite instead. So add two helper methods and modify popMole one more time to do this as follows:

The changes to popMole are as follows:

Right before the mole laughs, it runs a CCCallFunc action to call a specified method (setTappable). This method sets the userData property on the sprite to TRUE, which we’ll use to indicate whether the mole is tappable.

Similarly, after the mole laughs, it uses a CCCAllFunc action to call unsetTappable, which sets the flag back to FALSE.

The method also immediately returns if there has been 50 or more spawns, since 50 is the limit for this game.

It resets the display frame of the sprite to the base image (“mole_1.png”) at the beginning of the method, since if the mole was hit last time, it will still be showing the “hit” image and will need to be reset.

Ok, now that the sprite has a userData flag indicating whether it can be tapped or not, we can finally add the tap detection code as follows:

The registerWithTouchDispatcher method sets things up so that the ccTouchBegan method gets called for each touch. For more details on this and why this is useful, check out an explanation in the How To Make a Tile Based Game with Cocos2D Ttutorial.

The ccTouchBegan method converts the touch to coordinates in the layer, and loops through each mole. If the mole isn’t tappable (the userData is false), it skips to the next mole. Otherwise, it uses CGRectContainsPoint to see if the touch point is within the mole’s bounding box.

If the mole is hit, it sets the mole as no longer tappable, and increases the score. It then stops any running actions, plays the “hit” animation, and moves the mole immediately back down the hole.

One final step – add some code to update the score and check for the level complete condition at the beginning of tryPopMoles:

That’s it! Compile and run your code, and you should be able to whack moles and increase your score! How high of a score can you get?

Gratuitous Sound Effects

As usual, let’s add even more fun to the game with some zany sound effects. Download these sound effects I made with Garage Band and Audacity, unzip the file, and drag the sounds to your Resources folder. Make sure that “Copy items into destination group’s folder” is selected, and click Add.

Then make the following changes to HelloWorldScene.m:

Compile and run your code, and enjoy the groovy tunes!(source:raywenderlich)


上一篇:

下一篇: