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

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. Welcome to the second half of the tutorial series that walks you through a basic turn-based strategy game for the iPhone! In the first part […] By .

5 (1) · 1 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.

Welcome to the second half of the tutorial series that walks you through a basic turn-based strategy game for the iPhone!

In the first part of the series, we covered how to load the tile map, initialize military units (soldiers, cannons and helicopters), and how to program their movement on the tile map using the A* pathfinding algorithm.

Now in this second and final part of the series, you’ll enable the units to fulfill their mission in life – make war!

You will add buildings to the map and code multiple ways to win the game. You’ll also incorporate the logic for switching turns between players, as well as some nice finishing details like adding music and sound effects.

Best of all, this project is ripe for expansion and customization so you can make your own Turn-Based Strategy game!

This project starts where we left off in the last tutorial, so make sure you have the project where we left it off if you don’t have it already.

Forward march!

Adding a Context Menu

Before turning to the combat portion of the game, you should spend a few minutes enriching the gameplay experience by adding a context menu. After a player has moved a unit, this menu will prompt them to choose whether the unit should stay in the new location without attacking, revert to its previous location, or, if possible, attack nearby enemy units.

First, as we usually have to in order to support new functionality, we have to add some helper methods. And of course, our helper methods need some instance variables in place to work correctly. So, open the project in Xcode, switch to HelloWorldLayer.h and add the following instance variables:

CCMenu *actionsMenu;
CCSprite *contextMenuBck;

In the above, we’ve defined a CCMenu instance for our context menu and have also created a CCSprite instance which will point to the background for our menu.

We need to make actionsMenu and the previously defined selectedUnit into properties since we’ll need to refer to them from the Unit class later on in our code. So add the following:

@property (nonatomic, assign) Unit *selectedUnit;
@property (nonatomic, assign) CCMenu *actionsMenu;

Of course, we also need to synthesise the above, so switch over to HelloWorldLayer.m add the following at the top of the file:

@synthesize selectedUnit;
@synthesize actionsMenu;

Switch back to HelloWorldLayer.h and add method definitions for the two helper methods we’re going to implement:

-(void)showActionsMenu:(Unit *)unit canAttack:(BOOL)canAttack;
-(void)removeActionsMenu;

Next, switch to HelloWorldLayer.m and add the following methods at the end of the file:

-(void)showActionsMenu:(Unit *)unit canAttack:(BOOL)canAttack {
    // 1 - Get the window size
    CGSize wins = [[CCDirector sharedDirector] winSize];
    // 2 - Create the menu background
    contextMenuBck = [CCSprite spriteWithFile:@"popup_bg.png"];
    [self addChild:contextMenuBck z:19];
    // 3 - Create the menu option labels
    CCLabelBMFont * stayLbl = [CCLabelBMFont labelWithString:@"Stay" fntFile:@"Font_dark_size15.fnt"];
    CCMenuItemLabel * stayBtn = [CCMenuItemLabel itemWithLabel:stayLbl target:unit selector:@selector(doStay)];
    CCLabelBMFont * attackLbl = [CCLabelBMFont labelWithString:@"Attack" fntFile:@"Font_dark_size15.fnt"];
    CCMenuItemLabel * attackBtn = [CCMenuItemLabel itemWithLabel:attackLbl target:unit selector:@selector(doAttack)];
    CCLabelBMFont * cancelLbl = [CCLabelBMFont labelWithString:@"Cancel" fntFile:@"Font_dark_size15.fnt"];
    CCMenuItemLabel * cancelBtn = [CCMenuItemLabel itemWithLabel:cancelLbl target:unit selector:@selector(doCancel)];
    // 4 - Create the menu
    actionsMenu = [CCMenu menuWithItems:nil];
    // 5 - Add Stay button
    [actionsMenu addChild:stayBtn];
    // 6 - Add the Attack button only if the current unit can attack
    if (canAttack) {
        [actionsMenu addChild:attackBtn];
    }
    // 7 - Add the Cancel button
    [actionsMenu addChild:cancelBtn];
    // 8 - Add the menu to the layer
    [self addChild:actionsMenu z:19];
    // 9 - Position menu
    [actionsMenu alignItemsVerticallyWithPadding:5];
    if (unit.mySprite.position.x > wins.width/2) {
        [contextMenuBck setPosition:ccp(100,wins.height/2)];
        [actionsMenu setPosition:ccp(100,wins.height/2)];
    } else {
        [contextMenuBck setPosition:ccp(wins.width-100,wins.height/2)];
        [actionsMenu setPosition:ccp(wins.width-100,wins.height/2)];
    }
}

-(void)removeActionsMenu {
    // Remove the menu from the layer and clean up
    [contextMenuBck.parent removeChild:contextMenuBck cleanup:YES];
    contextMenuBck = nil;
    [actionsMenu.parent removeChild:actionsMenu cleanup:YES];
    actionsMenu = nil;
}

The two methods in the above code handle the creation of the context menu and the disposal of the menu when it is no longer needed. Right now the menu only lets the player cancel or confirm their movement, but it will eventually provide a third option for attacking an enemy unit. You’ll add that capability soon.

Now switch to Unit.m and find popStepAndAnimate: and replace the first if statement with the following:

// 1 - Check if there remain path steps to go through
if ([movementPath count] == 0) {
    moving = NO;
    [self unMarkPossibleMovement];
    BOOL enemiesAreInRange = NO;
    // You'll determine later if there is a nearby enemy to attack.
    [theGame showActionsMenu:self canAttack:enemiesAreInRange];
    return;
}
// 2 - Get the next step to move toward

We call the showActionsMenu:canAttack: helper method we just implemented to display the context menu. Currently, the enemiesAreInRange value is hardcoded to NO and so the Attack option in the menu will not be shown.

Add the following lines at the top of ccTouchBegan::

// If the action menu is showing, do not handle any touches on unit
if (theGame.actionsMenu)
    return NO;
// If the current unit is the selected unit, do not handle any touches
if (theGame.selectedUnit == self) 
    return NO;
// If this unit has moved already, do not handle any touches
if (movedThisTurn) 
    return NO;

Next, we need to add the methods which handle the action selected via the context menu. However, one of the actions will require us to detect whether the current unit is a Soldier unit (you’ll understand why, when we get to that bit of code). So, we need to import the header for the Soldier unit at the top of Unit.m:

#import "Unit_Soldier.h"

Finally, we add the following code to the end of the file to implement the context menu actions:

// Stay on the current tile
-(void)doStay {
    // 1 - Remove the context menu since we've taken an action
    [theGame removeActionsMenu];
    movedThisTurn = YES;
    // 2 - Turn the unit tray to indicate that it has moved
    [mySprite setColor:ccGRAY];
    [theGame unselectUnit];
    // 3 - Check for victory conditions
    if ([self isKindOfClass:[Unit_Soldier class]]) {
        // If this is a Soldier unit and it is standing over an enemy building, the player wins.
        // We'll handle this situation in detail later 
    }
}

// Attack another unit
-(void)doAttack {
    // You'll handle attack later
}

// Cancel the move for the current unit and go back to previous position
-(void)doCancel {
    // Remove the context menu since we've taken an action
    [theGame removeActionsMenu];
    // Move back to the previous tile
    mySprite.position = [theGame positionForTileCoord:tileDataBeforeMovement.position];
    [theGame unselectUnit];
}

And that’s it!

Build and run the project. You should be able to see this menu and interact with it after moving any unit.