Collisions and Collectables: How To Make a Tile-Based Game with Cocos2D 2.X Part 2

Charlie Fulton

Update 1/17/2013 Fully updated for Cocos2D 2.1 rc0, Tiled 0.9.0 (released Feb 2013), Xcode 4.6, and ARC. (original post by Ray Wenderlich, update by Charlie Fulton).

Mmm, that was tasty!

Mmm, that was tasty!

This is the second part of a 2-part tutorial where we cover how to make a tile-based game with Cocos2D and the Tiled map editor. We are creating a simple tile-based game where a ninja explores a desert in search of tasty watermelon-things!

In the first part of the tutorial, we covered how to create a map with Tiled, how to add the map to the game, how to scroll the map to follow the player, and how to use object layers.

In the this part of the tutorial, we’ll cover how to make collidable areas in the map, how to use tile properties, how to make collectable items and modify the map dynamically, and how to make sure your ninja doesn’t overeat.

So let’s pick up where we left off last time and make our map a bit more game-like!

Tiled Maps and Collisions

You may have noticed that currently the ninja can move right through walls and obstacles with no problem at all. He is a ninja, but even ninjas aren’t that good!

So we need to figure out a way to mark some tiles as “collidable” so we can prevent the player from moving into those positions. There are many possible solutions to this (including using object layers), but I’m going to show you a new technique that I think is effective and also a good learning exercise – using a meta layer and layer properties.

Let’s dive right in! Load up Tiled again, click “Layer\Add Tile Layer…”, name the Layer “Meta”, and click OK. This is the layer we will be putting a few fake tiles in to indicate “special tiles”.

So now we need to add our special tiles. Click “Map\New Tileset…”, browse to meta_tiles.jpg in your TileGame\TileGameResources folder, and click Open. Set the Margin and Spacing to 1 and click OK.

With the layers window selected, click on meta_tiles in the Tilesets window. You will see two tiles: red and green.

Tiled and Meta Tiles

There is nothing at all special about these tiles – I just made a simple image with two red and green tiles with partial transparency. However we’re going to decide that red means “collidable” (we’ll use green later on), and paint our scene appropriately.

So make sure the Meta layer is selected, choose the stamp tool, choose the red tile, and paint over any object that you want the ninja to collide with. When you’re done it might look like the following:

Tiled and Painting Tiles for Collision Detection

Next, we need to set a property on the tile to flag it so we can recognize in code that this is the tile that is “collidable.” Right click on the red tile in the Tilesets section, and click “Tile Properties…”. Add a new property for “Collidable” set to “True” like the following:

Tile Properties in Tiled

Save the map and return to Xcode. Make the following changes to HelloWorldLayer.m:

// Inside the HelloWorldLayer interface with the other private properties
@property (strong) CCTMXLayer *meta;
 
// In init, right after loading background
self.meta = [_tileMap layerNamed:@"Meta"];
_meta.visible = NO;
 
// Add new method
- (CGPoint)tileCoordForPosition:(CGPoint)position {
    int x = position.x / _tileMap.tileSize.width;
    int y = ((_tileMap.mapSize.height * _tileMap.tileSize.height) - position.y) / _tileMap.tileSize.height;
    return ccp(x, y);
}

Ok let’s stop here a second. We declare a member/variable for the meta layer as usual, and load a reference from the tile map. Note that we mark the layer as invisible since we don’t want to see these objects, they are for annotating what is collidable only.

Next we add a new helper method that helps us convert x,y coordinates to “tile coordinates”. Each of the tiles has a coordinate, starting with (0,0) for the upper left and (49,49) for the bottom right (in our case).

Tile Coordinates in Tiled

The above screenshot is from the Java version of Tiled, btw. Showing the coordinates for tiles is a feature I don’t think they’ve ported to the Qt version yet.

Anyway, some of the functions we’re about to use require tile coordinates rather than x,y coordintes, so we need a way to convert the x,y coordinates into tile coordinates. This is exactly what this function does!

Getting the x coordinate is easy – we just divide it by the width of a tile. To get the y coordinate, we have to flip things around because in Cocos2D (0,0) is at the bottom left, not the top left.

Next replace the contents of setPlayerPosition with the following:

CGPoint tileCoord = [self tileCoordForPosition:position];
int tileGid = [_meta tileGIDAt:tileCoord];
if (tileGid) {    
    NSDictionary *properties = [_tileMap propertiesForGID:tileGid];
    if (properties) {        
        NSString *collision = properties[@"Collidable"];
        if (collision && [collision isEqualToString:@"True"]) {            
            return;
        }
    }
}
_player.position = position;

Here we convert the x,y coordinates for the player to tile coordinates. Then we use the tileGIDAt function in the meta layer to get the GID at the specified tile coordinate.

Huh, what’s a GID? GID stands for “globally unique identifier” (I think). But in this case I like to think of it as the id for the tile we’re using, which would be the red square if that’s where we’re trying to move.

We then use the GID to look up properties for that tile. It returns a dictionary of properties, so we look through to see if “Collidable” is set to true, and if it is we return immediately, hence not setting the player position and making the move invalid.

And that’s it! Compile and run the project, and you should now no longer be able to walk through any tiles you painted red:

Screenshot demonstrating you cannot move through walls

Modifying the Tiled Map Dynamically

So far our ninja is having a fine time exploring, but this world is a little dull. There’s simply nothing to do!

Plus our ninja looks a bit hungry. So let’s spice things up by giving our ninja something to eat.

For this to work, we’re going to have to create a foreground layer for any objects we want the user to collect. That way, we can simply delete the tile from the foreground layer when the ninja picks it up, and the background will show through.

So open up Tiled, go to “Layer\Add Tile Layer…”, name the layer “Foreground”, and click OK. Make sure the Foreground layer is selected, and add a couple collectibles to your map. I liked to use the tile that looks like a Watermelon or something to me.

Adding collectibles to the Tile Map

Now, we need to mark those tiles as collectible, similarly to how we marked some of the tiles as collidable. Select the meta layer, switch over to the meta_tiles, and paint a green tile over each of your collectables. You’ll have to click “Layer\Raise Layer” to make sure the meta layer is on top so that the green is visible.

Painting collectables with meta tiles

Next, we need to add the property to the tile to mark it as collectable. Right click on the green tile in the Tilesets section, click “Tile Properties…” and add a new property with name “Collectable”, value “True”.

Save the map and go back to Xcode. Make the following changes to HelloWorldWorldLayer.m:

// in HelloWorldLayer interface declaration with the other private properties
@property (strong) CCTMXLayer *foreground;
 
// In init, right after loading background
self.foreground = [_tileMap layerNamed:@"Foreground"];
 
// Add to setPlayerPosition, right after the if clause with the return in it
NSString *collectible = properties[@"Collectable"];
if (collectible && [collectible isEqualToString:@"True"]) {
    [_meta removeTileAt:tileCoord];
    [_foreground removeTileAt:tileCoord];    
}

Here is standard stuff to keep a reference to the foreground layer. The new thing is we check to see if the tile the player is moving to has the “Collectable” property. If it does, we use the removeTileAt method to remove the tile from both the meta layer and the foreground layer.

Compile and run the project, and now your ninja will be able to dine on tasty-melon-thingie!

Screenshot of Ninja about to eat a melon

Creating a Score Counter

Our ninja is happy and fed, but as players we’d like to know how many melons he’s eaten. You know, we don’t want him getting fat on us.

Usually we’d just add a label to our layer and be done with it. But wait a minute – we’re moving the entire layer all the time, that will screw us up, oh noes!

This is a good opportunity to show how to use multiple layers in a scene – this is the type of situation they are built for. We’ll keep our HelloWorld layer as we’ve been doing, but we’ll make an additional Layer called HelloWorldHud to display our label. (Hud means heads up display).

Of course, our two layers need some method of communicating – the Hud layer will want to know when the ninja snacks on a melon. There are many many ways of getting the two layers to communicate, but we’ll go with the most simple way possible – we’ll hand the HelloWorld layer a pointer to the HelloWorldHud layer, and it can call a method to notify it when the ninja snacks.

So add the following to HelloWorldLayer.h:

// Before HelloWorldLayer class declaration after #import "cocos2d.h"
@interface HudLayer : CCLayer
- (void)numCollectedChanged:(int)numCollected;
@end

And make the following changes to HelloWorldLayer.m:

// At top of file after imports (from @implementation -> @end)
@implementation HudLayer 
{
    CCLabelTTF *_label;
}
 
- (id)init 
{
    self = [super init];
    if (self) {
        CGSize winSize = [[CCDirector sharedDirector] winSize];
        _label = [CCLabelTTF labelWithString:@"0" fontName:@"Verdana-Bold" fontSize:18.0];
        _label.color = ccc3(0,0,0);
        int margin = 10;
        _label.position = ccp(winSize.width - (_label.contentSize.width/2) - margin, _label.contentSize.height/2 + margin);
        [self addChild:_label];
    }
    return self;
}
 
-(void)numCollectedChanged:(int)numCollected 
{    
    _label.string = [NSString stringWithFormat:@"%d",numCollected];
}
@end
 
 
// Inside the HelloWorldLayer interface with the other private properties
@property (strong) HudLayer *hud;
@property (assign) int numCollected;
 
 
// Add to the +(CCScene *) scene method right before the return
HudLayer *hud = [HudLayer node];
[scene addChild:hud];
layer.hud = hud;
 
// Add inside setPlayerPosition, in the case where a tile is collectable
self.numCollected++;
[_hud numCollectedChanged:_numCollected];

Nothing too fancy here. Our second layer derives from CCLayer and just adds a simple label to the bottom right corner. We modify the scene to add the second layer to the scene as well, and pass the HelloWorld layer a reference to the Hud. Then we modify the HelloWorldLayer to call a method on the Hud when the count changes, so it can update the label accordingly.

Compile and run the project, and if all goes well you should see a melon counter in the bottom right!

Screenshot of melon counter

Gratuituous Sound Effects and Music

You know this wouldn’t be a game tutorial from this site without completely unnecessary but fun sound effects and music :]

Simply make the following changes to HelloWorldLayer.m:

// At top of file
#import "SimpleAudioEngine.h"
 
// At top of init for HelloWorldLayer
[[SimpleAudioEngine sharedEngine] preloadEffect:@"pickup.caf"];
[[SimpleAudioEngine sharedEngine] preloadEffect:@"hit.caf"];
[[SimpleAudioEngine sharedEngine] preloadEffect:@"move.caf"];
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"TileMap.caf"];
 
// In case for collidable tile
[[SimpleAudioEngine sharedEngine] playEffect:@"hit.caf"];
 
// In case of collectable tile
[[SimpleAudioEngine sharedEngine] playEffect:@"pickup.caf"];
 
// Right before setting player position
[[SimpleAudioEngine sharedEngine] playEffect:@"move.caf"];

Now our ninja can groove happy as he eats!

Where To Go From Here?

That’s it for this tutorial series, at least for now. You should have a good grasp on the most important concepts related to using tile maps in Cocos2D at this point.

Here is a copy of the Tile-Based Cocos2D game that we’ve developed so far.

If you enjoyed this series, our good friends from Geek and Dad have developed a follow-up to the series (not updated for Cocos2D 2.X yet though): Enemies and Combat: How To Make a Tile-Based Game with Cocos2D Part 3! Check it out to see how you can extend the game to add enemies, projectiles, and a win/lose scene!

If you have any additional tips or suggestions for how to effectively use Tiled or tile-based maps in Cocos2D effectively, or if you’ve used or are planning to use tile-based maps in your projects, please share below!

Charlie Fulton

Charlie Fulton is a full time iOS developer. He has worked with many languages and technologies in the past 16 years, and is currently specializing in iOS and Cocos2D development. In his spare time, Charlie enjoys hunting, fishing, and hanging out with his family.

You can follow Charlie on Twitter.

Other Items of Interest

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Come check out Alt U

Our Books

Our Team

Video Team

... 9 total!

Swift Team

... 15 total!

iOS Team

... 47 total!

Android Team

... 15 total!

OS X Team

... 12 total!

Apple Game Frameworks Team

... 15 total!

Unity Team

... 11 total!

Articles Team

... 8 total!