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
You are currently viewing page 2 of 5 of this article. Click here to view the first page.

Handling Turns

If you play with the latest build, you may notice that now, once you move a unit, you can’t move it again, ever. You may have also noticed that since you began the project, there hasn’t been any way to switch between players – you can move any player’s units at any time!

While these aspects may make for a surreal and interesting game, the topic of this tutorial is, after all, turn-based gaming. So, to stick with the program, you’ll add a HUD at the top of the screen to indicate which player’s turn it is. The HUD will also provide a button to pass the turn to the other player. And of course, players will only be able to move their own units during their turn.

To get going on these additions, let’s add a few more instance variables to keep track of things. Switch to HelloWorldLayer.h and add the following:

CCMenuItemImage *endTurnBtn;
CCLabelBMFont *turnLabel;

Then, add the following method definitions:

-(void)addMenu;
-(void)doEndTurn;
-(void)setPlayerTurnLabel;
-(void)showEndTurnTransition;
-(void)beginTurn;
-(void)removeLayer:(CCNode *)n;
-(void)activateUnits:(NSMutableArray *)units;

Now switch to HelloWorldLayer.m, add the following lines at the end of init (after loading units):

// Set up turns
playerTurn = 1;
[self addMenu];

Next, add all the new methods you defined above to the end of the file:

// Add the user turn menu
-(void)addMenu {
    // Get window size
    CGSize wins = [[CCDirector sharedDirector] winSize];
    // Set up the menu background and position
    CCSprite * hud = [CCSprite spriteWithFile:@"uiBar.png"];
    [self addChild:hud];
    [hud setPosition:ccp(wins.width/2,wins.height-[hud boundingBox].size.height/2)];
    // Set up the label showing the turn 
    turnLabel = [CCLabelBMFont labelWithString:[NSString stringWithFormat:@"Player %d's turn",playerTurn] fntFile:@"Font_dark_size15.fnt"];
    [self addChild:turnLabel];
    [turnLabel setPosition:ccp([turnLabel boundingBox].size.width/2 + 5,wins.height-[hud boundingBox].size.height/2)];
    // Set the turn label to display the current turn
    [self setPlayerTurnLabel];
    // Create End Turn button
    endTurnBtn = [CCMenuItemImage itemFromNormalImage:@"uiBar_button.png" selectedImage:@"uiBar_button.png" target:self selector:@selector(doEndTurn)];
    CCMenu * menu = [CCMenu menuWithItems:endTurnBtn, nil];
    [self addChild:menu];
    [menu setPosition:ccp(0,0)];
    [endTurnBtn setPosition:ccp(wins.width - 3 - [endTurnBtn boundingBox].size.width/2, wins.height - [endTurnBtn boundingBox].size.height/2 - 3)];
}

// End the turn, passing control to the other player
-(void)doEndTurn {
    // Do not do anything if a unit is selected
    if (selectedUnit)
        return;
    // Switch players depending on who's currently selected
    if (playerTurn ==1) {
        playerTurn = 2;
    } else if (playerTurn ==2) {
        playerTurn = 1;
    }
    // Do a transition to signify the end of turn
    [self showEndTurnTransition];
    // Set the turn label to display the current turn
    [self setPlayerTurnLabel];
}

// Set the turn label to display the current turn
-(void)setPlayerTurnLabel {
    // Set the label value for the current player
    [turnLabel setString:[NSString stringWithFormat:@"Player %d's turn",playerTurn]];
    // Change the label colour based on the player
    if (playerTurn ==1) {
        [turnLabel setColor:ccRED];
    } else if (playerTurn == 2) {
        [turnLabel setColor:ccBLUE];
    }
}

// Fancy transition to show turn switch/end
-(void)showEndTurnTransition {
    // Create a black layer
    ccColor4B c = {0,0,0,0};
    CCLayerColor *layer = [CCLayerColor layerWithColor:c];
    [self addChild:layer z:20];
    // Add a label showing the player turn to the black layer
    CCLabelBMFont * turnLbl = [CCLabelBMFont labelWithString:[NSString stringWithFormat:@"Player %d's turn",playerTurn] fntFile:@"Font_silver_size17.fnt"];
    [layer addChild:turnLbl];
    [turnLbl setPosition:ccp([CCDirector sharedDirector].winSize.width/2,[CCDirector sharedDirector].winSize.height/2)];
    // Run an action which fades in the black layer, calls the beginTurn method, fades out the black layer, and finally removes it
    [layer runAction:[CCSequence actions:[CCFadeTo actionWithDuration:1 opacity:150],[CCCallFunc actionWithTarget:self selector:@selector(beginTurn)],[CCFadeTo actionWithDuration:1 opacity:0],[CCCallFuncN actionWithTarget:self selector:@selector(removeLayer:)], nil]];
}

// Begin the next turn
-(void)beginTurn {
    // Activate the units for the active player
    if (playerTurn ==1) {
        [self activateUnits:p2Units];
    } else if (playerTurn ==2) {
        [self activateUnits:p1Units];
    }
}

// Remove the black layer added for the turn change transition
-(void)removeLayer:(CCNode *)n {
    [n.parent removeChild:n cleanup:YES];
}

If you look at the method definitions we added to HelloWorldLayer.h and compare them against the methods we added just now to HelloWorldLayer.m, you’ll notice that we’re missing one method – activateUnits:. There’s a reason for this. The activateUnits: activates all the units belonging to the currently active player. However, we have no method in the Unit class to do this.

So let’s switch to Unit.h and add a method definition for a new helper method:

-(void)startTurn;

And the next step, of course, is to add the method implementation to the end of Unit.m:

// Activate this unit for play
-(void)startTurn {
    // Mark the unit as not having moved for this turn
    movedThisTurn = NO;
    // Mark the unit as not having attacked this turn
    attackedThisTurn = NO;
    // Change the unit overlay colour from gray (inactive) to white (active)
    [mySprite setColor:ccWHITE];
}

Now that we have the necessary helper method, we can switch back to HelloWorldLayer.m and add the activateUnits: implementation to the end of the file:

// Activate all the units in the specified array (called from beginTurn passing the units for the active player)
-(void)activateUnits:(NSMutableArray *)units {
    for (Unit *unit in units) {
        [unit startTurn];
    }
}

As you’ll notice, the method gets passed the unit array for the currently active player and the method loops through each of the units in the array, activating each in turn using the startTurn method that we implemented above.

Are we done? Well, almost :] The code can be built and run at this point and it would mostly work as expected. However, there’s one issue – there is nothing in the code to prevent player 1’s (or 2’s) units being selected when it’s not his/her turn since we don’t set the units not in play to deactivated status.

We can fix this pretty easily. Open Unit.m and add the following lines of code to the top of ccTouchBegan::

// Was a unit belonging to the non-active player touched? If yes, do not handle the touch
if (([theGame.p1Units containsObject:self] && theGame.playerTurn == 2) || ([theGame.p2Units containsObject:self] && theGame.playerTurn == 1)) 
    return NO;

All we do is check if the touched unit belongs to player 1 and if the active player is player 2 (or vice versa) since if the touched unit does not belong to the active player, we should not do anything.

Try building and running the project now. You should see a beautiful button in the top-right corner that allows the current player to pass his/her turn. A player can move each of their units once during their turn, then the unit becomes inactive till the next turn. Soon, they’ll be able to attack as well!

When you end the turn, you should see the transition effect in play.