How To Make a Tile-Based Game with Cocos2D 2.X
In this 2-part tutorial, you’re going to learn how to make a tile-based game with Cocos2D and the Tiled map editor. You will do so by creating a simple tile-based game where a ninja explores a desert in search of tasty watermelon things! By Charlie Fulton.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Make a Tile-Based Game with Cocos2D 2.X
25 mins
Adding the Tiled Map to our Cocos2D Scene
First thing first, right click on the TileGameResources group, click “Add\Existing Files…” and add the new TileMap.tmx file you just created to your project.
Next let’s turn off Retina Display support because we don’t have any retina sized art.
Make the following changes to AppDelegate.m:
// comment out these lines in the -application:didFinishLaunchingWithOptions: method
// this should be lines 93-94
// if( ! [director_ enableRetinaDisplay:YES] )
// CCLOG(@"Retina Display Not supported");
Open up HelloWorldLayer.h, and replace the contents with the following:
#import <GameKit/GameKit.h>
// When you import this file, you import all the cocos2d classes
#import "cocos2d.h"
// HelloWorldLayer
@interface HelloWorldLayer : CCLayer
{
}
// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
@end
Replace the contents of HelloWorldLayer.m with the following:
#import "HelloWorldLayer.h"
#import "AppDelegate.h"
@interface HelloWorldLayer()
@property (strong) CCTMXTiledMap *tileMap;
@property (strong) CCTMXLayer *background;
@end
@implementation HelloWorldLayer
// Helper class method that creates a Scene with the HelloWorldLayer as the only child.
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(id) init
{
if( (self=[super init]) ) {
self.tileMap = [CCTMXTiledMap tiledMapWithTMXFile:@"TileMap.tmx"];
self.background = [_tileMap layerNamed:@"Background"];
[self addChild:_tileMap z:-1];
}
return self;
}
@end
Here we make a call to the CCTMXTiledMap file, instructing it to create a map from the map file we created with Tiled.
Some quick background on CCTMXTiledMap. It’s a CCNode, so you can set its position, scale, etc. The children of the node are the layers of the map, and there’s a helper function where you can look the up by name – which we do here 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 all we do here is save a reference to the tile map and the background layer, then add the tile map to the HelloWorld layer.
And that’s it! Compile and run the code, and you should see the bottom left corner of your map:
Not bad! But for this to be a game, we need three things: a) a player, b) a starting point to put the player, and c) to move our view so that we 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 (which is what we’ve been working with so far), and object layers.
Object layers allow 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 our case, we’re going to create an area for our “spawn point” for our player.
So go to the menu bar in Tiled and pick “Layer\Add Object Layer…”, name the layer “Objects”, and click OK. To insert an Object, select the Insert Rectangle(R) item from the toolbar. Your current layer is shown in the bottom left corner.
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.
We just want 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 we’ll just be using the x, y coordinates.
Then right click the gray object you just added, and click “Object Properties”. Give it a name of “SpawnPoint” and click OK:
Supposedly, you can get fancy here and set the Type of the object to a Cocos2D class name and it will create an object of that type for you (such as CCSprite), but I couldn’t find where it was doing that in the source code. Update: Tyler from GeekAndDad.com pointed out that the code used to be in a previous version of Cocos2D, but was removed due to issues with it a while back.
Anyway – we’re just going to leave the type blank, which will create an NSMutableDictionary for us where we can access the various aspects of the object, including the x, y coordinates.
Save the map and go back to Xcode. Make the following changes to HelloWorldLayer.m:
// After the interface declaration, with the other private properties
@property (strong) CCSprite *player;
// Inside the init method, after setting self.background
CCTMXObjectGroup *objectGroup = [_tileMap objectGroupNamed:@"Objects"];
NSAssert(objectGroup != nil, @"tile map has no objects object layer");
NSDictionary *spawnPoint = [objectGroup objectNamed:@"SpawnPoint"];
int x = [spawnPoint[@"x"] integerValue];
int y = [spawnPoint[@"y"] integerValue];
_player = [CCSprite spriteWithFile:@"Player.png"];
_player.position = ccp(x,y);
[self addChild:_player];
[self setViewPointCenter:_player.position];
Ok 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.
We then call the objectNamed method on the CCTMXObjectGroup to get a NSMutableDictionary containing a bunch of useful info about the object, including x and y coordinates, width, and height. In this case all we care about is the x,y coordinates, so we pull those out and set that as the position of our player sprite.
At the end we want to set the view to focus on where the player is. So now add the following new method to HelloWorldLayer.m (anywhere in the file is fine now in Objective-C):
- (void)setViewPointCenter:(CGPoint) position {
CGSize winSize = [CCDirector sharedDirector].winSize;
int x = MAX(position.x, winSize.width/2);
int y = MAX(position.y, winSize.height/2);
x = MIN(x, (_tileMap.mapSize.width * _tileMap.tileSize.width) - winSize.width / 2);
y = MIN(y, (_tileMap.mapSize.height * _tileMap.tileSize.height) - winSize.height/2);
CGPoint actualPosition = ccp(x, y);
CGPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
CGPoint viewPoint = ccpSub(centerOfView, actualPosition);
self.position = viewPoint;
}
Ok, let’s explain this a bit too. Imagine this function is setting the center of a camera. We allow the user to pass in any x,y coordinate in the map here – but if you think about it there are some points that we don’t want to be able to show – for example we 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:
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, we need to check the upper bounds as well, and that’s exactly waht we do here.
Now so far we’ve been treating this function as if it was setting the center of where a camera was looking. However… that isn’t exactly what we’re doing. There is a way in Cocos2D to manipulate the camera of a CCNode, but using that can make things more difficult than the solution we’re going to use: moving the entire layer instead.
Take a look at this diagram:
Imagine a big world, and we’re looking at the coordinates from 0 to winSize.height/width. The center of our view is centerOfView, and we know where we want the center to be (actualPosition). So to get the actual position to match up to the center of view, all we 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 – let’s see it in action! Compile 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!