Cocos2D-X Tile Map Tutorial: Part 2

Part 2 of a Cocos2D-X tile map tutorial series about his ninja and his quest for watermelon. By Jorge Jordán.

Leave a rating/review
Save for later
Share

Mmm, that was tasty!

Mmm, that was tasty!

Welcome back to our 2-part Cocos2D-X tile map tutorial series!

Here you’ll learn how to make a tile-based game with Cocos2D-X, the cross-platform C++ port of Cocos2D-iPhone.

Note: This tutorial is a port of a similar tutorial for Cocos2D-iPhone. If you are looking for the Cocos2D-iPhone version, you can check it out here.

In the first part of the tutorial, you learned 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.

This part of the tutorial covers 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 you left off last time and make the 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 you need to figure out a way to mark some tiles as “collidable” so you 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 and name the Layer Meta. This is the layer you will be putting a few fake tiles in to indicate “special tiles”.

So now you need to add the special tiles. Click Map\New Tileset…, browse to meta_tiles.png in your TileGame\Resources\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.

Tile Map with Meta Layer

There is nothing at all special about these tiles – I just made a simple image with two red and green tiles with partial transparency. Henceforth, red means “collidable” and it will be used to paint the scene appropriately.

So make sure the Meta layer is selected, choose the Stamp Brush 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:

TileGame metatiles drawing

Next, you need to set a property on the tile to flag it so you 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 Collidable

Click OK, save the map and return to Xcode. Add a new private property to HelloWorldScene.h:

CCTMXLayer *_meta;

This will be a reference to your new meta layer. Add add a new public method declaration:

CCPoint tileCoordForPosition(CCPoint position);

This will be a helper routine you’ll write to convert a position to a tile coordinate.

Next open HelloWorldScene.cpp and add these lines to the the init method, right after loading background:

_meta = _tileMap->layerNamed("Meta");
_meta->setVisible(false);

This gets a reference to the meta layer, and turns it to be invisible. You don’t want the player to see red cactii!

Next, add this new method:

CCPoint HelloWorld::tileCoordForPosition(CCPoint position)
{
    int x = position.x / _tileMap->getTileSize().width;
    int y = ((_tileMap->getMapSize().height * _tileMap->getTileSize().height) - position.y) / _tileMap->getTileSize().height;
    return ccp(x, y);
}

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

Next you add a new helper method that helps you 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 your case).

Tiled java Coordinates

The above screenshot is from an older (Java) version of Tiled, by the way. Showing the coordinates for tiles is a feature they’ve ported to newer (Qt) version yet.

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

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

Next replace the contents of setPlayerPosition with the following:

void HelloWorld::setPlayerPosition(CCPoint position) 
{
    CCPoint tileCoord = this->tileCoordForPosition(position);
    int tileGid = _meta->tileGIDAt(tileCoord);
    if (tileGid) {
        CCDictionary *properties = _tileMap->propertiesForGID(tileGid);
        if (properties) {
            CCString *collision = new CCString();
            *collision = *properties->valueForKey("Collidable");
            if (collision && (collision->compare("True") == 0)) {
                return;
            }
        }
    }
    _player->setPosition(position);
}

Here you convert the x,y coordinates for the player to tile coordinates. Then you 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 is used, which would be the red square if that’s where you’re trying to move.

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

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

TileMap with Collidable Walls

Modifying the Tiled Map Dynamically

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

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

For this to work, you’re going to have to create a foreground layer for any objects you want the user to collect. That way, you 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 and name the layer Foreground. 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.

Tile Map with Collectable watermelons

Now, you need to mark those tiles as collectible, similarly to how you marked some of the tiles as collidable. Select the Meta layer, switch over to the meta_tiles view 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.

Tile Map with collect meta

Next, you 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 and click OK.

Tile Properties Collectable

Save the map and go back to Xcode. Add a new private property to HelloWorldScene.h:

CCTMXLayer *_foreground;

Then open HelloWorldScene.cpp, and add this line to the init method, right after loading background:

_foreground = _tileMap->layerNamed("Foreground");

This gets a reference to the foreground layer, which you’ll need later.

Next add these lines to setPlayerPosition, right after the if clause with the return in it:

CCString *collectible = new CCString();
*collectible = *properties->valueForKey("Collectable");
if (collectible && (collectible->compare("True") == 0)) {
    _meta->removeTileAt(tileCoord);
    _foreground->removeTileAt(tileCoord);
}

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

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

Ninja about to eat a melon

Jorge Jordán

Contributors

Jorge Jordán

Author

Over 300 content creators. Join our team.