Tower Defence: Part 3

Writing Tower Defence: Part 3

This is the third and final in a series of articles explaining the issues involved in writing a simple tower defence game. You can find the completed game on Kongregate, here, the first article here, and the second here. If you wish to follow along and have not read the first article, you can start with this archive.

This article adds some 'finishing touches' that will take a technical demo (which is effectively what you have by the end of article 2) and make it a game.

I. Information

One important feature which turns a technical test into a game is giving the player the information he needs to judge whether or not he is doing well. In the finished game, we want to be able to see which wave we are on, how many lives are left and how much money we have left. We will also add score later, so let's put that in now too, to make a nice 2×2 information panel. One way to do it would be to add the indentation to the main image, as we have done for the tower buttons. However, this time I will show how to make a more dynamic panel. First, add the panel and the four text fields to the XML; replace the current one line InfoPanel clip with:

			<clip id="InfoPanel" class="InfoPanel">
				<frame>
					<place id="Text" name="wavetext" depth="17000" x="5" y="0"/>
					<place id="Text" name="scoretext" depth="17001" x="5" y="15"/>
					<place id="Text" name="moneytext" depth="17002" x="75" y="0"/>
					<place id="Text" name="livestext" depth="17003" x="75" y="15"/>
				</frame>
			</clip>

And create an InfoPanel.as:

// Basic gameplay information
class InfoPanel extends MovieClip {
	var wavetext, scoretext, moneytext, livestext; // subelements

	function onLoad(){
		lineStyle(1, 0, 40);
		beginFill(0x77575B);
		moveTo(0,30);
		lineTo(0,0);
		lineTo(139,0);
		lineStyle(1, 0xffffff, 40);
		lineTo(139,30);
		lineTo(0,30);
		endFill();

		wavetext.selectable = scoretext.selectable = moneytext.selectable = livestext.selectable = false;
	}

}

We could put all the fields that we want to display into InfoPanel as pseudo-properties (with a Set method that would cause the text to update), and if this were C# with syntactically pleasant properties I would probably have done that. But the other option (and the one I have taken) is to keep that information in the Map class, and update the text from there.

Three of the relevant variables are already in Map: lives, waves and money. Add a declaration for score, and in initGame, set it to zero (score = 0;). Where you have declared the Flash elements (creepContainer, towerContainer, overlay and towertext), add a declaration for infoPanel. Because infopanel is not a child of the map, we need to assign it at some point after both the map and the info panel have loaded: App.onLoad is the place where we know all our elements have loaded, so we should do it there. In App.onLoad, add the line:

		map.infoPanel = infopanel;

Now, in Map.initGame we need to update the information panel to show the 'start of game' values. Add some lines to that function to do so:

		infoPanel.scoretext.text = "Pts: " + score;
		infoPanel.wavetext.text = "Wave 0";
		infoPanel.moneytext.text = "$: " + money;
		infoPanel.livestext.text = "Lives: " + lives;

In part 2 we already added the support for lives in the map, and left a 'hanging reference' to a Map.changeLives method in the creep's think function. Most of the time changeLives will be simple (update the variable and the info panel), and when we reach 0 the game should end.

	function changeLives(dlives){
		lives += dlives; infoPanel.livestext.text = 'Lives: '+lives;
		if(lives <= 0) endGame();
	}

lives should never be less than zero, but it is good 'paranoid coding' practice to do something sensible with the impossible case if it is easy to do so. endGame doesn't exist yet; we'll add that in the next section of this article. Changing the score and money is also simple:

	function changeScoreAndMoney(dscore, dmoney){
		score += dscore; money += dmoney;
		infoPanel.scoretext.text = 'Pts: ' + score;
		infoPanel.moneytext.text = '$: ' + money;
	}

At the moment the only place we are doing anything with money is Map.endPlace. On the last line, where we are currently subtracting from the money variable directly, replace the line with:

		changeScoreAndMoney(0, -placing.data[placing.level].cost);

Yes, you were already spending that money (you may have noticed in previous tests that you can only place 5 towers; that's because you start with 250 money and each tower costs 50), but now when you place a tower the display should update. At this point we want to add the feature that killing a creep gets you money and score. For this TD, you get the same money and score from a creep, which is determined by the cash property that gets attached to the creep when it is spawned (see article 2, section II). In the creep's think function, in the section to do with creep death (if(hp <= 0) etc), add a line (anywhere before the return statement):

			map.changeScoreAndMoney(cash, cash);

If you play the game now, you should see the score and money counting up as you kill creeps, and lives count down when creeps get through. You can build more towers when the money gets high enough.

II. End Conditions and Game States

At the moment, the game never ends, even though the lives counter reaches zero. In most TD games, the end condition is twofold: a 'win' state if you defeat all the waves, and a 'lose' state if too many creeps get through. In Infinite TD, there is not a scripted set of waves, so there is no 'win' state; the player's objective is simply to defer the 'loss' for as long as possible. Each creep which 'escapes' will cost one life, and when your lives reach zero the game is over.

As well as 'in play' and 'over', there may be other states (for example 'paused') that the game can be in, so it should be represented by an enumeration. In ActionScript, this means an array that we can select out of, and a variable to hold the current status:

	var states = {noGame: -1, paused: 0, inPlay: 1, end: 50};
	var status = states.noGame;

The game logic should only run if we are currently in the state 'inPlay'. Almost all our game logic is in map.onEnterFrame, so add this one line to the top of that function:

		if(status != states.inPlay) return;

This is one big advantage of using think functions and not onEnterFrame methods for all the creeps and towers that we add: because everything is called from onEnterFrame, we only have to consider this issue once. Or ... actually twice. There is one other place where we are currently performing time-based actions that have an effect on the game: App.onEnterFrame. The frame number is what makes a lot of actions in the game happen (waves, spawn and tower firing) and that must not be updated when the game is paused. Replace App.onEnterFrame with this:

	function onEnterFrame(){
		if(map.status == map.states.inPlay) frameno++;
	}

Then, in Map.initGame, add the line:

	status = states.inPlay;

And to finish off the code in Map to handle pausing, add a pause method which can be called to stop or resume the action:

	function pause(paused){
		status = paused ? states.paused : states.inPlay;
		pausetext.text = paused ? 'Paused!' : '';
	}

We are now in a position to write the endGame method that is to be run when the player runs out of lives (see section I): it sets the game state to a 'game over' state, and displays the game over screen:

	function endGame(){
		if((status < states.paused) || (status > states.inPlay)) return;
		status = states.end;
		var gameover = _root.app.gameover;
		gameover.paneltext.text = 'Final score: ' + score +
			'\nSurvived to wave: '+wave +
			'\nTowers built: '+towers.length;
		gameover._visible = true;
		this.placing = null;
	}

At this point, you should be able to play the game, and when you run out of lives the board will 'freeze'. This is because the game over screen doesn't yet exist; so far we've written the background code to handle a changing game state, but we haven't added the UI elements needed to pause the game, or to display the game over screen and get back to the menu. Right back at the start of article I we added a stub in the XML for the game over screen (the clip called GameOver). Replace that with these declarations:

			<clip id="GameOverImage" import="res/gameover.png"/>
			<clip id="GameOver" class="GameOver">
				<frame>
					<place id="GameOverImage" name="image" depth="16384" x="0" y="0"/>
					<place id="PanelText" name="paneltext" depth="16385" x="110" y="160"/>
					<place id="MenuButton" name="tomenu" depth="16386" x="200" y="340"/>
				</frame>
			</clip>

(If not using SWFmill: create a GameOver symbol with the background image, and containing a text field and an instance of the MenuButton symbol, which is already defined.)

Note that the resource we're using here (gameover.png) is mostly semi-transparent. Having the game over screen overlay the play area is as simple as that. The text will be updated from the Map code we just wrote, but we still need a GameOver.as to handle the events on that button (and some other initialisation issues):

class GameOver extends MovieClip {
	var paneltext, tomenu;
	function onLoad(){
		_visible = false;

		tomenu.init("Return to menu");
		var gameover = this;
		tomenu.onRelease = function(){
			_root.app.menu._visible = true;
			gameover._visible = false;
		}
		paneltext.selectable = false;
	}
}

Finally in this section we will create the two bottom buttons, Pause and End Game. There are already definitions in the XML for this, we just need to write the script behind them. Both buttons are fundamentally the same, so create a custom class for them to inherit from: BottomButton, naturally in BottomButton.as:

class BottomButton extends GlowButton {
	var text;
	function onLoad(){
		textfield = createTextField('textfield', 1, 0, 12.5, 65, 20);
		var tf = new TextFormat();
		textfield.embedFonts = true;
		tf.font = 'LightFont';
		tf.color = 0xFFc000;
		textfield.setNewTextFormat(tf);
		super.onLoad();
		lineStyle(1, 0, 40);
		beginFill(0x77575B);
		moveTo(0,23);
		lineTo(64,23);
		lineTo(64,0);
		lineStyle(1, 0xffffff, 40);
		lineTo(0,0);
		lineTo(0,23);
		endFill();

		glowFilter = new flash.filters.GlowFilter(0xFFc000, 80, 16, 16);

		super.init('');
	}
}

This is quite long but simply creates a text field in code, and draws a 64×23 rectangle. The call to init sets up the text field, so the subclass can simply assign the value; the argument is used to set the text but it will quickly be overwritten. The subclass code is then simply to define the action to be taken, and to set the text. Here is EndGameButton.as:

class EndGameButton extends BottomButton {
	function onLoad(){
		super.onLoad();
		textfield.text = 'End Game';
	}

	function onRelease(){
		_root.app.map.endGame();
	}
}

... and PauseButton.as:

class PauseButton extends BottomButton {
	var map;

	function onLoad(){
		super.onLoad();
		textfield.text = 'Pause';
		map = _root.app.map;
	}

	function onRelease(){
		if(map.status == map.states.paused){
			map.pause(false);
			textfield.text = "Pause";
		} else if(map.status == map.states.inPlay) {
			map.pause(true);
			textfield.text = "Unpause";
		}
	}
}

Note that because we're reading from _root.app.map in the onLoad, you must add the PauseButton to the scene after the Map. The event handlers in both cases call the methods on Map we defined earlier.

At this point the game is fully playable: you can play a game, it will show you your score when you run out of lives, you can pause and unpause it and you can return to the menu to play again afterwards. Game state allows you to do this simply and easily, and it is a good technique for most games. You can extend it to have several different 'inPlay' states, for example if you have powerups that put the world into a different state.

III. Upgrading Towers

One of the tradeoffs that makes a TD game strategic is the decision as to whether to upgrade an existing tower, or build another. Upgrades are typically more cost effective but (at the higher levels at least) cost enough that you must plan for them. In article 2 when I introduced the data structure for towers, I mentioned that we were creating a structure that would accommodate upgrades. Let's add some data to give our gun tower five levels (in App.as, inserted data in bold):

	var towerTypes = {
		GunTower: [
			{cost: 50, rotate: 60, range: 100, cooldown: 20, type: 'bullet', projectilespeed: 400, projectilesize: 2, damage: 5,
				description: 'Gun tower 1: Basic projectile tower', canHit: {ground:true, air:true}, colour:0 },
			{cost: 40, rotate: 60, range: 120, cooldown: 15, type: 'bullet', projectilespeed: 400, projectilesize: 2, damage: 8,
				description: 'Gun tower 2: Increased range, damage and rate of fire', canHit: {ground:true, air:true}, colour:0  },
			{cost: 80, rotate: 60, range: 120, cooldown: 15, type: 'bullet', projectilespeed: 400, projectilesize: 2, damage: 12,
				description: 'Gun tower 3: Increased damage', canHit: {ground:true, air:true}, colour:0x000080  },
			{cost: 120, rotate: 60, range: 130, cooldown: 10, type: 'bullet', projectilespeed: 400, projectilesize: 2, damage: 12,
				description: 'Gun tower 4: Increased range and rate of fire', canHit: {ground:true, air:true}, colour:0x0000A0  },
			{cost: 250, rotate: 60, range: 140, cooldown: 30, type: 'bullet', projectilespeed: 400, projectilesize: 3, damage: 50,
				description: 'Gun tower 5: High power sniper round', canHit: {ground:true, air:true}, colour:0x0000C0  }
		]
	};

The level of a tower is controlled by (unsurprisingly) the Tower.level field. But currently we have no way of accessing that. We will need an upgrade button on the UI; this could be anywhere (it could even pop up when you choose a tower), but I choose to put it in one of the recesses at the upper right, which are encapsulated by the TowerButtons symbol. In the XML file, add a new symbol for the upgrade button:

			<clip id="UpgradeButtonImage" import="res/upgrade.png" />
			<clip id="UpgradeButton" class="UpgradeButton">
				<frame>
					<place id="UpgradeButtonImage" name="image" x="-15" y="-15" depth="1"/>
				</frame>
			</clip>

... and add a reference to it in the TowerButtons section (change in bold):

			<clip id="TowerButtons">
				<!-- Use the class name of the tower element as the name -->
				<frame>
					<place id="GunTower" name="GunTower" depth="1" x="40" y="40"/>

					<place id="UpgradeButton" name="upgradeButton" depth="6" x="140" y="90"/>
				</frame>
			</clip>

Finally, create a class file (UpgradeButton.as) for the button:

class UpgradeButton extends MovieClip {
	var glowFilter;
	function onRollOver(){
		var map = _root.app.map;
		if(map.status < 0) return;
		if(!glowFilter)
			glowFilter = new flash.filters.GlowFilter(0xFFD0A0, 0.4, 16, 16);
		this.filters = [glowFilter];
		if(map.selectedTower){
			_root.app.paneltext.text = map.selectedTower.getPanelText('Upgrade to:', 1);
		} else _root.app.paneltext.text = "Upgrade Tower\n\nSelect a tower and then use this tool to upgrade it";
	}

	function onRollOut(){
		filters = [];
		if(_root.app.map.status < 0) return;
		_root.app.map.resetPanelText();
	}

	function onRelease(){
		var map = _root.app.map;
		if(map.status < 0) return;
		if(!map.selectedTower) return;
		if(map.selectedTower.data.length == map.selectedTower.level+1) return;
		if(map.money < map.selectedTower.data[map.selectedTower.level+1].cost){
			_root.app.paneltext.text = 'You can\u2019t afford that upgrade!';
			return;
		}
		map.money -= map.selectedTower.data[map.selectedTower.level+1].cost;
		map.selectedTower.upgrade();
		_root.app.paneltext.text = 'Upgraded to level '+(map.selectedTower.level+1) + '!';
	}
}

This code makes many references to map.selectedTower, which doesn't yet exist. Selection behaviour is simple: clicking a tower selects it, and deselects any previous selection. In Map, declare the variable: var selectedTower = null;. In Tower, add another section to onRelease:

		else if(placed){
			map.selectedTower = this;
			_root.app.paneltext.text = getPanelText('Selected tower');
		}

We also want the information about the tower to appear in the text panel to the right when we select it, or when choosing a new tower or upgrading an existing one. That goes in Tower.getPanelText:

	function getPanelText(pretext, dl){
		if(!dl) dl = 0;
		var data = this.data[this.level + dl];
		if(!data) return pretext + ' (Not available)';
		var s = pretext + '\n\n' + data.description +
			'\n\nCost: ' + data.cost + '\nRange: ' + data.range +
			'\nDamage: ' + data.damage +
			'\nReload: ' + Math.round(1000 * data.cooldown / _root.app.framerate) + 'ms';
		if(this.data.length > this.level + dl + 1)
			s += '\n\nUpgradable';
		else s += '\n\nFull power (level '+(this.level+dl+1)+')';
		return s;
	}

dl is an argument to allow you to find information about a different level; in the UpgradeButton's roll-over handler we pass 1 to get the text for 1 level higher than the tower currently is. Now we have this function, we should also update the text when choosing a new tower from the tower buttons: in Tower.as's onRollOver:

	function onRollOver(){
		if(map.status < 0) return;
		mouseOver = true;
		if(button||placed){
			base.filters = [glowFilter];
			_root.app.paneltext.text = getPanelText(placed ? 'Highlighted tower' : 'Proposed tower');
		}
	}

There is one other minor tweak I've added here: the rollover effect doesn't happen if there is no game in progress. Finally, write Map.resetPanelText for when you roll off a button:

	function resetPanelText(){
		_root.app.paneltext.text = selectedTower ? selectedTower.getPanelText('Selected tower') : 'No tower selected';
	}

... and call it from Tower.onRollOut:

		if(map.status >= 0) map.resetPanelText();

You should now be able to upgrade the tower, and see the change in projectile colour (and damage, depending on which wave you're at). But there is no graphical display to show that you have done so. It is possible to have a different graphic for each level of the tower in different frames of the movie clip, and move which frame is displayed. However, I choose not to update the base image but instead to tag the tower with an overlay that shows the level.

In general, a tag is a small overlay graphic that is attached to a larger item to show some special feature of it. A good example would be the experience marks on the units in the Civilisation series. The level of a tower is a perfect candidate for this. I experimented with placing a text tag for the level in one corner, but I found that visually unappealing. So the technique I show here is to draw circles to mark the level, one for every level above the base. If you plan to have more than 5 or at most 6 levels of tower you will need to make a different tagging system.

To do this, add a function to the Tower class:

	function showTags(){
		if(!tags) createEmptyMovieClip('tags', 1);
		tags.clear();
		var dx = -2.5 * level;
		var dy = baseradius * 0.5;
		tags.beginFill(0xC0FFFF);
		for(var i = 0; i < level; i++){
			_root.drawCircle(tags, 2, 1+dx, dy);
			dx += 5;
		}
		base.endFill(0);
	}

... and, at the bottom of the upgrade function we wrote above, add the line: showTags(); – to call it. (Remember that the level as displayed to the user is one higher than the internal value, so if your towers are levels 1-5 to the user, they are 0-4 here ... so you get 0-4 circles.) If you want to add tags for level 1 towers, or to signify other things, you will need to call showTags whenever you change them.

IV: Creep and Tower Variety

Now the game is functional, but it's boring. All you get is progressively harder waves of the same creep, and all you have to counter them are simple projectile towers. Back at the beginning of article 2 we added the data structure for creeps in the XML and in App.as. Adding more creeps is simply a case of adding more clips to the library to display them:

			<clip id="creep_FastImage" import="res/creep_fast.png"/>
			<clip id="creep_Fast"   class="Creep">
				<frame>
					<place id="creep_FastImage" name="base" x="-7" y="-7" depth="1"/>
				</frame>
			</clip>
			<clip id="creep_FlyingImage" import="res/creep_flying.png"/>
			<clip id="creep_Flying"  class="Creep">
				<frame>
					<place id="creep_FlyingImage" name="base" x="-7" y="-7" depth="1"/>
				</frame>
			</clip>
			<clip id="creep_BossImage" import="res/creep_boss.png"/>
			<clip id="creep_Boss" class="Creep">
				<frame>
					<place id="creep_BossImage" name="base" x="-7" y="-7" depth="1"/>
				</frame>
			</clip>

(in a graphical IDE each of these is simply an image with the registration point appropriately in the middle), and adding more data to the creep data structure (entire variable reproduced) in App.as:

	// Data for creep types. p/dp: probability at start and rate of change
	var creepTypes = {
		Ground: {
			hp: 5, cash: 1, shield: 0.1, speed: 20, type: 'ground', n:20, p: 100, dp: 0
		}, Fast: {
			hp: 5, cash: 1.2, shield: 0.1, speed: 40, type: 'ground', n:20, p: 20, dp: 0
		}, Flying: {
			hp: 5, cash: 1.3, shield: 0.1, speed: 20, type: 'air', ignoresPath: true, n:15, p:20, dp: 1
		}, Boss: {
			hp: 100, cash:30, shield: 2, speed:15, type: 'ground', n:1, p: 0, dp: 1
		}
	};

We also need to modify the wave creation code in Map.onEnterFrame, which should be at the top of that function. We're currently choosing the wave type with this line, which always uses the Ground type:

			type = {name: 'Ground', type:_root.app.creepTypes.Ground};

Replace that line with this code segment, which chooses a type based on the calculated probability values for the current wave number:

			for(var ti in _root.app.creepTypes){
				type = _root.app.creepTypes[ti];
				var ta = { type: type, name:ti, cp: type.p + wave * type.dp };
				if(ta.cp < 0) ta.cp = 0;
				total += ta.cp;
				types.push(ta);
			}
			var n = random(total);
			for(var ti in types){
				var ta = types[ti];
				n -= ta.cp;
				if(n < 0){ type = ta;  break; }
			}

Towers are similar: add extra tower definitions in the XML:

			<clip id="Gun2Image" import="res/gun2.png" />
			<clip id="Gun2"  x="-15" y="-15">
				<frame>
					<place id="Gun2Image" name="base" x="-15" y="-15" depth="1"/>
				</frame>
			</clip>
			<clip id="MortarTower" class="Tower">
				<frame>
					<place id="TowerBase" name="base" x="-15" y="-15" depth="1"/>
					<place id="Gun2" name="gun" depth="16386"/>
				</frame>
			</clip>

			<clip id="Gun3Image" import="res/gun3.png" />
			<clip id="Gun3"  x="-15" y="-15">
				<frame>
					<place id="Gun3Image" name="base" x="-15" y="-15" depth="1"/>
				</frame>
			</clip>
			<clip id="AATower" class="Tower">
				<frame>
					<place id="TowerBase" name="base" x="-15" y="-15" depth="1"/>
					<place id="Gun3" name="gun" depth="16386"/>
				</frame>
			</clip>

			<clip id="Gun4Image" import="res/gun4.png" />
			<clip id="Gun4"  x="-15" y="-15">
				<frame>
					<place id="Gun4Image" name="base" x="-15" y="-15" depth="1"/>
				</frame>
			</clip>
			<clip id="LaserTower" class="Tower">
				<frame>
					<place id="TowerBase" name="base" x="-15" y="-15" depth="1"/>
					<place id="Gun4" name="gun" depth="16386"/>
				</frame>
			</clip>

... and to App:

	var towerTypes = {
		GunTower: [
			... (what is here already stays)
		],
		MortarTower: [
			{cost: 90, rotate: 40, range: 180, cooldown: 40, type: 'splash', projectilespeed: 100, projectilesize: 3, damage: 8, splashradius: 40,
				description: 'Mortar tower 1: Medium range splash damage', canHit: {ground:true}, colour:0x8000000 },
			{cost: 120, rotate: 40, range: 200, cooldown: 40, type: 'splash', projectilespeed: 100, projectilesize: 3, damage: 11, splashradius: 50,
				description: 'Mortar tower 2: Increased range, damage and splash radius', canHit: {ground:true}, colour:0xc000000 },
			{cost: 200, rotate: 40, range: 200, cooldown: 30, type: 'splash', projectilespeed: 100, projectilesize: 3, damage: 17, splashradius: 60,
				description: 'Mortar tower 3: Increased damage and splash radius', canHit: {ground:true}, colour:0xff00000 },
			{cost: 200, rotate: 40, range: 200, cooldown: 30, type: 'splash', projectilespeed: 100, projectilesize: 3, damage: 22, splashradius: 60,
				description: 'Mortar tower 4: Flak allows the tower to hit air units', canHit: {ground:true, air:true}, colour:0xffc0000 }
		],
		AATower: [
			{cost: 60, rotate: 80, range: 80, cooldown: 15, type: 'bullet', projectilespeed: 400, projectilesize: 2, damage: 3,
				description: 'AA tower 1: Rapid fire anti-aircraft tower', canHit: {air:true}, colour:0xFFFF00 },
			{cost: 50, rotate: 90, range: 100, cooldown: 12, type: 'bullet', projectilespeed: 400, projectilesize: 2, damage: 5,
				description: 'AA tower 2: Increased range, damage and rate of fire', canHit: {air:true}, colour:0xFFFF00  },
			{cost: 120, rotate: 95, range: 110, cooldown: 12, type: 'bullet', projectilespeed: 400, projectilesize: 2, damage: 9,
				description: 'AA tower 3: Increased range and damage', canHit: {air:true}, colour:0xFFFF00  },
			{cost: 140, rotate: 95, range: 110, cooldown: 10, type: 'bullet', projectilespeed: 400, projectilesize: 2, damage: 15,
				description: 'AA tower 4: Increased rate of fire and damage', canHit: {air:true}, colour:0xFFFF00  },
			{cost: 250, rotate: 95, range: 110, cooldown: 7, type: 'bullet', projectilespeed: 500, projectilesize: 1, damage: 22,
				description: 'AA tower 5: Rapid fire AA machine gun', canHit: {air:true}, colour:0xFFFFC0  }
		],
		LaserTower: [
			{cost: 90, rotate: 40, range: 130, cooldown: 30, type: 'laser', projectilesize: 2, laserlife:3, damage:7,
				description: 'Laser tower 1: Instant point damage to ground units', canHit: {ground:true}, colour:0xc00000 },
			{cost: 50, rotate: 45, range: 135, cooldown: 28, type: 'laser', projectilesize: 2, laserlife:4, damage:9,
				description: 'Laser tower 2: Increased damage, range and rate of fire', canHit: {ground:true}, colour:0xd00000 },
			{cost: 70, rotate: 50, range: 135, cooldown: 28, type: 'laser', projectilesize: 2, laserlife:5, damage:13,
				description: 'Laser tower 3: Better targeting; can hit air units', canHit: {ground:true, air:true}, colour:0xff0000 },
			{cost: 250, rotate: 50, range: 145, cooldown: 26, type: 'laser', projectilesize: 3, laserlife:8, damage:35,
				description: 'Laser tower 4: Much improved damage', canHit: {ground:true, air:true}, colour:0xffc000 },
			{cost: 500, rotate: 50, range: 150, cooldown: 30, type: 'laser', projectilesize: 4, laserlife:12, damage:70,
				description: 'Laser tower 5: Deadly instant immolation for creeps', canHit: {ground:true, air:true}, colour:0xffe0a0 }
		]

	};

We must also add new buttons to the TowerButtons clip for the new tower types:

			<clip id="TowerButtons">
				<!-- Use the class name of the tower element as the name -->
				<frame>
					<place id="GunTower" name="GunTower" depth="1" x="40" y="40"/>
					<place id="MortarTower" name="MortarTower" depth="2" x="90" y="40"/>
					<place id="AATower" name="AATower" depth="3" x="140" y="40"/>
					<place id="LaserTower" name="LaserTower" depth="4" x="40" y="90"/>

					<place id="UpgradeButton" name="upgradeButton" depth="6" x="140" y="90"/>
				</frame>
			</clip>

If all the new towers were firing bullets, this would be enough. However, we have introduced two new types of weaponry: 'splash' and 'laser'. Splash is similar to a bullet in that it is a mobile projectile, but it does not have collision detection, instead landing at its original aiming marker and creating a larger circle to deal damage. Laser is an instant hit line from the tower to the target, which is displayed for a short time and disappears. As the bullet code is all in the Map, we need to make some changes to that class.

Firstly, in the creep-bullet collision detection loop inside onEnterFrame, add a line to skip the test for these weapon types:

		for(var ci = creeps.length - 1; ci >= 0; ci--){
			...
			for(var bi = bullets.length; bi >= 0; bi--){
				var bullet = bullets[bi];
				if((bullets[bi].type == 'splash') || (bullet.type == 'laser')) continue; 
				...
			}
			...
		}

In the section of onEnterFrame that redraws the overlay, replace the bullet loop with this:

		for(var bi in bullets){
			var bullet = bullets[bi];
			if((bullet.type == 'bullet') || (bullet.type == 'splash')){
				overlay.lineStyle(0,0,0);
				overlay.beginFill(bullet.colour);
				_root.drawCircle(overlay, bullet.size, bullet.x, bullet.y );
				overlay.endFill();
			} else if (bullet.type == 'laser') {
				overlay.lineStyle(bullet.size, bullet.colour, 100 * bullet.range / bullet.maxlife);
				overlay.beginFill(0,0); // artifact prevention
				overlay.moveTo(bullet.x, bullet.y);
				overlay.lineTo(bullet.tx, bullet.ty);
			}
		}

You can extend this principle to draw as many different types of weapon as you like. The tower which fires the weapon also needs to make some adjustments; in the Tower class's think function replace the firing code with this (much of it is the same as before):

		if(facing && (_root.app.frameno >= nextfire) && (_root.dist2([_x, _y], [target._x, target._y]) < r2)){
			// Fire
			var bullet = {speed: data.projectilespeed, damage: data.damage, x: _x, y: _y, type:data.type, canHit:data.canHit, range: data.range, size:data.projectilesize };
			bullet.direction = _root.normalise([target._x - _x, target._y - _y]);
			bullet.colour = data.colour;
			bullet.source = this;
			if(data.type == 'splash'){
				bullet.splashradius = data.splashradius;
				bullet.deathfn = makesplash;
				bullet.range = Math.sqrt(_root.dist2([_x, _y], [target._x, target._y]));
			} else if(data.type == 'laser'){
				bullet.range = bullet.maxlife = data.laserlife;
				bullet.speed = 0;
				bullet.tx = target.x; bullet.ty = target.y;
				// Laser is instant hit
				target.takedamage(this, bullet.damage);
			}
			map.bullets.push(bullet);
			nextfire = _root.app.frameno + data.cooldown;
		}

Note that the damage is caused instantly to the creep, within the tower's think step, for the laser, whereas with the original bullet it happens in the collision detection step. For the splash damage, we use a third method: assigning a 'death function' to the projectile, which is called when it reaches its target range. Create the function makesplash (I put it in Tower as that is where the reference is needed):

	function makesplash(map, bullet){
		var splash = map.overlay.attachMovie(
			'PlayOnce', 'splash'+map.creepdepth, map.creepdepth++);
		splash.life = 15;
		splash.lineStyle(0,0,0);
		splash.beginFill(0xC00000, 20);
		_root.drawCircle(splash, bullet.splashradius, 0, 0);
		splash.endFill();
		splash._x = bullet.x; splash._y = bullet.y;

		for(var ci in map.creeps){
			var creep = map.creeps[ci];
			if(!bullet.canHit[creep.type.type]) continue;
			if(creep.hitTest(splash)) creep.takedamage(bullet, bullet.damage);
		}
	}

Essentially this is in two parts: drawing the graphical display, and then hit-testing it against all the creeps. Because we want the graphical display to match the damage region, we can just use the built-in hit testing; if you want a more advanced graphical display you might have to do a distance-from-centre test here instead (easy enough to do). I use a PlayOnce clip to signify that the splash indicator will disappear on its own; this is a simple class which removes itself when it's done. Here is PlayOnce.as (which you may steal):

class PlayOnce extends MovieClip {
	var life = null;
	function onEnterFrame() {
		if(life == null) life = this._totalframes;
		if(!life--){
			this.removeMovieClip();
		}
	}
}

You need to add PlayOnce to the library, too:

			<clip id="PlayOnce" class="PlayOnce"/>

If you followed the tutorial though, you should now have a game which is functionally equivalent to the version of Infinite TD which I initially uploaded to Kongregate!

V. Game Modes

A common feature in all genres of game is a choice of modes for the player; often difficulty levels, sometimes a special mode (no powerups, one life, one hit point, etc), but almost always modifying the game experience in a simple way. You do not need to rewrite your game logic five times to accommodate five game modes! Instead, we can encode the properties of each mode in an array, as for creeps and towers, in App:

	// Data for game modes
	var modes = {
		Normal: { money: 100, lives: 10 },
		Hard: { money: 50, lives: 10 },
		Survival: { money: 100, lives: 1}
	}

In my simple example only the initial conditions are changed, but you could also have factors for the creep properties, wave interval, even the colours used for drawing the map. To make the different modes accessible, we must modify Menu to create one button for every mode:

// Menu
class Menu extends MovieClip {
	function onLoad(){
		var y = 130, d = 1;
		var menu = this;
		for(var thismode in _root.app.modes){
			var startButton = attachMovie('MenuButton', 'start' + thismode, d++);
			startButton._x = 200;
			startButton._y = y;
			y += 50
			startButton.mode = thismode;
			startButton.init('Start ' + thismode + '!');
			startButton.onRelease = function(){
				menu._visible = false;
				_root.app.status.text = 'Clicked';
				_root.app.map.initGame(0x402010, 0x907020, 0xFFFFC0, this.mode);
				_root.app.paneltext.text = 'Started a new '+this.mode+' game. Click a tower button to begin placing. Waves appear every 30 seconds.';
			}
			startButton.oldOnRollOver = startButton.onRollOver;
			startButton.onRollOver = function(){
				this.oldOnRollOver();
				var mode = _root.app.modes[this.mode];
				_root.app.paneltext.text = this.mode + '\n\nStarting money: ' + mode.money + '\nLives: ' + mode.lives;
			}
		}
	}
}

Now instead of one start button, we have three, each of which passes the name of the relevant mode to the Map.initGame method. We must also modify that method to use the properties of the mode:

	function initGame(mapCol, pathCol, textCol, modeName){
		createPath();
		draw(mapCol, pathCol);
		this.modeName = modeName;
		var mode = _root.app.modes[modeName];
		money = mode.money;
		lives = mode.lives;
		...

Declare the field modeName in Map to satisfy the compiler; we don't use it at present but for a high score table or for in-game effects you will want to know which mode you are playing in.

VI. Graphical Tidiness

Fonts: In SWFmill, fonts are slightly awkward. You can embed an entire font simply enough, but that can be quite large. It is better to include only the subset of glyphs which you are actually going to use. (In the Flash IDE this section is probably not at all relevant; simply choose a font for your text field and it will work everything out.)

			<font id="BaseFont" import="/windows/fonts/verdanab.ttf" glyphs=" QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890&#x2018;&#x2019;&amp;!.,"/>
			<font id="LightFont" name="LightFont" import="/windows/fonts/verdana.ttf"  glyphs=" QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890‘’&!.,"/>

Remember to escape any high-bit or special characters in the XML! If you are creating text fields dynamically (as we did for the BottomButton class), you need to use the id of the font declaration in the TextFormat in order to have the font show up correctly.

Range Circles: In my version of Infinte TD, you get a range circle displayed when you are placing a tower, or when you roll over one. The technique of having a range circle is quite common, not only in TD games but also other genres. To accomplish it, you can draw on a clip using the AS drawing commands. We actually added the showRange function and the display when placing a tower in article 2, so we need only add the mouse-over behaviour on placed towers now.

In Tower.onRollOver, add: if(placed) showRange(15); and in Tower.onRollOut, add: if(placed) clear();. In fact, because drawing the range circle makes the clip bigger, onRollOut won't fire when you want it to, so add a mouse move handler to clear the overlay when you leave the main part of the clip:

	function onMouseMove(){
		if(placed){
			if(_root.dist2([_xmouse, _ymouse], [0,0]) > (radius * radius)) clear();
		}
	}

Next Wave: In the early part of a TD game, the player often beats the creeps very easily, and wishes to spawn the next wave early. The code to implement such a button is very easy because of the frame based mechanic of the map:

	function onRelease(){
		_root.app.map.nextwaveframe = _root.app.frameno;
	}

You could also add score (using _root.app.map.changeScoreAndMoney) – but remember, if adding score for bringing the next wave forward, that the player can press the button many times in the time it takes creeps to run the path. Don't award a large score for this, or only allow it to be pressed if few creeps are on the screen already.

Kongregate Stats API: This is explained well in the Shootorials and on the Kongregate website. To submit scores, you need to call _root.kongregateServices.connect() in your startup code (I put it in App.onLoad), and then when you are ready to submit, use _root.kongregateStats.submit. I do it in endGame:

		if(_root.kongregateStats){
			_root.kongregateStats.submit('Score (' + modeName + ')', score);
			_root.kongregateStats.submit('Wave (' + modeName + ')', wave);
			_root.kongregateStats.submit('Towers built (' + modeName + ')', towers.length);
		}

Remember to set up the relevant stats and make them public if you want them to appear on the Kongregate high scores panel! Also, make sure the things you submit do not have 'infinite score cheats'. If you use shared objects to save game state, you should submit score information when you load a game, so that a user doesn't have to play the whole game again if you added a new stat (or, lucky day, you get badges on your game).

Other Considerations: There are a number of things which I have not touched on in this series which are common requirements for Flash game developers, but which are not necessary for a simple TD game. I will briefly mention them here and you will probably want to do further reading on these subjects.

VII. Article Debriefing

The important message from this article is that even when you think you are 'nearly finished', there is still a lot of coding to be done. Don't release an unfinished game; you will be reviewed harshly and people will likely not come back once you update it. You need to put in the extra features in the first place.

In this article we've seen how to show the user the state of the game, how to switch the game between states (how to pause it and how to end the game) and how to let the user start a new game once the old one is finished. We've also reaped the benefits of the data-driven style I mentioned in article 2, by adding new creeps and towers in a fraction of the time it took to write the first ones. I've shown you how to manage three types of weaponry (projectile, splash damage and instant-hit lasers), and how to give the user a choice of difficulty levels without changing the core code.

Now you have a fully functional tower defence game. Congratulations! Hopefully you can see how to extend it further, and add your own twists to the genre, if you wish to do so.

VIII. Series Debreifing

Through this series of articles I have not only given a blow-by-blow walkthrough of the process of creating a tower defence game, but also introduced some more general techniques for game development. Here are the core ideas that I hope you will find useful as you go forward and develop more games – in Flash or in other languages.

I hope that you have found these articles useful, and welcome comments.

Here is an archive of the end of the tutorial, for comparison.

Appendix: Legal stuff