Cocos2D Tutorial for iOS: How To Create A Mole Whacking Game: Part 2/2

A Cocos2D tutorial on how to create a fun mole whacking game, that is a universal app for the iPhone and iPad. By Ray Wenderlich.

Leave a rating/review
Save for later
You are currently viewing page 2 of 3 of this article. Click here to view the first page.


Hide contents

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:

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

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:

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

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:

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

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:

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

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:

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

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 Tutorial.

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:

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;

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?

Showing the score in the game

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:

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

Compile and run your code, and enjoy the groovy tunes!