Making a Customizable Animal Picture Matching Game in AS3
Source files: matching.zip (CS5 format)
What you will learn:
Prerequisites:
You can download the completed source files
from here: /www.flashbynight.com/tutes/matching
The level of difficulty of this tutorial is roughly ‘advanced beginner’. If you reckon it’s a bit too tough, try some of the easier tutorials at : http://www.flashbynight.com/tutes
Let’s get to it. We’re going to learn how to make an animal matching game for kids and along the way we’re going to learn a lot of useful AS3 tips and tricks. I used Flash CS5 on the PC to create the finished product. You can use any version of Flash from Flash 8 and up, but the screenshots in this tutorial may look a little different from what you see on your own PC.
Step 1
Step 1 is where we set up our working environment. Open up Flash and modify the stage size to 700 x 500. You can use CTRL + J to do this. Save the file under the name ‘Game.fla’ and let’s move on.
We’re going to store our code in a separate document, so we need to create a document class that we call Game.as. If you are not sure how to do this, please refer to the earlier tutorial at http://www.flashbynight.com/tutes/doc_class/
We will set up our actionscript file:
package {
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class Game extends MovieClip {
public function Game() {
}//constructor
}//class
}//package
We only need to import two things: the class definitions for movie clips and the class definitions for mouse events, since we are going to control our game using the mouse.
Step 2
That’s our .as ready to work on. Let’s get our .fla file ready for business. We will need two frames: one for the intro and one for the quiz itself. I have decorated my intro page like this:
Basically, I tried to use safari-like colors. I added a picture of a giraffe. I made a fun logo, and I added a PLAY button. The most important thing is the PLAY button; everything else is just decoration.
Create your own play button, right click on it and convert it to a symbol and choose Button (or MovieClip if you wish). Give it the instance name bplay.
If you would like to create some nifty effects for the title text, you can check out how using this previous turotial: http://www.flashbynight.com/tutes/logo/
I set up the second frame as follows:
The second text box, which shows up as a dotted line, is blank for the moment. We’ll add our ‘GAME OVER’ message here, but for now, just make sure it’s a dynamic text box with the instance name congrats:
Step 3
Rules, we need rules. What we are setting out to do is to display pictures of five animals alongside their names. Their names will be scrambled up and the challenge is to match the names to the animals. We do this by clicking on a picture and then clicking on a name. Alternatively, we could click on a name first and then click on a picture. When a right choice is made, the picture is labeled and is no longer clickable. When a wrong choice is made, we hear a buzzer sound. When all the animals have been selected, we will display a ‘congrats’ message.
Step 4
We’ll need a list of variables to do this. Before the constructor function, add the following variable definitions:
//assets, variables, array definitions
public var animal_list:Array;
public var display_list:Array;
public var animal_objects:Array;
public var buttons:Array;
public var num_items:int;
public var i:int;var ii:int;
public var temp1:String;var temp2:int;var rnd1:int;var rnd2:int;
public var namebox:Namebox;
public var answer1:String;
public var answer2:String;
public var num_correct:int;
public var openpix:Openpix;
public var frame:Frame;
public var sound1:Sound1= new Sound1;
public var sound2:Sound2= new Sound2;
We have four arrays to store the names of the animal pictures and buttons and then to store object references to the pictures and buttons (this will be explained later). We need a variable to store the number of items being tested. When we use a for loop, we generally use the variable i (for iteration) and since we need to use a nested loop, we will need ii as well. The temp and rnd variables are for temporary and random values. And so on. Openpix is the name of a customized class we will create to import external picture files. Sound1 and Sound2 will be used to give feedback on whether an answer is right or wrong.
Step 5
Okay, now every developer likes to have a certain way of doing things and every developer knows that the key to good productivity is reusing code and writing code that is reusable.
I tend to have an intro screen for every game I do and every screen works in the same way. (Check out games at flashbynight.com for examples)
From the constructor function, we will call a function called startscreen(), and from there we will control the beginning of the game. In programming terms, this looks something like this:
public function Game() {
startscreen();
}//constructor
public function startscreen(){
gotoAndStop(1);
bplay.addEventListener(MouseEvent.CLICK,Nav1);
function Nav1(eventObject:MouseEvent){gotoAndStop(2); populate();}
}
So the constructor function (which plays automatically) calls startscreen(). From there, we first stop the movie, to make sure it doesn’t run on to frame 2. Next, we add an event listener to our bplay button. When the listener ‘hears’ a click on the PLAY button, it moves to frame 2 and calls the function populate(), which will ‘populate’ the screen. By the way, always be as descriptive as possible with names when programming.
Our game need not use a game loop (see tutorial at www.flashbynight.com/tutes/gameloop), but we can control the flow of events by using listeners, such as the one above.
Let’s see what we will do with our populate() function. This is the first half:
public function populate(){
num_correct=0;
answer1="";answer2="";
animal_list=["tiger","lynx","panther","cheetah","leopard"];
display_list=new Array;buttons=new Array;
for(i=0;i<animal_list.length;i++){display_list[i]=animal_list[i];}
trace(animal_list);trace(display_list);
}//populate
Step by step: We will use the variable num_correct to keep track of the correct answers so that we know when the matching game is finished. We will use answer1 and answer2 to keep track of which pictures and buttons have been clicked last. The animal list will keep track of the animals that we will show pictures of. We will need a separate array to keep track of the labels we will apply to the buttons. Then we will need a separate array to keep track of the buttons themselves.
The for loop assigns the same values to the display_list as are contained in the animal_list. We cannot simply say display_list = animal_list . If we do so, when we change a value in one list, it will affect the other list, and we don’t want that.
We could simply say:
display_list=["tiger","lynx","panther","cheetah","leopard"];
This would be easier, but we want our code to be as reusable as possible. Hence, if we create a new quiz, we only need to change one line of code instead of two.
Publish the movie by pressing CTRL + ENTER and check that everything is working. The trace statements should output two identical lists.
However, we don’t want two identical lists. We want the order to be scrambled, so that we can make a game of matching them up. Let’s see a quick and dirty method of doing this. First add this line to the populate() function, before the trace statements:
scramble(display_list);
We can keep the pictures in order and scramble the buttons by sending the display list to our scramble() function. We will write the scramble function in such a way that it is reusable – it’ll scramble any array sent to it. Let’s do it:
public function scramble(a){
for(i=0;i<30;i++){
rnd1=Math.random()*a.length;rnd2=Math.random()*a.length;
temp1=a[rnd1];a[rnd1]=a[rnd2];a[rnd2]=temp1;
}//fori
}//scramble
Basically, our function swaps two elements of the array sent to it, and it does this thirty times so that the array is good and scrambled. We use temp1 to store the first value momentarily while we write over it. Math.random() gives a number between 0 and 1, which we multiply by the length of the array. Since we’ve already declared rnd1 and rnd2 as integers, we avoid any messy decimal points. It’s not the only way to achieve this effect, but it’s simple and it works. Best of all, it is easily reusable. Flash CS5 allows you to keep code snippets handy and this is a great code snippet. It’s probably too simple to bother creating a customized class for, but that would also be a good idea if you needed to use this function a lot in your work.
Publish the game again and check that the second list of animal names have been scrambled. (Make sure you called the scramble() function before tracing the output.
Before looking at the rest of the populate() function, we will need to create two things to display on the screen:
Create a thick white box 100 x 100 pixels with the middle cut out, and give it the linkage name Frame. We will use this box to highlight whichever pictures we have selected. Since we will be displaying pictures sized 100 x 100, we want our ‘frame’ to have the same dimensions. I used a solid white line of thickness 12.45.
Next, we need to create a button for the user to click and which will display our animal names. Mine looks a bit like this (frame 1 and frame 2):
I gave the button the linkage name ‘Namebox’ and it contains a dynamic text field, ‘animalname’. The buttons should be about 100 pixels in width so that we can space things nicely.
Now, we’re ready to move on. Here is the rest of the populate() function:
public function populate(){
num_correct=0;
answer1="";answer2="";
animal_list=["tiger","lynx","panther","cheetah","leopard"];
display_list=new Array;buttons=new Array;
for(i=0;i<animal_list.length;i++){display_list[i]=animal_list[i];}
scramble(display_list);trace(animal_list);trace(display_list);
for(i=0;i<animal_list.length;i++){
namebox=new Namebox;addChild(namebox);
namebox.animalname.text=display_list[i];
namebox.mouseChildren = false;
namebox.x=50+(i*120);namebox.y=350;
buttons[i]=namebox;
}//for i
frame=new Frame;addChild(frame);frame.visible=false;
getimages();
}//populate
Following the code from where we left off, we loop through and add a namebox button for every animal in the list, then we fill in the text box. We use the variable i to control the horizontal spacing. Finally we store the reference to each button in our buttons[] array. We then add our ‘frame’, but keep it invisible for now. Finally, we call a function getimages(), which will load our images onto the screen.
A couple of notes: we could just use for(i=0;i<animal_list.length;i++) instead of for(i=0;i<animal_list.length;i++), since we know we are going to use five images, but once again we want to make our code reusable. The line, namebox.mouseChildren = false; ensures that we will be clicking on the button itself and not the text box underneath.
Go ahead and test it if you like, but you’ll need to remove the getimages() line before you do, since we haven’t written it yet.
Step 6
Time to load up the pictures. Of course, before we do that, we need to source some pictures. Here are mine:
By the way, the lynx is the one with ‘whiskers’. The panther is the black one (bet you thought they were pink). The leopard and the cheetah both have spots but the cheetah is leaner and more streamlined (so it can sprint). I watch a lot of Animal Planet when I’m not coding.
I got these pictures from the clip art collection at http://office.microsoft.com/en-us/ Read the license there if you like; you’re free to do anything with their pictures except sell them. I simply imported them into PowerPoint and clicked ‘Save as Picture’. (See, Microsoft are not so bad after all.) Then I edited them so they are all 100 x 100 - a good size to work with. I saved them as jpg files under their names – cheetah.jpg, tiger.jpg etc. The names must match the ones in the display_list array.
Loading pictures is a tricky business in flash so here is where we can create a custom class (more reusability). What I want is to create a class called openpix. This class will have a function called getpix(a,x,y), which will load a jpeg entitled a at coordinates x and y. So to load a picture, we will use the code openpix.getpix(a,x,y).
We will do the ‘under the hood’ stuff in a minute. Let’s create our getimages() function first:
public function getimages(){
animal_objects=new Array;
for(i=0;i<animal_list.length;i++){
openpix=new Openpix;addChild(openpix);openpix.getpix(animal_list[i],70+(i*110),120);
animal_objects[i]=openpix;
}//for i
addListeners();
}//getimages
So what we have done here is to create an array to store our openpix objects. We define and add openpix as we would a MoveClip. Then we call the openpix.getpix() function that we mentioned. Finally, outside of the FOR loop, we call the next function addListeners(), which will add listeners to our pictures and our buttons.
Okay, let’s see how we put together a custom class. The following code is saved in an .as file by the name Openpix.as and stored in the same folder as Game.as.
package {
import flash.display.MovieClip;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.display.Loader;
public class Openpix extends MovieClip{
var imageLoader:Loader;
var pic_label:String;
public function Openpix() {
// constructor code
}
public function getpix(a,mcx,mcy){
pic_label=a;
var imageLoader:Loader = new Loader();
var image:URLRequest = new URLRequest(a+".jpg");
imageLoader.load(image);
addChild (imageLoader);
this.x=mcx;
this.y=mcy;
//imageLoader.x = mcx;
//imageLoader.y = mcy;
}//openpix
}
}
Okay, in brief, our class needs to import the properties of a movie clip as well as a ‘URL Loader’ – even though we are loading a jpg, not a URL. We need to define a variable pic_label, which will hold the name of the picture. Also, we need to use a Loader, which we will call imageLoader.
We have a few lines of code to load in the image and then we place the image according to the x,y coordinates given.
The way we have done it above is that the Openpix object sits at 0,0 and we reposition the picture. We could also, for various reasons, keep the picture at 0,0 relative to Openpix and reposition the object using these lines:
imageLoader.x = mcx;
imageLoader.y = mcy;
For this function to work, the images have to be in the same folder as game.swf. It will work even if this folder is online and accessed via the Internet.
Further things we could do with this class is to track whether the object has fully loaded (important when dealing with large file sizes) or use one Openpix object to lead multiple pictures. We’ll consider these things out of the scope of this tutorial.
Once again, this would be a good time to test your game; you will need to temporarily remove the line addListeners(); to do so.
Step 7
Time to add listener functions to the movie clips and the buttons, so that when they are clicked, we can perform the relevant actions.
Here it is:
public function addListeners(){
for(i=0;i<animal_objects.length;i++){
animal_objects[i].mouseChildren = false;
animal_objects[i].addEventListener(MouseEvent.CLICK, pic_click);
}//fori
for(i=0;i<display_list.length;i++){
buttons[i].addEventListener(MouseEvent.CLICK, but_click);
}//fori
}//addlisteners
Notice that we always use lots of smaller functions instead of one or two longer ones. Compartmentalization makes it easier for you to debug your work – Flash always mentions which function caused a particular error. It also makes it easier for people to read your code.
Anyway, it should be easy to see that we are looping around again and adding a listener called pic_click to the pictures and but_click to the buttons. We will need to write out both of these listener functions in just a moment. By the way, we have done a lot of things – added labels, picture, loops, but Flash handles all these actions almost instantaneously.
We need to handle our clicks. First, a function for when the user clicks a picture:
public function pic_click(event:MouseEvent){
frame.visible=true; frame.x=event.target.x;frame.y=event.target.y;
answer1=event.target.pic_label;
if(answer1==answer2){correct();}else{notcorrect();}
}//pic_click
As you can see, we use event.target to refer to the item being clicked. The first thing we want to do is to highlight the picture which is clicked. We do this by making the frame visible and then moving it to the x and y coordinates of the event.target.
We can set answer1 as equal to event.target.pic_label, which store the name of the picture, remember? We check now if it is equal to answer2, which is stored when the user clicks on a button. We then either call the function correct() or notcorrect(). We don’t want to give negative feedback if the user hasn’t clicked on a button yet, but we’ll worry about that when we get to the notcorrect() function.
Before that, let’s see what happens when the user clicks on a button:
public function but_click(event:MouseEvent){
for(i=0;i<buttons.length;i++){buttons[i].gotoAndStop(1);}
event.target.gotoAndStop(2);
answer2=event.target.animalname.text;
if(answer1==answer2){correct();}else{notcorrect();}
}//but_click
As you can see, we use a similar technique to the way we processed the picture clicks. My buttons have two frames to add a highlighting effect, (see step 5), so I use a FOR loop to set all of them to frame 1, and then event.target.gotoAndStop(2); sets only the one that was clicked to frame 2. The rest of the code is self-explanatory if you’ve understood the previous function.
Step 8
Getting there, we’re almost done. We need a function to deal with correct answers. Here we go:
public function correct(){num_correct++;sound1.play();
for(i=0;i<buttons.length;i++){if(buttons[i].animalname.text==answer2){break;}}
for(ii=0;ii<animal_list.length;ii++){if(animal_list[ii]==answer2){break;}}
buttons[i].x=animal_objects[ii].x-5;
buttons[i].y=animal_objects[ii].y+110;
buttons[i].gotoAndStop(1);
buttons[i].removeEventListener(MouseEvent.CLICK, but_click);
animal_objects[ii].removeEventListener(MouseEvent.CLICK, pic_click);
answer1="";answer2="";
if(num_correct==animal_objects.length){congrats.text="Well done! Y ou've matched all the animals!";}
}//correct
Right then, I have a ‘ting’ sound in my library with the identifier Sound1. We can play that here and increase the value of num_correct. By the way, I got the ‘ting’ sound and the buzzer sound for the wrong answer from Microsoft’s clip art site that I mentioned in step 6. Yes, they have sounds as well as images.
The next step requires a little kung fu. We use a loop with a break; statement to ensure that buttons[i] refers to the button that was clicked. We then use the same technique to ensure that animal_list[ii] (and animal_objects[ii]) refer to the animal picture chosen.
We move the button and place it underneath the correct picture. The -5 and +110 are just for alignment purposes. You may need to fiddle with these numbers.
We use a gotoAndStop() function to unhighlight the button. We remove the listener so that it can no longer be clicked. We remove the listener from the picture so it can no longer be clicked.
We reset answer1 and answer2, ready for the next choices.
We use an IF conditional to check if all the right answers have been found. If so, we display a victory statement.
Only one more thing: to process wrong answers:
public function notcorrect(){
if(answer1!="" && answer2!=""){
sound2.play();
answer1="";answer2="";
frame.visible=false;
for(i=0;i<display_list.length;i++){buttons[i].gotoAndStop(1);}//fori
}}//not correct
So we make sure that both a picture AND a button have been selected. We use an IF conditional for this.
We play our buzzer sound, which we named earlier sound2.
We reset answer1 and answer2, ready for the next choices.
We then unhighlight the picture by making the frame invisible. We reset all buttons to frame 1.
Step 9
And we’re done! Publish and test your movie. Knock on wood, it should work. If not, check the code carefully. Take a look at the source code from the link above, if you get stuck.
We’ve learned quite a few things this tutorial. What are you waiting for? Go and write some code!
Feel free to use this code as you wish, although I would really appreciate a mention to my site flashbynight.com if you do use the code.
Have a look at flashbynight.com for some interesting little flash games, all made by yours truly.