Cocos2D-X Tile Map Tutorial: Part 2

Jorge Jordán

This post is also available in: Spanish

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

Creating a Score Counter

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

Usually you’d just add a label to your layer and be done with it. But wait a minute – you’re moving the entire layer all the time, that will screw you 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. You’ll keep your HelloWorld layer as you’ve been doing, but you’ll make an additional layer called HudLayer to display your label. (Hud means heads up display).

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

So in Xcode click on File\New\File… and choose the C++ Class template under iOS\C and C++.

Click Next, name it HudLayer and click Create.

Open up HudLayer.h, and replace the contents with the following:

#ifndef __HUDLAYER_H__
#define __HUDLAYER_H__
 
#include "cocos2d.h"
 
using namespace cocos2d;
 
class HudLayer : public cocos2d::CCLayer
{
private:
    CCLabelTTF *_label;
 
public:
    // Method 'init' in cocos2d-x returns bool, instead of 'id' in cocos2d-iphone (an object pointer)
    virtual bool init();
 
    // there's no 'id' in cpp, so we recommend to return the class instance pointer
    static CCScene* scene();
 
    // a selector callback
    void menuCloseCallback(CCObject* pSender);
 
    // preprocessor macro for "static create()" constructor ( node() deprecated )
    CREATE_FUNC(HudLayer);
 
    void numCollectedChanged (int numCollected);
};
 
#endif // __HUDLAYER_H__

This creates a class that derives from CCLayer, the Cocos2D-X class that represent a layer. It creates a private instance variable to keep track of the label to display, and has a helper function to update the number to display in the label.

Next replace HudLayer.cpp with:

#include "HudLayer.h"
 
using namespace cocos2d;
 
bool HudLayer::init()
{
    if (CCLayer::init()) {
        CCSize winSize = CCDirector::sharedDirector()->getWinSize();
 
        _label = new CCLabelTTF();
        _label->initWithString("0", "Verdana-Bold", 18.0);
        _label->setColor(ccc3(0,0,0));
 
        int margin = 10;
        _label->setPosition(ccp(winSize.width - (_label->getContentSize().width/2) - margin, _label->getContentSize().height/2 + margin));
        this->addChild(_label);
    }
 
    return true;
}
 
void HudLayer::numCollectedChanged(int numCollected)
{
    CCString *labelCollected = new CCString();
    labelCollected->initWithFormat("%d",numCollected);
    _label->setString(labelCollected->getCString());
}

In init, you create a label and add it as a child of the layer. In numCollectedChanged, you update the text of the label.

Now let’s put this layer to use. In HelloWorldScene.h, import your new file at the top:

#include "HudLayer.h"

And declare two new private properties – for your new layer, and the amount of watermelons collected:

HudLayer *_hud;
int _numCollected;

In HelloWorldScene.cpp, add this code to the CCScene * scene() method right before the return:

HudLayer *hud = new HudLayer();
hud->init();
scene->addChild(hud);
layer->_hud = hud;

This creates your layer and adds it to the scene. It also sets the hud variable on your main layer to the newly created layer, so you have a way to talk to it.

Finally, add this code to setPlayerPosition, in the case where a tile is collectable:

 
_numCollected++;
_hud->numCollectedChanged(_numCollected);

Here you’ve modified the HelloWorldScene to call a method on the HudLayer when the count changes, so it can update the label accordingly.

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

Melon counter label

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 HelloWorldScene.cpp:

// At top of file
#include "SimpleAudioEngine.h"
 
// At top of init method
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("pickup.caf");
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("hit.caf");
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("move.caf");
CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("TileMap.caf");
 
// Inside setPlayerPosition, in case of collidable tile
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("hit.caf");
 
// Inside setPlayerPosition, in case of collectable tile
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("pickup.caf");
 
// Inside setPlayerPosition, right before setting player position
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("move.caf");

Now your 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-X at this point.

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

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

Jorge Jordán

Jorge Jordán is an indie iOS developer who spends his spare time trying to make his dreams and games come true at Insane Platypus Games. He likes to travel and to play bass and guitar.

You can find Jorge on Twitter, Facebook and LinkedIn.

User Comments

4 Comments

  • i impliment this tutorial but my game is crashed i don't lnow....why that haapant,i chaked out my code many times...

    but the game crashed with thre error
    >A this=(cocos2d::CCTMXLayer*) 0*0843e680
    patelvishu007
  • I implemented this game using Cocos2D-X version 2.1.5 and Xcode 5. The game runs fine, except the background music that plays only once. What should i do to keep the background music playing over and over? Thanks in advance.
    HumanOfPrey
  • Hi, I ran you example on both my android phone and my android tablet and just some parts of the map appear to have vertical black lines between tiles. I try changing the color of the spacing in the original image, and the black lines when running the game also change color. So is there a very small offset when cocos2dx is displaying the tile map? Or is it only a problem on android devices? since it seems alright that you are displaying well on your iPad(Unfortunately I don't have one so I am not able to test it on ios). Is there a way to avoid it? Thank you?
    cezheng
  • Great tutorial
    However I still have a some questions.
    Can you determine where a map is placed on the screen?
    How would this work if you used multiple maps?
    sadhi

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in September: iOS 8 App Extensions!

Sign Up - September

RWDevCon Conference?

We are considering having an official raywenderlich.com conference called RWDevCon in DC in early 2015.

The conference would be focused on high quality Swift/iOS 8 technical content, and connecting as a community.

Would this be something you'd be interested in?

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

... 49 total!

Update Team

Editorial Team

  • Ryan Nystrom

... 23 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • Victor Grushevskiy

... 33 total!

Subject Matter Experts

  • Richard Casey

... 4 total!