How to Make a Turn-Based Strategy Game – Part 1

This is a post by iOS Tutorial Team Member Pablo Ruiz, an iOS game developer, and co-founder and COO at InfinixSoft. Check out his blog, or follow him on Twitter. In this tutorial, you’ll learn how to create a turn-based strategy game for the iPhone. I’ve always liked this type of game, the Advance Wars […] By .

Leave a rating/review
Save for later
Share

This is a post by iOS Tutorial Team Member Pablo Ruiz, an iOS game developer, and co-founder and COO at InfinixSoft. Check out his blog, or follow him on Twitter.

In this tutorial, you’ll learn how to create a turn-based strategy game for the iPhone. I’ve always liked this type of game, the Advance Wars series for Nintendo DS being one of my favorites. Simplified versions of turn-based strategy games, such as Hero Academy, have lately become popular.

Some of the things you’ll learn include:

  • How to load a map with different types of terrains.
  • How to create different types of units with unique properties.
  • How to make them move and attack using a couple of AI pathfinding methods.

By the end of the tutorial you will have a basic, playable version of an Advance Wars-like game that can be played with a friend on the same device.

To make the most of this tutorial, you’ll need some Cocos2D experience and a beginning understanding of AI. You will also use Tiled for your maps, so a good understanding of this program and of tile maps in general is required.

In this first part of the two-part series, you’ll set everything up to enable you to load units for both teams and move them around.

Are you ready? Commence mission!

Getting Started: Game Preview

For those who haven’t played any of the Advance Wars games, the objective of most of the missions is to either eliminate all of the other player’s units, or to capture their headquarters (“HQ”) by moving a soldier over it. (It’s actually a bit more complicated than that, but we’ll simplify it for the sake of this tutorial.)

Here’s a screenshot of how the game will look after you’re finished:

As you can see in the image, each player controls their own units. Player 1 controls the red units and Player 2 the blue ones. As the tutorial progresses, you’ll create four types of units, each with different properties, such as their range of movement and damage capabilities.

Each player will also have an HQ they must protect. You’ll create the HQ buildings in such a way that it will be easy to extend the game to create other types of buildings, like factories, airports, etc.

Finally, in the above image you can also see different types of terrain: grass, mountains, water and forest. Each terrain type allows only certain types of movement from the units. For example, soldiers can walk over water (with reduced movement), but tanks cannot. Helicopters can fly anywhere.

While it’s not visible in the image, the final version of the game will have a bar at the top of the screen indicating whose turn it is, as well as an End Turn button to pass the turn to the other player. These will be added in the second part of the tutorial.

Getting Started: Assembling the Resources

To get started, download the tutorial starter project and extract it to your preferred location. This will be the starting point containing the basic Cocos2D project and most of the assets, including the free game art pack by Vicki and the tilemap used to create the map you see above.

I made this project by creating a new project from the Cocos2D basic template, which provides you with a working project that has a HelloWorldLayer with a label in the middle of the screen. Of course, we’ll not be using the HelloWorldLayer but will be creating our own game UI but this gives us a basic starter Cocos2D project that we can be certain works correctly.

In fact, it might be a good idea for you to open the project in Xcode at this point and compile it and run it to make sure that everything is working. Do note though that we’ve removed the actual “Hello World” text from the HelloWorldLayer and so, you’d only get a black screen (after the initial Cocos2D splash) if the project runs correctly. But if the projects does compile properly, then you’re set!

The project structure is pretty straightforward. Inside the TurnWars folder, you’ll find:

  • All the classes used in the game
  • The libs folder containing all the Cocos2D files
  • The Resources folder containing all the graphics

You’ll find that the tilemap is already done. Since this isn’t a Tiled tutorial, I won’t go over the details of how to create it, but I will mention things that are important for the project to work. For a very complete tutorial on how to create tilemaps using Tiled, I recommend this tutorial created by Ray.

Loading the Terrain

Time to start coding! The first step is to load the terrain layer contained within the tilemap. To get a better idea of how this works, open StageMap.tmx, which is inside Resources/Tiles, in Tiled, to see how it looks. (And if you don’t have have Tiled, you can download it from here.)

Viewing the Tile Map in Tiled

When you do this, you’ll notice the four types of terrain I said you would create. (Actually, there are a few more tiles than that, but the extras are just different versions of the water terrain, created for cosmetic reasons.)

Inspect the tiles (by right-clicking them in the tileset window and selecting “Tile Properties…”) and you will see two properties called MovementCost and TileType. I created these properties so that we can identify specific attributes for each tile (such as the type of tile) in our code later.

Tile Properties for Movement Cost

The StageMap.tmx file contains just five layers: the background layer, which contains these terrain tiles; two Units layers, for placing each player’s units; and two Buildings layers, for placing each player’s buildings.

Layers in Tile Map

Now that you have an understanding of how the tilemap is structured, switch over to the Xcode project for TurnWars.

You need to load the StageMap.tmx tilemap inside your HelloWorldLayer, which will be the main layer of the game.

To do this, open HelloWorldLayer.m and replace the init method with the following:

-(id)init {
    if ((self=[super init])) {
        self.isTouchEnabled = YES;
        [self createTileMap];
    }
    return self;
}

All we’re doing in the above code is to enable touch handling for our layer, HelloWorldLayer, and then calling the createTileMap method. But before adding the new method, we need to set up some instance variables to be used in the method. So add the following to HelloWorldLayer.h (within the @interface section’s curly brackets):

    CCTMXTiledMap *tileMap;
    CCTMXLayer *bgLayer;
    CCTMXLayer *objectLayer; 
    NSMutableArray * tileDataArray;

In the above code, we’ve defined several instance variables to point to the tile map, several of its layers, and an array to hold data about individual tiles on the background tile layer.

Of the above instance variable, tileDataArray needs to be set up as a property since we’ll need it later on. So add the following property declaration:

@property (nonatomic, assign) NSMutableArray *tileDataArray;

Quickly switch over to HelloWorldLayer.m and synthesise the tileDataArray property (below the @implementation line):

@synthesize tileDataArray;

While not strictly necessary, we should also add a method definition for createTileMap at this point so that we follow good coding practices. So switch back to HelloWorldLayer.h and add the following above the @end line:

-(void)createTileMap;

Now we get to the createTileMap method implementation, right? Well, we can certainly do that, but in order to ensure that our code will compile properly, we have do one more thing first – we have to add the TileData class. What’s the TileData class, you ask? TileData holds the information from each tile that you’ll need throughout the project – this is the information stored in the tile properties that we looked at earlier. The TileData class needs to keep track of:

  • Type: the tile’s type.
  • Movement Cost: how much it costs a unit to move through a tile.
  • Position: the tile’s position, which you’ll use later to determine a unit’s movement options.

Add TileData.h and TileData.m files to your project by creating a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class TileData, and make it a subclass of CCNode.

Replace the contents of TileData.h with the following:

#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "HelloWorldLayer.h"

@class HelloWorldLayer;

@interface TileData : CCNode {
    HelloWorldLayer * theGame;
    BOOL selectedForMovement;
    BOOL selectedForAttack;
    int movementCost;
    CGPoint position;
    TileData * parentTile;
    int hScore;
    int gScore;
    int fScore;
    NSString * tileType;
}

@property (nonatomic,readwrite) CGPoint position;
@property (nonatomic,assign) TileData * parentTile;
@property (nonatomic,readwrite) int movementCost;
@property (nonatomic,readwrite) BOOL selectedForAttack;
@property (nonatomic,readwrite) BOOL selectedForMovement;
@property (nonatomic,readwrite) int hScore;
@property (nonatomic,readwrite) int gScore;
@property (nonatomic,assign) NSString * tileType;

+(id)nodeWithTheGame:(HelloWorldLayer *)_game movementCost:(int)_movementCost position:(CGPoint)_position tileType:(NSString *)_tileType;
-(id)initWithTheGame:(HelloWorldLayer *)_game movementCost:(int)_movementCost position:(CGPoint)_position tileType:(NSString *)_tileType;
-(int)getGScore;
-(int)getGScoreForAttack;
-(int)fScore;

@end

Then, replace TileData.m with the following:

#import "TileData.h"

@implementation TileData

@synthesize parentTile,position,selectedForAttack,selectedForMovement,gScore,hScore,movementCost,tileType;

+(id)nodeWithTheGame:(HelloWorldLayer *)_game movementCost:(int)_movementCost position:(CGPoint)_position tileType:(NSString *)_tileType {
	return [[[self alloc] initWithTheGame:_game movementCost:_movementCost position:_position tileType:_tileType] autorelease];
}

-(id)initWithTheGame:(HelloWorldLayer *)_game movementCost:(int)_movementCost position:(CGPoint)_position tileType:(NSString *)_tileType {
	if ((self=[super init])) {
		theGame = _game;
        selectedForMovement = NO;
        movementCost = _movementCost;
        tileType = _tileType;
        position = _position;
        parentTile = nil;
        [theGame addChild:self];
	}
	return self;
}

-(int)getGScore {
    int parentCost = 0;
    if (parentTile) {
        parentCost = [parentTile getGScore];
    }
    return movementCost + parentCost;
    
}

-(int)getGScoreForAttack {
    int parentCost = 0;
    if(parentTile) {
        parentCost = [parentTile getGScoreForAttack];
    }
    return 1 + parentCost;
}

-(int)fScore {
	return self.gScore + self.hScore;
}

-(NSString *)description {
	return [NSString stringWithFormat:@"%@  pos=[%.0f;%.0f]  g=%d  h=%d  f=%d", [super description], self.position.x, self.position.y, self.gScore, self.hScore, [self fScore]];
}

@end

The above code is fairly straightforward since the TileData class, as the name implies, is mostly a data receptacle. You initialize it with various values for the class properties and have a couple of methods which calculate various scores based on the properties for the current tile and its parent.

If you’re wondering what the g, h, and f scores are, check out our Introduction to A* Pathfinding tutorial.

Now that we have a TileData class, we need to add an import statement to use it in HelloWorldLayer. In HelloWorldLayer.h add the following lines at the top of the file (below the existing #import line):

#import "TileData.h"

@class TileData;

At last we get to the createTileMap method itself! Add it to the end of HelloWorldLayer.m (before the @end):

-(void)createTileMap {
    // 1 - Create the map
    tileMap = [CCTMXTiledMap tiledMapWithTMXFile:@"StageMap.tmx"];        
    [self addChild:tileMap];
    // 2 - Get the background layer
    bgLayer = [tileMap layerNamed:@"Background"];
    // 3 - Get information for each tile in background layer
    tileDataArray = [[NSMutableArray alloc] initWithCapacity:5];
    for(int i = 0; i< tileMap.mapSize.height;i++) {
        for(int j = 0; j< tileMap.mapSize.width;j++) {
            int movementCost = 1;
            NSString * tileType = nil;
            int tileGid=[bgLayer tileGIDAt:ccp(j,i)];
            if (tileGid) {
                NSDictionary *properties = [tileMap propertiesForGID:tileGid];
                if (properties) {
                    movementCost = [[properties valueForKey:@"MovementCost"] intValue];
                    tileType = [properties valueForKey:@"TileType"];
                }
            }
            TileData * tData = [TileData nodeWithTheGame:self movementCost:movementCost position:ccp(j,i) tileType:tileType];
            [tileDataArray addObject:tData];
        } 
    }
}

In the code above:

  1. You first load the tilemap included in the project.
  2. Then, you retrieve the layer named "Background", which contains the terrain tiles for the map.
  3. Next, you create an NSMutableArray to contain the information of every tile in the map. That array is populated with TileData objects created with properties gathered from each tile in the tile layer, the same properties you looked at in Tiled earlier.

Finally, add the following line to the beginning of dealloc to release the NSMutableArray you create in the code above:

[tileDataArray release];

Go ahead and run the project now. You should see the tiles you saw before in Tiled appear on the screen.