Book Image

Creating Games with cocos2d for iPhone 2

By : Paul Nygard
Book Image

Creating Games with cocos2d for iPhone 2

By: Paul Nygard

Overview of this book

Cocos2d for iPhone is a simple (but powerful) 2D framework that makes it easy to create games for the iPhone. There are thousands of games in the App Store already using cocos2d. Game development has never been this approachable and easy to get started. "Creating Games with cocos2d for iPhone 2" takes you through the entire process of designing and building nine complete games for the iPhone, iPod Touch, or iPad using cocos2d 2.0. The projects start simply and gradually increase in complexity, building on the lessons learned in previous chapters. Good design practices are emphasized throughout. From a simple match game to an endless runner, you will learn how to build a wide variety of game styles. You will learn how to implement animation, actions, create "artificial randomness", use the Box2D physics engine, create tile maps, and even use Bluetooth to play between two devices. "Creating games with cocos2d for iPhone 2" will take your game building skills to the next level.
Table of Contents (16 chapters)
Creating Games with cocos2d for iPhone 2
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Adding interactivity


Now that we have our randomized grid of tiles on the board, we need to add the touch handler to let us interact with them. Since our game mechanics are pretty simple, we will use just the ccTouchesEnded method as follows:

Filename: MTPlayfieldLayer.m

-(void) ccTouchesEnded:(NSSet *)touches
             withEvent:(UIEvent *)event {
    
    // If game over, go back to the main menu on any touch
    if (isGameOver) {
        [[CCDirector sharedDirector]
                    replaceScene:[MTMenuScene node]];
    }
    
    UITouch *touch = [touches anyObject];
  
    CGPoint location = [touch locationInView: [touch view]];
    CGPoint convLoc = [[CCDirector sharedDirector]
                            convertToGL:location];
    
    // If the back button was pressed, we exit
    if (CGRectContainsPoint([backButton boundingBox],
                            convLoc)) {
        [[CCDirector sharedDirector]
                replaceScene:[MTMenuScene node]];
    }
    
    // If we have 2 tiles face up, do not respond
    if ([tilesSelected count] == 2) {
        return;
    } else {
        // Iterate through tilesInPlay to see which tile
        // was touched
        for (MTMemoryTile *aTile in tilesInPlay) {
            if ([aTile containsTouchLocation:convLoc] &&
                [aTile isFaceUp] == NO) {
                // Flip the tile
                [aTile flipTile];
                // Hold the tile in a buffer array
                [tilesSelected addObject:aTile];
                
                // Call the score/fail check,
                // if it is the second tile
                if ([tilesSelected count] == 2) {
                    // We delay so player can see cards
                   [self scheduleOnce:@selector(checkForMatch)
                                 delay:1.0];
                   break;
                }
                
            }
        }
    }
}

In the touch handler, the touches are provided in an NSSet. However, since we do not have multiple touches enabled, we can be sure we will only be getting a single touch that we care about. Why is there no multi-touch in this game? Multiple simultaneous touches would cause confusion for the player and really complicate the code to determine which tiles should be checked for a match. So by not enabling multiple touches, we save ourselves extra work, and reduce the confusion for the flow of the game.

The first section of the method checks to see if we have reached a game over state (as represented by a YES value in the isGameOver variable, any touch after we have reached game over will return the player to the menu screen.

The second section of the method is detecting touches on the back button. The location and convLoc variables together convert the touch into a coordinate in our game screen. We use this location to check if the backButton was touched. If it has been touched, we also exit to the menu screen, regardless of what else is going on in the game.

We then check to make sure the tilesSelected array doesn't have two items in it. The tilesSelected array is with the tiles that have been flipped face up. If there are two tiles already face up, that means the match checking has not yet been resolved. In those cases, we don't want to let the user keep flipping over tiles, so we simply return without responding to the touch. This will effectively throw away the touch, so we can safely move on with our game.

If we don't have two tiles selected already, then we iterate through all of the tiles in the tilesInPlay array and poll it to determine: a) are you being touched? and b) are you face down. If both of these are true, we send the message to the touched tile to flip over (flipTile), and we add the tile to the tilesSelected array. If this was our second tile added to the tilesSelected array, we will call the checkForMatch method after a delay of one second. This delay gives us two benefits: it allows the player to see the potential match they just made, and it gives plenty of time to finish iterating through the tilesInPlay array so we don't risk mutating the array. Mutating an array means you tried to change it while it was being evaluated. If we skipped the delay, the checkForMatch method would cause this mutation (and crash) because it can remove tiles from the tilesInPlay array. Go ahead and try it yourself. Actually seeing the error messages when you know what you did wrong will help you know where to look later, when you cause a crash without knowing what you did wrong.

Checking for matches

Since we have done quite a bit of preparation for the rest of the mechanics of the game, it might come as a surprise that the logic to check for matching tiles is very simple. Since we stored the name of the image used for each tile inside the MTMemoryTile object, it is a matter of comparing the two and see if they are the same.

Filename: MTPlayfieldLayer.m

-(void) checkForMatch {
    // Get the MemoryTiles for this comparison
    MTMemoryTile *tileA = [tilesSelected objectAtIndex:0];
    MTMemoryTile *tileB = [tilesSelected objectAtIndex:1];
    
    // See if the two tiles matched
    if ([tileA.faceSpriteName
            isEqualToString:tileB.faceSpriteName]) {
        // We remove the matching tiles
        [self removeMemoryTile:tileA];
        [self removeMemoryTile:tileB];
    } else {
        // No match, flip the tiles back
        [tileA flipTile];
        [tileB flipTile];
    }

    // Remove the tiles from tilesSelected
    [tilesSelected removeAllObjects];
}

-(void) removeMemoryTile:(MTMemoryTile*)thisTile {
    [thisTile removeFromParentAndCleanup:YES];
}

If you recall, in the ccTouchesEnded method we stored the face up tile in the tilesSelected array. Our logic only allows there to be two objects in the tilesSelected array, and the checkForMatch method is called only when there are two objects in that array. Because of those restrictions, we can safely assume that there are objects in that array at index 0 and index 1. (We create references to them as tileA and tileB to make the code simpler.)

It is trivial at this point to call isEqualToString on the faceSpriteName variable of tileA and pass it the value of the faceSpriteName variable from tileB. If these strings are equal, we have a match. When comparing strings, you cannot use == operations, you must use isEqualToString:.

When a match is found, we call the removeMemoryTile method that simply removes the tile passed. If we don't have a match, we send the message to each tile to flip itself back over. Since we have resolved the matching, either by making a match or by turning the tiles back over, we then remove the tiles from the tilesSelected array so we have an empty array to hold the next possible match.