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 […]

Version

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:

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:

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.

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:

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.

Attacking Other Units

It’s time to handle combat, our reason for playing! It will work as follows:

After moving a unit, if it is in combat range (immediately adjacent to an enemy unit, except for the cannon, which has a bigger range), units it can attack will be marked in red.

If the player selects one of the marked units, combat will be begin.

The attacker unit will shoot first, and if the defending unit survives the attack, it will retaliate.

The damage dealt by each unit will depend upon the types of units involved, pretty similar to a rock-paper-scissors game. (And no, the Lizard-Spock variant will not be allowed :D) Each unit type is strong against some units and weaker against others.

The first thing to be done is to check for enemy units nearby, immediately after a unit completes moving. To do this, switch to Unit.m and replace section #1 of popStepAndAnimate: with the following:

In the above code, in section 1.3 we check tileDataArray, which is an array of all tiles displayed in the background layer, to see if any tiles are marked as selectedForAttack. But how does a tile get set as selectedForAttack? That actually happens when we call markPossibleAction: in section 1.2 but since we haven’t actually implemented the attack action till now, we have to add this functionality to markPossibleAction:.

But before we make the changes to markPossibleAction:, we have to add some helper methods. Why? Because we have no way to check the board for tiles that contain an enemy unit at the moment. And as usual, when we need helper methods with an overall view of the gameplay area, we add the helper methods to HelloWorldLayer.

Now that we have the helper methods in place, we can call checkAttackTile:unitOwner: from Unit objects to check each tile adjacent to the moved unit.

We have to do that in several places in markPossibleAction: in Unit.m. First, replace the commented out else if condition about 9 lines down from the top (the one handling the kACTION_ATTACK possibility) with the following:

We also have to implement the doAttack: method, which is called when the Attack option is selected from the context menu. Currently, we have a placeholder method in Unit.m for it. Replace it with the following:

Finally, add the following to the end of unselectUnit to call the above method to unmark the tiles marked for attack:

[self unMarkPossibleAttack];

Build and run the program. If you move a unit near an enemy unit and select “attack” from the context menu, you should see the enemy unit get marked in red.

Right now, if you tap on the unit you want to attack, nothing happens. Plus, you might also notice that once a unit is selected to attack, you can’t end your turn either. Get ready to dive into the code that will handle the actual exchange of fire, damage calculation, and destruction of units (and of course, fix other issues like the one mentioned above).

To do this, we need to add a new helper method to HelloWorldLayer which helps you determine the damage from an attack. So add the following method definition to HelloWorldLayer.h:

Next, we need to add the method implementation to HelloWorldLayer.m but since our method will need to know about each specific Unit type in order to figure out the damage, we’re going to need to add imports for all the Unit types to the top of HelloWorldLayer.m:

Since we have all the pieces (or methods, if you will) in place for units attacking each other, we can handle an attack in the game. But first, we need to be able to determine whether we are selecting to move or attack using the currently selected unit. We already have instance variables in the Unit class for this but they weren't originally set up as properties and so we can't access them from HelloWorldLayer. So let's create the properties.

Finishing Touches to the Game

The final step in working out gameplay is to make something happen when there are no units left. If you remember from Part 1, this is one of the scenarios for winning.

How do we check this win scenario? It's pretty simple. After each kill, the program will check if the destroyed unit was that team's last unit. If it was, the program will display a congratulatory screen and restart the game.

And of course, this means adding a few more helper methods - to check if there are more enemy units and to display the congratulatory message. So switch to HelloWorldLayer.h and add the following method definitions:

In order to use the methods we defined above, we switch to Unit.m and add a call to checkForMoreUnits at the end of section #4 in dealDamage::

[theGame checkForMoreUnits];

If you're wondering why we add the call to checkForMoreUnits to section #4, and are confused because section #6 calls attackedBy: on the attacker, do note that when attackedBy: is called on the attacker, that in turn again calls dealDamage: for the attacking unit. So in this secondary run you'll be calling checkForMoreUnits again.

Basically, when one unit attacks another, checkForMoreUnits

Adding Buildings

I mentioned at the beginning of the tutorial that there would be two ways to win a game: by eliminating all units from one of the teams, or by having a soldier unit capture the other player's HQ. It's time to implement the latter scenario.

First, add a new class, Building, which will be the base for other buildings. In this tutorial you'll create a simple HQ, but you can use the same technique to create other types of buildings to make your gameplay more interesting.

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

Do note that the logic for buildingInTile: always returns the first building that it finds on a given tile. This could be an issue if two buildings existed on the same tile but since that's not how our game works, this is not something we have to worry about.

The final step is to make sure the game ends when a soldier unit moves over an enemy HQ. Just open Unit.m and add the following code inside the "if" statement for section #3 in doStay:

// Get the building on the current tile
Building *buildingBelow = [theGame buildingInTile:[theGame getTileData:[theGame tileCoordForPosition:mySprite.position]]];
// Is there a building?
if (buildingBelow) {
// Is the building owned by the other player?
if (buildingBelow.owner != self.owner) {
NSLog(@"Building captured!!!");
// Show end game message
[theGame showEndGameMessageWithWinner:self.owner];
}
}

That's it! Compile and run the project, and you should see the HQs appear on both sides of the screen. Try moving a soldier unit onto the enemy's HQ. The game should end at that point.

Music and Sounds

You're almost done with the project. Let's add some music and sound effects to jazz things up and you're set! In case you're curious, I got the music from Incompetech.com and made the sound effects with CXFR.

First, import SimpleAudioEngine at the top of HelloWorldLayer.m:

#import "SimpleAudioEngine.h"

Now we can use SimpleAudioEngine to play the background music. Add the following line at the end of init (after [self addMenu]):

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 […]