Advertisement

Create a Complete Typing Game in Flash with AS3

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

Leading on from my earlier tutorial on detecting keyboard combos, we'll see how to build a full game that tests your typing skills.


Final Result Preview

Let's take a look at the final result we will be working towards:

Use the letters on the screen to type English words! Invalid words will lose you points, so watch out.


Step 1: Introduction

In this tutorial we will be working on a very awesome typing game using this very useful Combo Detection class. It is very recommended that you read that tutorial before continuing here, so that you understand what that class will be doing during our game.

In our game, we will have many blocks with letters on the screen, and the player has to type a word formed with the letter blocks. If that word is valid, the blocks are removed and the player gets points and more time to play. The game will end when the time reaches zero.

If you plan on completely following this tutorial, you should grab the source files.


Step 2: Adding the Images in Flash Professional

As it was done in the previously mentioned tutorial, we will add the all the images for our game in a Flash Professional .fla file, then generate a .swc file, which will be added into our FlashDevelop project for use.

For this tutorial, we will need a background image (thanks for Petr Kovar for the awesome wood background!), a generic block image with a selected frame and the currently typed keys box. You can find everything set up in our source files.

Background image
Generic block image
Selected generic block image
Word box

Other images such as the preloader and the game over screen will be added as well, but these will be made during the tutorial, since it makes more sense to do so.


Step 3: The LetterBlock

Before we can begin working on our code, let's set up our FlashDevelop project. This one is going to be an AS3 Project with Preloader, so select that option on FlashDevelop! Assuming that you have read the combos tutorial, you probably know how to add a .swc file to FlashDevelop's library. If you don't, just right-click it and select "Add to library". Grab the .swc file from the source and add it. That's it. Time for action!

Our letter block will be a simple object with a TextField in it and the block image shown in Step 2. Coding that is simple. Create the LetterBlock class:

package  
{
	import ArtAssets.LetterBlockImage;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;
	
	public class LetterBlock extends Sprite 
	{
		private var _image:MovieClip;
		
		private var _letterText:TextField;
		
		public function LetterBlock() 
		{
			_image = new LetterBlockImage();
			_image.stop();
			
			_letterText = new TextField();
			_letterText.defaultTextFormat = new TextFormat("Verdana", 40, 0xFFFFFF, true, null, null, null, null, TextFormatAlign.CENTER);
			_letterText.width = 60;
			_letterText.x = -30;
			_letterText.y = -26.3;
			_letterText.selectable = false;
			_letterText.multiline = false;
			
			addChild(_image);
			addChild(_letterText);
		}
		
		public function setLetter(letter:String):void
		{
			_letterText.text = letter;
		} 
		
	}

}

Lines 21-28 create the text field to hold our letter text, as well as position it on the screen and give it a font, font color and size. The setLetter function is only used to set the letter of the box.


Step 4: Loading up the Words

Every typing game needs words to work with. In this step, we will load an external file through the [Embed] tag and work with the data of the file. Obviously, this file contains words to be used in our game. It is available in the source files. Also, in this step we begin to work with the ComboHandler class, so add it in your FlashDevelop project as well!

Let's look at some code:

In Main.as:

private function init(e:Event = null):void 
{
	removeEventListener(Event.ADDED_TO_STAGE, init);
	
	ComboHandler.initialize(stage);
	
	DictionaryWords.loadWords();
}

Line 5 above initializes the ComboHandler, as required, and line 7 calls the loadWords() method from the DictionaryWords class. This class will be created by us, and its code is right below:

package  
{
	public class DictionaryWords 
	{
		[Embed(source = "../src/Words.txt", mimeType = "application/octet-stream")]
		private static var _Words:Class;
		
		public static function loadWords():void
		{
			var words:String = new _Words();
			
			var wordsArray:Array = words.split("\n");
			
			var i:int;
			var length:int = wordsArray.length;
			
			for (i = 0; i < length; i++)
			{
				ComboHandler.registerCombo(wordsArray[i], turnIntoLetters(wordsArray[i]));
			}
		}
		
		private static function turnIntoLetters(word:String):Array
		{
			var letters:Array = word.split("");
			
			if (letters[0] == "")
			{
				letters.shift();
			}
			
			if (letters[letters.length - 1] == "")
			{
				letters.pop();
			}
			
			var i:int;
			
			for (i = 0; i < letters.length; i++)
			{
				letters[i] = String(letters[i]).charCodeAt(0);
			}
			
			return letters;
		}
		
	}

}

Line 5 is the line that loads the external file and put it in the game at compile-time. This is all possible because of the [Embed] tag. If you want more information about it, I recommend this great article on Adobe Livedocs.

Here is a section of Words.txt, so you can see what we're working with:

ABBREVIATOR
ABC
ABCOULOMB
ABCS
ABDIAS
ABDICABLE
ABDICATE
ABDICATION
ABDICATOR
ABDOMEN
ABDOMINAL
ABDOMINOCENTESIS
ABDOMINOPLASTY

Line 12 (of DictionaryWords.as) is a very important line. Basically, it turns all the words from Words.txt, which were stored in a String, to elements in an Array. Since each word is separated by a newline character, all we had to do was call the split() method of the String class.

The turnIntoLetters function just turns a word into an Array with the key codes of each letter. That way, our ComboHandler class can work with it.


Step 5: Adding Letter Blocks to the Screen

Now that we have our words ready in the game, it is time to begin working on putting the letters on the screen. This is very simple. First of all, we need a game screen. The GameScreen class will contain all the logic of our game.

package  
{
	import ArtAssets.BackgroundImage;
	import flash.display.Sprite;
	import adobe.utils.CustomActions;
	
	public class GameScreen extends Sprite
	{
		private var _background:BackgroundImage;
		
		private var _blocksOnScreen:Vector.<LetterBlock>;
		
		public function GameScreen() 
		{
			_background = new BackgroundImage();
			_background.x = 275;
			_background.y = 200;
			
			addChild(_background);
			
			_blocksOnScreen = new Vector.<LetterBlock>();
			
			populateBlocks();
		}
		
		private function populateBlocks():void 
		{
			var i:int;
			
			var tempBlock:LetterBlock;
			
			for (i = 0; i < 8; i++)
			{
				tempBlock = new LetterBlock();
				tempBlock.x = 130 + ((i % 4) * 95);
				tempBlock.y = 80 + int(i / 4) * 80;
				tempBlock.setLetter(randomLetter());
				
				addChild(tempBlock);
				
				_blocksOnScreen.push(tempBlock);
				
				tempBlock = null;
			}
		}
		
		private function randomLetter():String
		{
			return String.fromCharCode((int(Math.random() * 26) + 65));
		}
		
	}

}

The _blocksOnScreen Vector is the main element in this code: it will contain all the blocks on the screen, allowing us to work with them anytime we want. Note that in Line 14 we add the BackgroundImage on the screen, which is a graphic from the .swc file.

Inside the populateBlocks() function, all that we do is add a new LetterBlock at a certain position, give it a random letter (which is generated by the randomLetter() function) and add it to the screen.

Now, we need to add the game screen in Main's child. Inside Main.as:

		private var _gameScreen:GameScreen;
		
		private function init(e:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			
			ComboHandler.initialize(stage);
			
			DictionaryWords.loadWords();
			
			_gameScreen = new GameScreen();
			
			addChild(_gameScreen);
		}

Compile the project and you will be able to see the blocks on the screen!


Step 6: Selecting a Block After a Key Press

In our game, we want the block to go to its "Selected" image when its corresponding key has been pressed. This is very easy to do. Go to the LetterBlock.as file and add this code:

private var _selected:Boolean;

public function select():void
{
	_selected = !_selected;
	
	_image.gotoAndStop(_selected == true ? "Selected" : "Unselected");
}

public function get letter():String
{
	return _letterText.text;
}

public function get selected():Boolean 
{
	return _selected;
}

The _selected variable will be used on the game's logic to check whether a block is selected or not. The same happens with letter.

All that is left is to make the block switch between "selected" and "unselected" now. Inside GameScreen.as:

import flash.events.Event;
import flash.events.KeyboardEvent;

// ** snip **

public function GameScreen() 
{
	_background = new BackgroundImage();
	_background.x = 275;
	_background.y = 200;
	
	addChild(_background);
	
	_blocksOnScreen = new Vector.<LetterBlock>();
	
	populateBlocks();
	
	addEventListener(Event.ADDED_TO_STAGE, onStage);
}

private function onStage(e:Event):void 
{
	removeEventListener(Event.ADDED_TO_STAGE, onStage);
	
	stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function onKeyDown(e:KeyboardEvent):void 
{
	var i:int;
	
	for (i = 0; i < _blocksOnScreen.length; i++)
	{
		if (_blocksOnScreen[i].letter == String.fromCharCode(e.keyCode) && !_blocksOnScreen[i].selected)
		{
			_blocksOnScreen[i].select();
			
			break;
		}
	}
}

Given that we should add our KeyboardEvent listeners to the stage, in the GameScreen's constructor, we add a listener for Event.ADDED_TO_STAGE, which will lead us to the onStage() function. This function adds the listener to the stage. The onKeyDown() function is responsible for going over all our blocks on the screen and verifying if there is any block with the letter pressed. If so, then we should select it, which is done in line 36.

After compiling the project, this is what we get:

(Press the keys on your keyboard!)


Step 7: Modifications to the ComboHandler Class

In order for our game to work the way we want, we'll need to do a few modifications to the ComboHandler class from the previous tutorial. The first modification is to make it able to only check for a combo when the user has stopped typing. This will be detected with the MAX_INTERVAL constant and through an update() function. Also, since the user has to type the exact word letters in order to "complete" it, we will change how we are checking if a combo matches the keys inside the pressedKeys Array. The last modification is to send an event even when the word typed is wrong. This will allow our game to detect the player's mistake and penalise him for that.

All the code below does what was explained:

Inside ComboHandler.as:

private static const MAX_INTERVAL:int = 500; // Milliseconds

private static var checkComboAfterClearing:Boolean;

public static function initialize(stageReference:Stage, checkComboAfterClearing:Boolean = false):void
{
	combos = new Dictionary();
	
	interval = 0;
	
	dispatcher = new EventDispatcher();
	
	ComboHandler.checkComboAfterClearing = checkComboAfterClearing;
	
	pressedKeys = [];
	
	stageReference.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private static function onKeyDown(e:KeyboardEvent):void
{
	if (getTimer() - interval > MAX_INTERVAL)
	{
		pressedKeys = [];
	}
	
	interval = getTimer();
	
	pressedKeys.push(e.keyCode);
	
	if (!checkComboAfterClearing)
	{
		checkForCombo();
	}
}

public static function update():void
{
	if (getTimer() - interval > MAX_INTERVAL)
	{
		checkForCombo();
		
		pressedKeys = [];
	}
}

private static function checkForCombo():void
{
	if (pressedKeys.length == 0)
	{
		return;
	}
	
	var i:int;
	var comboFound:String = "";
	
	for (var comboName:String in combos)
	{
		if ((combos[comboName] as Array).length == 0)
		{
			continue;
		}
		
		if (pressedKeys.join(" ") == (combos[comboName] as Array).join(" "))
		{
			comboFound = comboName;
			
			break;
		}
	}
	
	// Combo Found
	//if (comboFound != "")
	//{
	//pressedKeys = [];
	dispatcher.dispatchEvent(new ComboEvent(ComboEvent.COMBO_FINISHED, {comboName: comboFound} ));
	//}
}

We changed our ComboHandler's constructor declaration to allow us check for combos only after the user has stopped typing. This is verified in the update() function, using the MAX_INTERVAL constant. Also, the checkForCombo() function has been modified to use a more suitable combo checking.

Now, we also must change the GameScreen class to update the ComboHandler class:

public function GameScreen() 
{
	_background = new BackgroundImage();
	_background.x = 275;
	_background.y = 200;
	
	addChild(_background);
	
	_blocksOnScreen = new Vector.<LetterBlock>();
	
	populateBlocks();
	
	addEventListener(Event.ADDED_TO_STAGE, onStage);
	
	addEventListener(Event.ENTER_FRAME, gameLoop);
}

private function gameLoop(e:Event):void 
{
	ComboHandler.update();
}

The update is being done through an Event.ENTER_FRAME event listener, since it's a simple and good approach.

The last modification is to change how we initialize the ComboHandler class inside the Main class:

private function init(e:Event = null):void 
{
	removeEventListener(Event.ADDED_TO_STAGE, init);
	
	ComboHandler.initialize(stage, true);
	
	DictionaryWords.loadWords();
	
	_gameScreen = new GameScreen();
	
	addChild(_gameScreen);
}

By compiling the game, this is what we get:


Step 8: Adding the Word Box

It's time to give the player something else to rely on. Right now, the player can't see the sequence of letters that was already typed, so let's add a word box in the game. This box will contain the currently typed letters, organized by order, and will be cleaned when a combo event has been received. Create the WordBox class and add this code to it:

package  
{
	import ArtAssets.TypedLettersBoxImage;
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;
	
	public class WordBox extends Sprite
	{
		private var _image:Sprite;
		
		private var _textField:TextField;
		
		public function WordBox() 
		{
			_image = new TypedLettersBoxImage();
			
			_textField = new TextField();
			_textField.defaultTextFormat = new TextFormat("Verdana", 30, 0xFFFFFF, true, null, null, null, null, TextFormatAlign.CENTER);
			_textField.width = 500;
			_textField.x = -250;
			_textField.y = -25;
			_textField.selectable = false;
			_textField.multiline = false;
			_textField.text = "";
			
			addChild(_image);
			
			addChild(_textField);
		}
		
		public function addLetter(letter:String):void
		{
			_textField.appendText(letter);
		}
		
		public function clear():void
		{
			_textField.text = "";
		}
		
	}

}

This class is almost the same as the LetterBlock class, so no extensive explanation is necessary. The only thing that deserves attention is line 35, which contains a call to the appendText() method from the String class. This function will add text to the end of the current text, allowing us to display the typed letters always at the end of the current text from the word box.

Now, it's time to add the word box in the game. Go to GameScreen.as and add this code:

private var _wordBox:WordBox;

public function GameScreen() 
{
	_background = new BackgroundImage();
	_background.x = 275;
	_background.y = 200;
	
	addChild(_background);
	
	_blocksOnScreen = new Vector.<LetterBlock>();
	
	populateBlocks();
	
	_wordBox = new WordBox();
	_wordBox.x = 275;
	_wordBox.y = 350;
	
	addChild(_wordBox);
	
	addEventListener(Event.ADDED_TO_STAGE, onStage);
	
	addEventListener(Event.ENTER_FRAME, gameLoop);
}

private function onKeyDown(e:KeyboardEvent):void 
{
	var i:int;
	
	for (i = 0; i < _blocksOnScreen.length; i++)
	{
		if (_blocksOnScreen[i].letter == String.fromCharCode(e.keyCode) && !_blocksOnScreen[i].selected)
		{
			_blocksOnScreen[i].select();
			
			_wordBox.addLetter(_blocksOnScreen[i].letter);
			
			break;
		}
	}
}

Lines 1, 15-17 and 19 create the word box and put it on the screen. Line 36 calls the addLetter() function to add a letter in the box.

The result of this step is below. We will add the code to clear the word box in the next step.


Step 9: Integrating our ComboHandler With our GameScreen

Right now, the only changes that have been done in the ComboHandler only modified when and how to check for combos. This is the part where it gets fun: we will integrate our ComboHandler in the game. This means our ComboHandler will depend on GameScreen in order to run. Let's jump to the code, and see explanations after it!

Inside GameScreen.as:

public function GameScreen() 
{
	_background = new BackgroundImage();
	_background.x = 275;
	_background.y = 200;
	
	addChild(_background);
	
	_blocksOnScreen = new Vector.<LetterBlock>(8);
	
	populateBlocks();
	
	_wordBox = new WordBox();
	_wordBox.x = 275;
	_wordBox.y = 350;
	
	addChild(_wordBox);
	
	addEventListener(Event.ADDED_TO_STAGE, onStage);
	
	addEventListener(Event.ENTER_FRAME, gameLoop);
	
	ComboHandler.dispatcher.addEventListener(ComboEvent.COMBO_FINISHED, onWordFinished);
	
	ComboHandler.setGameInstance(this);
}

private function onWordFinished(e:ComboEvent):void 
{
	_wordBox.clear();
}

public function isKeyAvailable(key:String):Boolean
{
	var i:int;
	
	for (i = 0; i < _blocksOnScreen.length; i++)
	{
		if (_blocksOnScreen[i].letter == key && !_blocksOnScreen[i].selected)
		{
			return true;
		}
	}
	
	return false;
}

private function populateBlocks():void 
{
	var i:int;
	
	var tempBlock:LetterBlock;
	
	for (i = 0; i < 8; i++)
	{
		tempBlock = new LetterBlock();
		tempBlock.x = 130 + ((i % 4) * 95);
		tempBlock.y = 80 + int(i / 4) * 80;
		tempBlock.setLetter(randomLetter());
		
		addChild(tempBlock);
		
		_blocksOnScreen[i] = tempBlock;
		
		tempBlock = null;
	}
}

Inside GameScreen's constructor, we added an event listener to ComboHandler's dispatcher object, and called the setGameInstance() function of that class (which will be added below). This will give the current instance of the game screen to the ComboHandler class, and will clear the word box.

There's also an isKeyAvailable function created. This will be called within the combo handler to verify if it can add a key to the list of pressed keys.

Line 63 is just a fix to the change done in _blocksInScreen in the constructor.

Take a look at the code to add inside ComboHandler.as:

private static var gameInstance:GameScreen;

public static function setGameInstance(gameInstance:GameScreen):void
{
	ComboHandler.gameInstance = gameInstance;
}

private static function onKeyDown(e:KeyboardEvent):void
{
	if (getTimer() - interval > MAX_INTERVAL)
	{
		pressedKeys = [];
	}
	
	if (gameInstance.isKeyAvailable(String.fromCharCode(e.keyCode)))
	{
		interval = getTimer();
		
		pressedKeys.push(e.keyCode);
	}
	
	if (!checkComboAfterClearing)
	{
		checkForCombo();
	}
}

In the highlighted line we add a call to isKeyAvailable(), which is in the game screen instance stored in the ComboHandler class. This is the end of the integration between the game and the combo handler.

After compiling, you will notice that the game now clears the word box after the interval has passed:


Step 10: Act When a Word has Been Typed

In this step, we will make the game take some action when a word has been detected. First of all, we need to know when a word has been detected. Let's find out how:

Inside GameScreen.as:

public function GameScreen() 
{
	_background = new BackgroundImage();
	_background.x = 275;
	_background.y = 200;
	
	addChild(_background);
	
	_blocksOnScreen = new Vector.<LetterBlock>(8);
	
	populateBlocks();
	
	_wordBox = new WordBox();
	_wordBox.x = 275;
	_wordBox.y = 350;
	
	addChild(_wordBox);
	
	addEventListener(Event.ADDED_TO_STAGE, onStage);
	
	addEventListener(Event.ENTER_FRAME, gameLoop);
	
	ComboHandler.dispatcher.addEventListener(ComboEvent.COMBO_FINISHED, onWordFinished);
	
	ComboHandler.setGameInstance(this);
}

private function onWordFinished(e:ComboEvent):void 
{
	if (e.params.comboName != "")
	{
		removeSelectedLetters();
		populateBlocks();
		
		_wordBox.clear();
	}
}

private function removeSelectedLetters():void 
{
	var i:int;
	
	for (i = 0; i < 8; i++)
	{
		if (_blocksOnScreen[i].selected)
		{
			removeChild(_blocksOnScreen[i]);
			
			_blocksOnScreen[i] = null;
		}
	}
}

private function populateBlocks():void 
{
	var i:int;
	
	var tempBlock:LetterBlock;
	
	for (i = 0; i < 8; i++)
	{
		if (_blocksOnScreen[i] == null)
		{
			tempBlock = new LetterBlock();
			tempBlock.x = 130 + ((i % 4) * 95);
			tempBlock.y = 80 + int(i / 4) * 80;
			tempBlock.setLetter(randomLetter());
			
			addChild(tempBlock);
			
			_blocksOnScreen[i] = tempBlock;
		}
		
		tempBlock = null;
	}
}

In line 23 there is an event listener for ComboEvent.COMBO_FINISHED. This event will be fired every time a word has been formed or when the player has missed. If you go back to ComboHandler.as, you will notice that when the player misses, the name of the combo within the fired event will be null, or "". Due to that, we do the check in lines 28-34. The removeSelectedLetters() function will remove all selected letters, since the player has formed a word when it is called. We also changed the populateBlocks() function to only put a new block in places where there is no block - this fits what we are doing inside the function to remove blocks.

Compile the game and this is the result:


Step 11: Do Something When the Player Types a Wrong Word

Have you got any idea of what to do when a player misses a word? I was thinking of taking away time and score from the player (this will be done in later steps), as well as giving a 30% chance of modifying a selected letter from the misstyped word. The code below does exactly the latter. Move to GameScreen.as:

private function onWordFinished(e:ComboEvent):void 
{
	if (e.params.comboName != "")
	{
		removeSelectedLetters(false);
	}
	else
	{
		removeSelectedLetters(true);
	}
	
	populateBlocks();
	
	_wordBox.clear();
}

private function removeSelectedLetters(wasFromFailure:Boolean):void 
{
	var i:int;
	
	for (i = 0; i < 8; i++)
	{
		if (_blocksOnScreen[i].selected)
		{
			if ((wasFromFailure && Math.random() < 0.3) || !wasFromFailure)
			{
				removeChild(_blocksOnScreen[i]);
				
				_blocksOnScreen[i] = null;
			}
			else
			{
				_blocksOnScreen[i].select();
			}
		}
	}
}

Before looking at lines 5 and 9, let's jump to line 25: in this line, wasFromFailure is the variable that will define whether the game should "calculate" a 30% chance (through Math.random()) or just replace the block. And how is this value passed? Look at lines 5 and 9: if the name of the word is nothing, or "", that means the player has missed the word, which means we should pass true to removeSelectedLetters().

Compile the project and try to type a wrong word!


Step 12: Add a Score

It is now time to add a score in the game! First, we will create the image and place it on the screen. In the next step, we will give score to the player. For the score, we need to create a Score class, and this is the code for it:

package  
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;
	
	public class Score extends Sprite
	{
		private var _text:TextField;
		
		private var _score:int;
		
		public function Score() 
		{
			_text = new TextField();
			_text.defaultTextFormat = new TextFormat("Verdana", 40, 0xFFFFFF, true, null, null, null, null, TextFormatAlign.CENTER);
			_text.width = 500;
			_text.selectable = false;
			_text.multiline = false;
			
			addChild(_text);
			
			_score = 0;
			
			_text.text = "Score: " + _score.toString();
		}
		
		public function addToScore(value:int):void
		{
			_score += value;
			
			_text.text = "Score: " + _score.toString();
		}
		
	}

}

I believe there is not much to say about it - we have already done text like this two times before. Now we should add this score on the screen. Inside GameScreen.as:

private var _score:Score;

public function GameScreen() 
{
	_background = new BackgroundImage();
	_background.x = 275;
	_background.y = 200;
	
	addChild(_background);
	
	_blocksOnScreen = new Vector.<LetterBlock>(8);
	
	populateBlocks();
	
	_wordBox = new WordBox();
	_wordBox.x = 275;
	_wordBox.y = 350;
	
	addChild(_wordBox);
	
	_score = new Score();
	_score.x = 25;
	_score.y = 210;
	
	addChild(_score);
	
	addEventListener(Event.ADDED_TO_STAGE, onStage);
	
	addEventListener(Event.ENTER_FRAME, gameLoop);
	
	ComboHandler.dispatcher.addEventListener(ComboEvent.COMBO_FINISHED, onWordFinished);
	
	ComboHandler.setGameInstance(this);
}

There you go. Compile the project and you can now see the score!


Step 13: Give and Take Score

Now that the score is already added on the screen, all we have to do is give score to the player when he completes a word, and take away score when he misses it. The code in GameScreen.as:

private function removeSelectedLetters(wasFromFailure:Boolean):void 
{
	var i:int;
	
	var count:int = 0;
	
	for (i = 0; i < 8; i++)
	{
		if (_blocksOnScreen[i].selected)
		{
			count++;
			
			if ((wasFromFailure && Math.random() < 0.3) || !wasFromFailure)
			{
				removeChild(_blocksOnScreen[i]);
				
				_blocksOnScreen[i] = null;
			}
			else
			{
				_blocksOnScreen[i].select();
			}
		}
	}
	
	if (wasFromFailure)
	{
		_score.addToScore( -(count * 10));
	}
	else
	{
		_score.addToScore(count * 30);
	}
}

As you can see, it is very simple: we give 30 times the number of letters of a word to the player, and take away 10 times the number of letters of the mistyped word. The compiled game is below for testing!


Step 14: Add a Timer

Adding a timer will be almost the same thing as the score. There will be only one difference: we will need to be constantly updating the timer, and the timer will have a different text. Take a look at the code for Timer.as below:

package  
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;
	
	public class Timer extends Sprite
	{
		private var _text:TextField;
		
		private var _value:int;
		
		public function Timer() 
		{
			_text = new TextField();
			_text.defaultTextFormat = new TextFormat("Verdana", 20, 0xFF0000, true, null, null, null, null, TextFormatAlign.LEFT);
			_text.width = 200;
			_text.selectable = false;
			_text.multiline = false;
			
			addChild(_text);
			
			_value = 30;
			
			_text.text = "Time: " + timeString();
		}
		
		private function timeString():String
		{
			var minutes:int = _value / 60;
			
			var seconds:int = _value % 60;
			
			return minutes.toString() + ":" + seconds.toString();
		}
		
		public function addToTime(value:int):void
		{
			_value += value;
			
			_text.text = "Time: " + timeString();
		}
		
	}

}

As you may have noticed in lines 33, 35 and 37, we are creating a different text for timer. It will consist of minutes and seconds. For that to work, _value will be the time left in the game in seconds. Now the code to add the timer, in GameScreen.as:

private var _timer:Timer;

public function GameScreen() 
{
	_background = new BackgroundImage();
	_background.x = 275;
	_background.y = 200;
	
	addChild(_background);
	
	_blocksOnScreen = new Vector.<LetterBlock>(8);
	
	populateBlocks();
	
	_wordBox = new WordBox();
	_wordBox.x = 275;
	_wordBox.y = 350;
	
	addChild(_wordBox);
	
	_score = new Score();
	_score.x = 25;
	_score.y = 210;
	
	addChild(_score);
	
	_timer = new Timer();
	
	addChild(_timer);
	
	addEventListener(Event.ADDED_TO_STAGE, onStage);
	
	ComboHandler.dispatcher.addEventListener(ComboEvent.COMBO_FINISHED, onWordFinished);
	
	ComboHandler.setGameInstance(this);
}

There are no explanations needed in the code, so hit the compile button and take a look at your timer!

But it isn't counting down, is it? Let's do that!


Step 15: Decreasing and Increasing the Time Left

As discussed in the previous step, the timer needs to be constantly updated. This is because it is constantly decreasing, second by second. Also, we should award the player more seconds when a word is successfully completed, and take away some time when a word is mistyped. Inside GameScreen.as:

private var _updateTimer:Number;

public function GameScreen() 
{
	_background = new BackgroundImage();
	_background.x = 275;
	_background.y = 200;
	
	addChild(_background);
	
	_blocksOnScreen = new Vector.<LetterBlock>(8);
	
	populateBlocks();
	
	_wordBox = new WordBox();
	_wordBox.x = 275;
	_wordBox.y = 350;
	
	addChild(_wordBox);
	
	_score = new Score();
	_score.x = 25;
	_score.y = 210;
	
	addChild(_score);
	
	_timer = new Timer();
	
	addChild(_timer);
	
	addEventListener(Event.ADDED_TO_STAGE, onStage);
	
	addEventListener(Event.ENTER_FRAME, gameLoop);
	
	ComboHandler.dispatcher.addEventListener(ComboEvent.COMBO_FINISHED, onWordFinished);
	
	ComboHandler.setGameInstance(this);
	
	_updateTimer = 0;
}

private function gameLoop(e:Event):void 
{
	ComboHandler.update();
	
	_updateTimer += 1 / 30;
	
	if (_updateTimer >= 1)
	{
		_updateTimer -= 1;
		
		_timer.addToTime(-1);
	}
}

private function removeSelectedLetters(wasFromFailure:Boolean):void 
{
	var i:int;
	
	var count:int = 0;
	
	for (i = 0; i < 8; i++)
	{
		if (_blocksOnScreen[i].selected)
		{
			count++;
			
			if ((wasFromFailure && Math.random() < 0.3) || !wasFromFailure)
			{
				removeChild(_blocksOnScreen[i]);
				
				_blocksOnScreen[i] = null;
			}
			else
			{
				_blocksOnScreen[i].select();
			}
		}
	}
	
	if (wasFromFailure)
	{
		_score.addToScore( -(count * 10));
		_timer.addToTime( -count);
	}
	else
	{
		_score.addToScore(count * 30);
		_timer.addToTime(count * 2);
	}
}

Lines 1, 27, 29 and 33 create the timer, put it on the screen and add an event listener for Event.ENTER_FRAME. This listener will add 1/30 of a second to _updateTimer on each frame (we're assuming here that the game is running at 30 fps. If it's running at 24 fps, for example, it should add 1/24 of a second). When _updateTimer reaches one or more than one second, 1 is decreased from the timer's value. In lines 84 and 89, we decrease and increase time, respectively, based on whether the player has formed an "acceptable" word or not.

Also, when the user mistypes a word, an amount of seconds equal to the number of letters is decreased from the game timer. If a word is correct, the game awards twice the number of letters as seconds for the player.

This is your result:


Step 16: Improve our Random Letter Creation Process

In the current game, sometimes too few vowels appear on the screen. That makes it extremely hard to create words. In this step, we'll change that. The solution is extremely simple: we will use an array based on weight.

This array contains all the letters from the alphabet and we will get a letter by accessing a random index within the array's limits. However, the "weight" of each letter will be different. The "weight" is simply the number of letters in the array, so if the letter "R" has weight 2, for instance, there are two "R"s in the array. Looking from a probabilistic perspective, the more number you have of a letter, the higher your chance of accessing it.

The code will certainly help explain it. This code should be placed inside GameScreen.as:

private var _letters:Array = ["A", "A", "A", "B", "C", "D", "E", "E", "E", "F", "G", "H", "I", "I", "I", "J", "K", "L", "M", "N", "O", "O", "O", "P", "Q", "R", "R", "S", "T", "U", "U", "U", "V", "W", "X", "Y", "Z"];

private function randomLetter():String
{
	return _letters[int(Math.random() * _letters.length)];
}

In line 1, you can see the array of letters. Each vowel has weight 3, which means there are always 3 of them, and the letter "R" has weight 2. This is an extremely simplified array, but a lot more could be done with its idea.

Here's another way of putting it that makes the relative weights clearer:

private var _letters:Array = [
	"A", "A", "A", 
	"B", 
	"C", 
	"D", 
	"E", "E", "E", 
	"F", 
	"G", 
	"H", 
	"I", "I", "I", 
	"J", 
	"K", 
	"L", 
	"M", 
	"N", 
	"O", "O", "O", 
	"P", 
	"Q", 
	"R", "R", 
	"S", 
	"T", 
	"U", "U", "U", 
	"V", 
	"W", 
	"X", 
	"Y", 
	"Z"
];

private function randomLetter():String
{
	return _letters[int(Math.random() * _letters.length)];
}

Although not really "visible", you can check the compiled project below:


Step 17: Preloader Create the Graphics

Right now, the base of our game is complete. We will now add a preloader and a game over screen over the next steps. First of all, we need to create the preloader graphics. This will just be a rounded square bar with "Loading" written in it. You can grab the source files to get it.

Loading bar

Step 18: Preloader Write the Code

If you are familiar with FlashDevelop, this will not be difficult. We will modify the Preloader class to put our graphics in there. Jumping to the code:

import ArtAssets.LoadingImage;
import flash.display.Sprite;
// *** snip ***
private var _loadingImage:LoadingImage;
private var _loadingMask:Sprite;

public function Preloader() 
{
	if (stage) {
		stage.scaleMode = StageScaleMode.NO_SCALE;
		stage.align = StageAlign.TOP_LEFT;
	}
	addEventListener(Event.ENTER_FRAME, checkFrame);
	loaderInfo.addEventListener(ProgressEvent.PROGRESS, progress);
	loaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioError);
	
	_loadingImage = new LoadingImage();
	_loadingImage.x = 275;
	_loadingImage.y = 200;
	
	addChild(_loadingImage);
	
	_loadingMask = new Sprite();
	_loadingMask.x = 75;
	_loadingMask.y = 175;
	
	_loadingImage.mask = _loadingMask;
}

private function progress(e:ProgressEvent):void 
{
	_loadingMask.graphics.clear();
	
	_loadingMask.graphics.beginFill(0x000000);
	_loadingMask.graphics.drawRect(0, 0, _loadingImage.width * (e.bytesLoaded / e.bytesTotal), 50);
	_loadingMask.graphics.endFill();
}

private function loadingFinished():void 
{
	removeEventListener(Event.ENTER_FRAME, checkFrame);
	loaderInfo.removeEventListener(ProgressEvent.PROGRESS, progress);
	loaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, ioError);
	
	removeChild(_loadingImage);
	
	_loadingImage.mask = null;
	_loadingImage = null;
	
	_loadingMask = null;
	
	startup();
}

In the Preloader constructor, we created an instance of the loading image inside the .swc file, as well as a sprite that will be used as a mask. This mask will create the visual representation of the "loading" process.

The progress() function is the key function here: in it we update the mask by creating a rectangle of width e.bytesLoaded / e.bytesTotal times the width of the loading bar image. The bytesLoaded and bytesTotal properties of the ProgressEvent class show us how many bytes from the game have been loaded, and what are the total bytes. That way, by diving them we get the percentage of the game loaded.

In the loadingFinished() function, we have to clear every reference to the loading image and its mask. That way they can be garbage collected and will no longer use the memory reserved for the game.

Take a look at the preloader working!


Step 19: Game Over Screen Create the Graphics

Now we need to work on the game over screen. I wanted it to be very simple, only to show how to add a screen when the game has finished. The graphics are very simple: the same game background with a "Game Over" text and a fading animation. You can see the middle of the animation below:

Game over screen

Grab the source files to use it!


Step 20: Game Over Screen Write the Code

It is time now to add the game over screen in the game. Before doing that, we will need to have a way to know when the timer reaches 0 (which means game over). In order to do that, a getter function in Timer.as must be created:

public function get value():int 
{
	return _value;
}

With that, we can now create the GameOverScreen class:

package  
{
	import ArtAssets.GameOverScreenImage;
	import flash.display.Sprite;
	import flash.events.Event;
	
	public class GameOverScreen extends Sprite
	{
		private var _image:GameOverScreenImage;
		
		public function GameOverScreen() 
		{
			_image = new GameOverScreenImage();
			_image.x = 275;
			_image.y = 200;
			
			addChild(_image);
			
			addEventListener(Event.ENTER_FRAME, update);
		}
		
		public function update(e:Event):void
		{
			if (_image.currentFrame == _image.totalFrames)
			{
				_image.stop();
			}
		}
		
	}

}

The code inside update() was created to allow the animation to play only once. That way, the fade in effect will not loop.

Going to Main.as, add this code:

private var _gameOverScreen:GameOverScreen;

public function gameOver():void
{
	removeChild(_gameScreen);
	
	_gameOverScreen = new GameOverScreen();
	
	addChild(_gameOverScreen);
}

This code will be called by GameScreen when it detects that the game was lost. Now, inside GameScreen.as:

private function gameLoop(e:Event):void 
{
	ComboHandler.update();
	
	_updateTimer += 1 / 30;
	
	if (_updateTimer >= 1)
	{
		_updateTimer -= 1;
		
		_timer.addToTime(-1);
	}
	
	if (_timer.value < 0 && parent)
	{
		Main(parent).gameOver();
	}
}

The highlighted lines are the only change to this function. They will detect when the timer has reached less than 0 and whether it still has a parent (which means it wasn't removed by Main yet). At this point, it will call the gameOver() function of Main.

Compile the project, let the timer reach 0 and see what you get:


Conclusion

Great job -- you've created a basic yet full typing game! What's next? I suggest you try making a better timer, adding block letter transitions, and adding more effects.

Advertisement