Cocos2D-X Tile Map Tutorial: Part 1

Learn how to make a simple tile-based cross platform game in this Cocos2D-X tile map tutorial! By Jorge Jordán.

Leave a rating/review
Save for later
Share
Mmm, tasty melons!

Mmm, tasty melons!

This is a 2-part Cocos2D-X tile map tutorial series, where you’ll create a simple game about a ninja in the desert, in the search for a tasty watermelon.

Note that this tutorial is about Cocos2D-X, the cross-platform C++ port of Cocos2D-iPhone. So the code you’ll write here will work on iPhone, Android, and more!

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 this first part of the series, you’ll learn how to add a tile map to the game, scroll the map to follow the player, and use object layers. You’ll also learn how to use a map editor to create the tile maps themselves.

The second part of this series 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.

If you haven’t already, you may wish to start with the Cocos2D-X Tutorial for iOS and Android: Space Game, since that covers most of the basics that you’ll build upon here.

Ok, so let’s have some fun with tile maps!

Getting Started

For this tutorial you will need the latest version of Cocos2D-X installed (2.1.4 at the time of writing this tutorial). If you don’t have it already, download it now and install the templates by running the following commands in Terminal:

cd ~/Downloads/cocos2d-x-2.1.4
./install-templates-xcode.sh -f -u

Then create a new project in Xcode with the iOS\cocos2d-x\cocos2dx template. Click Next, name the project TileGame, select the options to make the project Universal, click Next and then click Create.

You are going to use ARC in this project so if this is the first time you have heard about ARC, I encorage you to take a look at the Beginning ARC series. By default the template isn’t set up to use ARC but, luckily, fixing it is really easy. Just go to Edit\Refactor\Convert to Objective-C ARC. Expand the dropdown and select only the files main.m, AppDelegate.cpp, HelloWorldScene.cpp, then click Check and finish the steps of the wizard.

Converto to ARC Menu - Select Classes

And that’s it! Build and Run and make sure everything still works OK – you should see the normal Hello World screen.

Next, download this zip file of resources for the game. The zip file contains the following:

  • A sprite you’ll use for your player object. This may look familiar from the How to Make A Simple iPhone Game with Cocos2D Tutorial!
  • Some sound effects made with the excellent cfxr utility that you’ll use in the tutorial.
  • Some background music made with Garage Band (see this post for more info).
  • The tile set you’ll be using – it actually comes with the map editor you’ll be using, but I thought it would be easier to include it with everything else.
  • Some additional “special” tiles explained a bit later.

Once you have the resources downloaded, unzip it and drag the TileGameResources folder to the Resources group in your project. In the project menu, right click the Resources group, and select Add Files to “TileGame”…. Select the Resources/TileGameResources folder, verify that Copy items into destination group’s folder (if needed) is checked and that Create groups for any added folders is selected, then click Finish.

If all works well, all of the files should be listed in your project.

Your project should look like this:

Resources Added

That’s it for now – time to have some fun and make your map!

Making a Map with Tiled

Cocos2D-X supports maps created with the open source Tiled Map Editor and saved in TMX format.

Visit the above link and download Tiled if you don’t have it already. At the time of writing this tutorial, the latest version is 0.9.0.

Then run Tiled, go to File\New, and fill in the dialog as follows:

New Tile map

In the orientation section, you can choose between Orthogonal (think: the Legend of Zelda) or Isometric (think: Disgaea). You’re going to pick Orthogonal here.

Next you get to set up the map size. Keep in mind that this is in tiles, not pixels. You are going to make a smallish sized map, so choose 50×50 here. Tiled will show you the total map size in pixels, at the bottom of the New Map dialog. This is calculated by multiplying the map size (50 tiles) by the tile size (32 px) in both height and width.

Finally you specify the tile width and height. What you choose here depends on the tile set that your artist will be making. For this tutorial, you are going to use some sample tiles that come with the Tiled editor which are 32×32, so choose that and click OK.

Next, you have to add the tile set that you’ll be using to draw your map. Click on Map in the menu bar, then chose New Tileset…, and fill in the dialog as follows:

New Tileset

To get the image, just click Browse and navigate to your TileGame/Resources/TileGameResources folder, and pick the tmw_desert_spacing.png file that you downloaded from the resource zip and added to your project. It will automatically fill out the name based on the filename.

You can leave the width and height as 32×32 since that is the size of the tiles. As for margin and spacing, this is what they mean:

  • Margin is how many pixels Tiled should skip (for both width and height) for the current tile before it starts looking for actual tile pixels.
  • Spacing is how many pixels Tiled should advance (for both width and height) after it reads the actual tile pixels to get to the next tile data.

If you take a look at tmw_desert_spacing.png, you’ll see that each tile has a 1px black border around it, which would explain the settings of margin and spacing as 1.

tmw_desert_zoom

Once you click OK, you will see the tiles show up in the Tilesets window. Now you can start drawing away! Simply click the Stamp Brush icon in the toolbar, then click a tile, then click anywhere on the map you’d like to place a tile.

Tiled with desert tile

So go ahead and draw yourself a map – be as creative as you’d like! Make sure to add at least a couple buildings on the map, because you’ll need something to collide into later!

Tile Map drawn

To make the drawing easier, you can take a look to the keyboard shortcuts. These are some of the most used:

  • You can drag a box around a series of tiles in the Tileset picker, to put down multiple adjacent tiles at the same time.
  • You can use the Bucket Fill Tool button in the toolbar to paint areas.
  • You can zoom in and out with View\Zoom In and View\Zoom Out.
  • The z key will rotate the tile when editing a map with the Stamp Brush tool.

There is a new feature you might have noticed Mini-map. This is an awesome feature, it let you see a (you guessed it) Mini-map! Take a look at my poor attempt at a maze in the bottom of the Mini-map below. The red box indicates the area you see in the main editing window.

New feature mini-map

Keep this Mini-map view in mind when you read about about scrolling in the next section.

Note that the resources for this tutorial comes the above map pre-made – feel free to use it if you’re feeling lazy. If you do so, you should open the map in Tiled and take a peek how it’s set up.

Once you’re done drawing the map, double click on the Tile Layer 1 in the Layers view, and change the name to Background. Then click File\Save and save the file to TileGame\Resources\TileGameResources in the TileGame project, and name the file TileMap.tmx, overriding the existing file.

You’re going to do some more stuff with Tiled later, but for now let’s get this map into the game!

Adding the Tiled Map to the Cocos2D-X Scene

Open up HelloWorldScene.h, and add right after the #include "cocos2d.h" line:

using namespace cocos2d;

This directs the compiler to use the cocos2d namespace so you don’t have to prefix everything with cocos2d:: here on out.

Then add these lines to the class definition, right after the opening curly brace:

private:
    CCTMXTiledMap *_tileMap;
    CCTMXLayer *_background;

This creates a private instance variable to keep track of the tile map itself, and another instance variable to keep track of the background layer in the map. You’ll learn more about tile map layers later.

Next, replace the contents of HelloWorldScene.cpp with the following:

#include "HelloWorldScene.h"

using namespace cocos2d;

CCScene* HelloWorld::scene()
{
    // 'scene' is an autorelease object
    CCScene *scene = CCScene::create();
    
    // 'layer' is an autorelease object
    HelloWorld *layer = HelloWorld::create();
    
    // add layer as a child to scene
    scene->addChild(layer);
    
    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    if ( !CCLayer::init() )
    {
        return false;
    }
    
    _tileMap = new CCTMXTiledMap();
    _tileMap->initWithTMXFile("TileMap.tmx");
    _background = _tileMap->layerNamed("Background");
    
    this->addChild(_tileMap);
    
    return true;
}

Here you make a call to the CCTMXTiledMap class, instructing it to create a map from the tile map file you created before with Tiled.

Some quick background: As CCTMXTiledMap is a CCNode, you can set its position, scale, etc. The children of the node are the layers of the tile map and there’s a helper function, layerNamed, where you can look them up by name – as you did to get the background. Each layer is a subclass of CCSpriteSheet for performance reasons – but this also means that you can only have one tileset per layer.

So the above source code saves a reference to the tile map and the background layer and then add the tile map to the HelloWorld layer.

And that’s it! Build and run your code on your iPad simulator, and you should see the bottom left corner of your map:

First run with tilemap

Not bad! But, for this to be a game, you need three things:

  1. a player
  2. a starting point to put the player
  3. to move your view so that you are looking at the player

And this is where it gets tricky. So let’s tackle this next!

Tiled Object Layers and Setting Tile Map Position

Tiled supports two kinds of layers:

  • Tile layers: These are which you’ve been working with so far.
  • Object layers: These allows you to draw boxes around portions of the maps to specify areas where things might happen. For example, you might make an area where monsters spawn, or an area that is deadly to enter. In this tutorial, you’re going to create an area for the spawn point of the player.

So go to the menu bar in Tiled and pick Layer\Add Object Layer, name the layer Objects. To insert an Object, select the Insert Rectangle(R) item from the toolbar. To know what layer is currently shown, you just look to the bottom left corner.

Current Layer

If you draw on the map, you’ll notice it doesn’t draw a tile. Instead, it draws a rectangle, which you can expand to cover multiple tiles or move around. In the latest version of tiled you can also draw other types of object shapes as ellipses, polygons and polylines.

You just need to select one tile for the player to start in. So choose somewhere on your map and click the tile. The size of the box doesn’t really matter, since you will just use the x, y coordinates. Note that this will be done for you already if you were lazy and used the sample map (although you will have to rename the provided object to “SpawnPoint” or you’ll get a crash – keep reading to learn how).

Tile map with spawn point

Then right click the gray object you just added, and select Object Properties…. Give it a name of SpawnPoint and click OK:

Spawn Point properties

In previous versions of Cocos2D-X you could set the Type of the object to a Cocos2D-X class name but was removed due to issues with it, so leave the Type blank, which will create a CCDictionary where you can access the various aspects of the object, including the x, y coordinates.

Save the map, go back to Xcode, and open HelloWorldScene.h. Add the following inside the private variables declaration, with the other private variables:

CCSprite *_player;

Next open HelloWorldScene.cpp and add the following inside the init method, after setting this->addChild(_tileMap):

CCTMXObjectGroup *objectGroup = _tileMap->objectGroupNamed("Objects");

if(objectGroup == NULL){
    CCLog("tile map has no objects object layer");
    return false;
}
    
CCDictionary *spawnPoint = objectGroup->objectNamed("SpawnPoint");

int x = ((CCString)*spawnPoint->valueForKey("x")).intValue();
int y = ((CCString)*spawnPoint->valueForKey("y")).intValue();
    
_player = new CCSprite();
_player->initWithFile("Player.png");
_player->setPosition(ccp(x,y));
    
this->addChild(_player);
this->setViewPointCenter(_player->getPosition());

There will be a warning on that last line – don’t worry, you’ll get to it in a second.

Let’s stop for a second and explain the bit about the object layer and object groups. First note that you retrieve object layers via the objectGroupNamed method on the CCTMXTiledMap object (rather than layerNamed). It returns a special CCTMXObjectGroup object.

Then objectGroup calls the objectNamed method to get a CCDictionary containing a bunch of useful info about the object, including x and y coordinates, width, and height. In this point of the tutorial all you need to care about is the x, y coordinates to set them as the position of your player sprite.

At the end of the code block you are setting the view to focus on where the player is. So now add the following line to HelloWorldScene.h:

// In the public section
void setViewPointCenter(CCPoint position);

and the new method to HelloWorldScene.cpp (at the bottom of the file is fine):

void HelloWorld::setViewPointCenter(CCPoint position) {
    
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    
    int x = MAX(position.x, winSize.width/2);
    int y = MAX(position.y, winSize.height/2);
    x = MIN(x, (_tileMap->getMapSize().width * this->_tileMap->getTileSize().width) - winSize.width / 2);
    y = MIN(y, (_tileMap->getMapSize().height * _tileMap->getTileSize().height) - winSize.height/2);
    CCPoint actualPosition = ccp(x, y);
    
    CCPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
    CCPoint viewPoint = ccpSub(centerOfView, actualPosition);
    this->setPosition(viewPoint);
}

Ok, here is the explanation of this block. Imagine this function is setting the center of a camera. It allows the user to pass in any x,y coordinate in the map here – but if you think about it there are some points that you don’t want to be able to show – for example you don’t want the screen to extend beyond the edges of the map (where it would just be blank space!).

For example, take a look at this diagram:

Diagram of tile map vs. viewport in Cocos2D-X

See how if the center of the camera is less than winSize.width/2 or winSize.height/2, part of the view would be off the screen? Similarly, is important to check the upper bounds as well, and that’s exactly what does setViewPointCenter.

Now so far this function has been treated as if it was setting the center of where a camera was looking. However… that isn’t exactly what it’s doing. There is a way in Cocos2D-X to manipulate the camera of a CCNode, but using that can make things more difficult than the solution you’re going to use: moving the entire layer instead.

Take a look at this diagram:

Diagram of how to move layer to fit within view in Cocos2D-X

Imagine a big world, and you’re looking at the coordinates from 0 to winSize.height/width. The center of your view is centerOfView, and you know where you want the center to be (actualPosition). So to get the actual position to match up to the center of view, all you do is slide the map down to match!

This is accomplished by subtracting the actual position from the center of view, and then setting the HelloWorld layer to that position.

Phew! Enough theory – is time to see it in action! Build and run the project, and if all goes well you should see your ninja in the scene, with the view moved to show him strutting his stuff!

Screen centered on the Ninja

Making the Ninja Move

This is a good start, but your ninja is just sitting there! And that’s not very ninja-like.

You will make the ninja move simply by moving him in the direction the user taps. Add the following code to the public section of HelloWorldScene.h:

void registerWithTouchDispatcher();
    
void setPlayerPosition(CCPoint position);
   
bool ccTouchBegan(CCTouch *touch, CCEvent *event);

void ccTouchEnded(CCTouch *touch, CCEvent *event);

Then open HelloWorldScene.cpp and add this to init, just before return true:

this->setTouchEnabled(true);

This sets the layer to be touch enabled so it pays attention to touch events. Next add these methods to the bottom of the file:

#pragma mark - handle touches

void HelloWorld::registerWithTouchDispatcher() {
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
}

bool HelloWorld::ccTouchBegan(CCTouch *touch, CCEvent *event)
{
    return true;
}

void HelloWorld::setPlayerPosition(CCPoint position) {
    _player->setPosition(position);
}

void HelloWorld::ccTouchEnded(CCTouch *touch, CCEvent *event)
{
    CCPoint touchLocation = touch->getLocationInView();
    touchLocation = CCDirector::sharedDirector()->convertToGL(touchLocation);
    touchLocation = this->convertToNodeSpace(touchLocation);
    
    CCPoint playerPos = _player->getPosition();
    CCPoint diff = ccpSub(touchLocation, playerPos);
    
    if ( abs(diff.x) > abs(diff.y) ) {
        if (diff.x > 0) {
            playerPos.x += _tileMap->getTileSize().width;
        } else {
            playerPos.x -= _tileMap->getTileSize().width;
        }
    } else {
        if (diff.y > 0) {
            playerPos.y += _tileMap->getTileSize().height;
        } else {
            playerPos.y -= _tileMap->getTileSize().height;
        }
    }
        
    // safety check on the bounds of the map
    if (playerPos.x <= (_tileMap->getMapSize().width * _tileMap->getTileSize().width) &&
        playerPos.y <= (_tileMap->getMapSize().height * _tileMap->getTileSize().height) &&
        playerPos.y >= 0 &&
        playerPos.x >= 0 )
    {
        this->setPlayerPosition(playerPos);
    }
    
    this->setViewPointCenter(_player->getPosition());
}

Here you override the registerWithTouchDispatcher method to register yourself to handle targed touch events. This will result in ccTouchBegan/ccTouchEnded methods being called (singular case), instead of ccTouchesBegan/ccTouchesEnded methods (plural case).

You may wonder what’s the difference between the singular case and the plural case. In this case it doesn’t matter either way. However, I wanted to introduce everyone to this method in case you hadn’t seen it already, because it has two significant advantages (these are listed verbatim from the cocos2D-X source):

  • “You don’t need to deal with NSSets, the dispatcher does the job of splitting them. You get exactly one UITouch per call.”
  • “You can *claim* a UITouch by returning YES in ccTouchBegan. Updates of claimed touches are sent only to the delegate(s) that claimed them. So if you get a move/ended/cancelled update you’re sure it’s your touch. This frees you from doing a lot of checks when doing multi-touch.”

Anyway, inside your ccTouchEnded location, you convert the location to view coordinates and then to GL coordinates as usual. What is new is you call this->convertToNodeSpace(touchLocation).
This is because the touch location will give you coordinates for where the user tapped inside the viewport (for example 100,100). But you might have scrolled the map a good bit so that it actually matches up to (800,800) for example. So calling this method offsets the touch based on how you have moved the layer.

Next, you figure out the difference between the touch and the player position. You have to choose a direction based on the touch, so first you decide whether to move up/down or side to side based on whichever is the greatest distance away. Then you just see if it’s positive or negative to move up or down.

You adjust the player position accordingly, and then set the viewpoint center to be the player position, which you already wrote in the last section!

Note you have to add a safety check to make sure you’re not moving your player off the map as well!

So Build and Run the project and try it out! You should now be able to tap the screen to move the ninja around.

The Ninja can run

Where To Go From Here?

That’s all for this part of the tutorial. At this point you should know the basics about creating maps and importing them into your game.

Here’s a sample project with the code you’ve developed so far.

In the part 2 of the tutorial you will learn how to add collision detection into the map to prevent your ninja from happily walking through the walls!

Jorge Jordán

Contributors

Jorge Jordán

Author

Over 300 content creators. Join our team.