Pages

Sunday, August 22, 2010

Preloading External SWF File Generates Error #1009: Cannot access a property or method of a null object reference

Exercise Files:
Stage_Issue_Preloader.fla
Stage_Issue_Load_This.fla

When you're preloading a SWF file that has code that makes any reference to the stage, you might encounter this error:

TypeError: Error #1009: Cannot access a property or method of a null object reference.

Let's take a look at the sample files and see if we'll get that same error. Make sure that you've downloaded them and placed them in the same folder. We have 2 files:
  1. Stage_Issue_Preloader - this Flash movie will be used to preload the other file
  2. Stage_Issue_Load_This - this Flash movie will be the one that's preloaded into the preloader movie
Both files already have code in them.

Let's open the Stage_Issue_Load_This.fla file first. Go ahead and test the movie so that Flash will generate a SWF file inside the same folder that contains our Flash documents. What happens in this movie is when you hit the enter key on your keyboard, the smiley face in the center of the stage will rotate. Try it out, but make sure that you disable keyboard shortcuts by going to Control > Disable Keyboard Shortcuts. What the movie does is not really important. What's important here is that if you take a look at the code in frame 1 of the Actions layer, you will see a reference to the stage at the beginning of the code:
stage.addEventListener(KeyboardEvent.KEY_DOWN, rotateSmileys);
So just take note of this line. Although it does not seem to be causing any errors now, it will once we try to preload it into the preloader file. So let's try that.

Open the Stage_Issue_Preloader.fla file. Check the code on frame 1 of the Actions layer and you will see that we already have our basic preloader code that will preload the Stage_Issue_Load_This.swf file. Now test the movie and you should see that same error message I mentioned at the beginning of this article:

TypeError: Error #1009: Cannot access a property or method of a null object reference.

This error mentions a null object. When you say null object, it means that the object has no value and, in a way, is kind of like a non-existent object. And the null object being referred to here is actually the stage.

But how can the stage be null? Isn't there already a stage when the preloader file is launched?
Yes. But the problem is that stage can only be accessed from a display object that's already in the display list. Our stage reference (the line that says stage.addEventListener(KeyboardEvent.KEY_DOWN, rotateSmileys);) is inside the Stage_Issue_Load_This.swf file. That's the file that's being preloaded, and it won't get added to the display list until it has loaded completely. So essentially, this means that for a certain amount of time while it's being loaded, it actually won't have a stage. So that's why stage is being seen as null. You see, when you're loading an external SWF file in Flash, it can actually start reading the code inside that SWF file even though it hasn't completely loaded yet. So in this circumstance, what happens is that the external SWF file hasn't been completely loaded yet, but Flash is already trying to execute the code inside it. So it's trying to execute the stage.addEventListener... line, but as we've learned, the external SWF file does not have a stage yet. But once it's been loaded completely and has been added to the display list, then the stage reference inside the Stage_Issue_Load_This.swf file will no longer be null.

So how do we fix this?
We'll take a look at 3 ways in which we can fix this issue. Basically, what we want to do is to make sure that stage is not null when the stage.addEventListener... line gets executed.


Method #1
For the first method, we will be adding some code to the Stage_Issue_Load_This.fla file (that's the file that's going to be preloaded). So make sure that file is open, then select frame 1 of the Actions layer and then go to the Actions panel. What we'll do is we will add an event handler that will tell us when the Flash movie has been added to the stage. The event for that is Event.ADDED_TO_STAGE. This event gets dispatched by the display object whenever it's been added to the stage. So when our Flash movie gets added to the stage, only then will we let Flash execute the stage.addEventListener... line. So we'll create an Event.ADDED_TO_STAGE event handler, and then place the stage.addEventListener... line inside the event listener function associated with that event handler.
this.addEventListener(Event.ADDED_TO_STAGE, checkStage);

function checkStage(e:Event):void
{
stage.addEventListener(KeyboardEvent.KEY_DOWN, rotateSmileys);
this.removeEventListener(Event.ADDED_TO_STAGE, checkStage); //Don't forget to remove the ADDED_TO_STAGE event listener after the stage.addEventListener... line has been added already
}
So here, we have an event listener function named checkStage that get's called when the Flash movie has been added to the stage. Once it's called, only then will the stage.addEventListener... line be executed. And since the Flash movie has already been added to the stage, then we won't get the previous error anymore.

*Make sure that you test this movie first in order to generate the SWF file with the updated code before you test it with the preloader movie.

The problem with this method, though, is that the movie won't work properly if you run it by itself (meaning when you don't load it using the preloader file). When you run the Flash movie by itself, then the Event.ADDED_TO_STAGE event never gets dispatched. So the stage.addEventListener... line never gets executed. The Event.ADDED_TO_STAGE event doesn't get dispatched because when you run the Flash movie by itself, it never adds itself to its own stage. It has to be loaded by another Flash movie and then be added to that Flash movie's stage, in order for the Event.ADDED_TO_STAGE event to be dispatched. So let's go ahead and take a look at other methods as well.


Method #2
For this second method, we will be adding some code to the Stage_Issue_Load_This.fla file as well (that's the file that's going to be preloaded). So make sure that file is open, then select frame 1 of the Actions layer and then go to the Actions panel. What we'll do is we will add some sort of checker that will check whether stage is not equal to null (if stage is not equal to null, then this means that our external SWF file already has a stage). If the stage is still null, then don't execute the code. But if the stage is no longer null, then Flash can go ahead and execute the stage.addEventListener... line. So let's take that stage.addEventListener... line and put it in an if statement that checks if stage is not equal to null.
if(stage != null) {
stage.addEventListener(KeyboardEvent.KEY_DOWN, rotateSmileys);
}

But this is not quite complete yet. This if statement will only run once. We want it to keep running so that it's going to keep on checking until stage is no longer equal to null. And one way of making this if statement execute numerous times would be to put it in an ENTER_FRAME event handler.
this.addEventListener(Event.ENTER_FRAME, checkStage);

function checkStage(e:Event):void
{

if(stage != null)
{
stage.addEventListener(KeyboardEvent.KEY_DOWN, rotateSmileys);
}

}
So here, I've created an ENTER_FRAME event handler that calls the listener function named checkStage. And inside the checkStage listener function is the if statement that checks if stage is not equal to null. And since this is an ENTER_FRAME event handler, this function is going to run numerous times. Therefore, the if statement inside it will keep checking if stage is no longer null. So we've ensured that only then will the stage.addEventListener... line be executed, thus avoiding the error.

And also, let's not forget to remove the ENTER_FRAME event listener once stage is no longer null, since by that time, we no longer need to check for that condition. If we don't remove the listener then it will keep on running and needlessly take up computing resources.
this.addEventListener(Event.ENTER_FRAME, checkStage);

function checkStage(e:Event):void
{

if(stage != null)
{
stage.addEventListener(KeyboardEvent.KEY_DOWN, rotateSmileys);
this.removeEventListener(Event.ENTER_FRAME, checkStage);
}

}
*Make sure that you test this movie first in order to generate the SWF file with the updated code before you test it with the preloader movie.


Method #3
In this third method, the code will be added to the Stage_Issue_Preloader.fla file instead. This fix is much shorter. You'll only need to transfer an already existing line to the top part of the code. But before we add the code, let's take a quick look at how a loader object works:
When an external SWF file is loaded by a loader object, the SWF file gets loaded as a child of the loader object. This is another way of saying that the external SWF file actually goes inside the loader. Once loading is complete, we can then add the loader to the stage. And since the loaded SWF file is inside the loader, then the SWF file gets added to the stage along with it. Just keep that in mind as the fix in this method has something to do with that.

So now, let's go ahead and edit the code. Make sure that the Stage_Issue_Preloader.fla file is open, then select frame 1 of the Actions layer and then go to the Actions panel. Scroll down until you see the function named loaded.
function loaded(e:Event):void
{
removeChild(percent_txt);
removeChild(progressBar_mc);
addChild(myLoader);
}
This function named loaded is the listener function for the Event.COMPLETE event handler in the code. This function will get called only when the loader object has finished loading the external SWF file. It is in this function where we add the loader object named myLoader to the stage. So this means that the loader object only gets added to the stage at the end of the loading process. But we can actually add the loader earlier. We can add it to the display list immediately after we create it, and before we even start the loading process. Instead of having the addChild(myLoader); statement inside the event listener function, let's just place it after we instantiate the loader object.
var myLoader:Loader = new Loader();
var myURL:URLRequest = new URLRequest("Stage_Issue_Load_This.swf");

addChild(myLoader);
This way, the loader gets added to the stage immediately. It will be empty at first, but it will already be on the stage. And that's OK. You can place empty loader objects on the stage. It does not have to have content in order for it to be placed on the stage.

So why do we need to add the
loader before we start the loading process? Well, because if the loader is already on the stage, it means that the externally loaded SWF file gets added to the stage right away as well (since we know that the SWF actually goes inside the loader). And since it's added to the stage right away, this means that stage will not be null.

If in the first and second methods, we made it so that the Flash movie will wait for stage to NOT be null before it executes the stage.addEventListener... statement, here, we've made it so that the external SWF file has a stage right away when it starts getting loaded into the Flash movie.

You can choose any of the methods, and hopefully, it will fix this issue for you.

Friday, August 20, 2010

Under The Sea



Created by Lorenzo Solis
ADMU BS Comtech '10

Audio Credits
From soundsnap.com:
Bubble sound effect
Wave Sound effect
Submarine sound effect
Splash sound effect

From freesound.org
Underwater sound effect

Thursday, August 19, 2010

Creating Multiple TextFields and Positioning Them Using Arrays and for Loops in Flash ActionScript 3.0

In this tutorial, we are going to learn how to create multiple text fields and how to position them in different locations using an array and a for loop in Flash using ActionScript 3.

Go ahead and create a new Flash ActionScript 3.0 document. Select frame 1, then go to the Actions Panel and we'll begin writing the code.

First, create an array in which we can store the TextFields. Then create a Number variable which will be equal to the number of TextFields that we'd like to create. Let's make 5 TextFields.
var aTextFields:Array = new Array();
var nTextFields:Number = 5;

Next, create the for loop. Make i equal to 0 and make the for loop run as long as i is less than nTextFields. After each iteration of the for loop, increment i by 1.
var aTextFields:Array = new Array();
var nTextFields:Number = 5;

for(var i:Number = 0; i < nTextFields; i++) {
// We'll create the TextFields in here
}

Inside the for loop, create new instances of the TextField class and put each instance in the array using array access notation.

Example:
aTextFields[0] = new TextField();
// This create a new instance of the TextField class and
// places it in the aTextFields array with an index of 0

But, again, this will be inside the for loop, so instead of using 0 or any other number, we'll replace it with i. So the for loop will now look like this:
for(var i:Number = 0; i < nTextFields; i++) {
aTextFields[i] = new TextField();
}

Each time the for loop iterates, it will create a new TextField and place it inside the aTextFields array. So when the for loop finishes, we will have 5 TextFields inside the aTextFields array. And each of those TextFields can be targeted using Array Access Notation. For example, if we'd like to set the border property of the TextFields to true, then we can type aTextFields[i].border = true; . So let's go back into the for loop and set some of the properties that we want our TextFields to have.
for(var i:Number = 0; i < nTextFields; i++) {
aTextFields[i] = new TextField();
aTextFields[i].width = 100;
aTextFields[i].height = 25;
aTextFields[i].border = true;
aTextFields[i].text = "This is TextField " + i;
}

If you test the movie now, you won't see the TextFields just yet. That's because we still need to add them to the display list. So let's make sure we do that.
for(var i:Number = 0; i < nTextFields; i++) {
aTextFields[i] = new TextField();
aTextFields[i].width = 100;
aTextFields[i].height = 25;
aTextFields[i].border = true;
aTextFields[i].text = "This is TextField " + i;
addChild(aTextFields[i]);
}

And then if you test the movie now, you'll see all the TextFields come out on the same spot. If we didn't put in the text inside the TextFields, then it will actually look like we only have 1 TextField. But we have 5. They're just all on the same spot and are just on top of each other. We could try randomizing their x and y properties so that they'll come out in different locations on the stage. We can use the random() method of the Math class for that. For example, we could type aTextFields[i].x = Math.random * 300; . That will create a number that can be anywhere between 0 or greater, but less than 300, which will then be assigned to the x property of the TextField. So let's go ahead and randomize the locations of our TextFields.
for(var i:Number = 0; i < nTextFields; i++) {
aTextFields[i] = new TextField();
aTextFields[i].width = 100;
aTextFields[i].height = 25;
aTextFields[i].border = true;
aTextFields[i].text = "This is TextField " + i;
aTextFields[i].x = Math.random() * 300;
aTextFields[i].y = Math.random() * 300;
addChild(aTextFields[i]);
}

So now, every time you test the movie, the TextFields will appear in different locations. But that's not actually what I'd like to do. Instead of having the TextFields appear at random positions, I'd like them all to be distributed evenly in one column. They'll all have the same x coordinate, but their y coordinate will each be adjusted so that they'll be displayed in a neat vertical fashion. I'll first change the x coordinate to 0 so that all the TextFields will be aligned to the left side of the stage.
for(var i:Number = 0; i < nTextFields; i++) {
aTextFields[i] = new TextField();
aTextFields[i].width = 100;
aTextFields[i].height = 25;
aTextFields[i].border = true;
aTextFields[i].text = "This is TextField " + i;
aTextFields[i].x = 0;
aTextFields[i].y = Math.random() * 300;
addChild(aTextFields[i]);
}

Now for the y coordinate of each TextField, I'd like the value to adjust accordingly. The 1st TextField (aTextFields[0]), should have a y coordinate of 0 so that it will appear at the topmost part of the stage. Then for the 2nd TextField (aTextFields[1]), I'd like for it to have a y coordinate that will put it right below the 1st TextField. Then the 3rd TextField should be below the second one, then the 4th TextField should be below the 3rd one, and so on...

So how do we make the y coordinate update within the for loop?
Well, one way of going about this would be to multiply i by the TextField's height. And then we assign the outcome of that to the y property of the TextField like so: aTextFields[i].y = i * aTextFields[i].height; . Each time the value of i increases, then the outcome of multiplying it by the TextField's height will increase as well (in Flash, the top most part of the stage is where y is equal to 0, and as we move down the stage, the value of y increases).

So our updated for loop will look like this:
for(var i:Number = 0; i < nTextFields; i++) {
aTextFields[i] = new TextField();
aTextFields[i].width = 100;
aTextFields[i].height = 25;
aTextFields[i].border = true;
aTextFields[i].text = "This is TextField " + i;
aTextFields[i].x = 0;
aTextFields[i].y = i * aTextFields[i].height;
addChild(aTextFields[i]);
}

So the 1st time the for loop runs, i will be equal to 0. And 0 multiplied by 25 (which is the TextField's height) will still be 0, so therefore, the y coordinate of the 1st TextField (aTextFields[0]) will be 0. The 2nd time the for loop runs, i will be equal to 1. And 1 multiplied by 25 is equal to 25. So the y coordinate of the 2nd TextField (aTextFields[1]) will be 25. And then the 3rd time the for loop runs, i will be equal to 2. And 2 multiplied by 25 is equal to 50. So the y coordinate of the 3rd TextField (aTextFields[2]) will be 50. And so on... So here, you'll see that after the 1st TextField, each succeeding TextField will appear right below the previous one.

NOTE: This works properly (meaning the TextFields are distributed evenly) because the TextFields all have the same height. If they didn't have the same height, the distribution will be uneven (some might overlap or others might have larger or smaller gaps between them).

Great! So now that we've been able to arrange our TextFields in one neat little column, let's just make a few more adjustments. I don't want the TextFields to be too close to the edge, so I'll just add a little offset (I'll make it 15 pixels). I'll change the x coordinate to 15, and then for the y coordinate, I'll add 15 pixels to i * aTextFields[i].height . This will put in 15 pixels worth of space from the left and top most edges of the stage.
for(var i:Number = 0; i < nTextFields; i++) {
aTextFields[i] = new TextField();
aTextFields[i].width = 100;
aTextFields[i].height = 25;
aTextFields[i].border = true;
aTextFields[i].text = "This is TextField " + i;
aTextFields[i].x = 15;
aTextFields[i].y = 15 + i * aTextFields[i].height;
addChild(aTextFields[i]);
}

So if you test the movie now, you should see that the TextFields aren't hugging the edges of the stage anymore.

And finally, one more adjustment. I think the TextFields are a little too close to each other and I would like to put vertical gaps in between them. I'll put in a gap of 5 pixels each. Now, I can't just add 5 pixels to the current y coordinate equation which is 15 + i * aTextFields[i].height . If I add 5, then it's just going to be added to the 15 pixels that we added for the initial offset. What we want is to create a 5 pixel gap in between the TextFields. So what we need to do instead is to add i * 5 to our current y coordinate equation so that it becomes 15 + i * aTextFields[i].height + i * 5 . This will give us the desired result.
for(var i:Number = 0; i < nTextFields; i++) {
aTextFields[i] = new TextField();
aTextFields[i].width = 100;
aTextFields[i].height = 25;
aTextFields[i].border = true;
aTextFields[i].text = "This is TextField " + i;
aTextFields[i].x = 15;
aTextFields[i].y = 15 + i * aTextFields[i].height + i * 5;
addChild(aTextFields[i]);
}

So if you test the movie now, you should now see some vertical space in between the TextFields.

Here's the code in full:
var aTextFields:Array = new Array();
var nTextFields:Number = 5;

for(var i:Number = 0; i < nTextFields; i++) {
aTextFields[i] = new TextField();
aTextFields[i].width = 100;
aTextFields[i].height = 25;
aTextFields[i].border = true;
aTextFields[i].text = "This is TextField " + i;
aTextFields[i].x = 15;
aTextFields[i].y = 15 + i * aTextFields[i].height + i * 5;
addChild(aTextFields[i]);
}

Wednesday, August 18, 2010

The Walk



Art and Animation by Victor Guerrero
ADMU BS MIS '10

Audio:
-{Intro Credits}-WIP
by xUnWantedx from newgrounds.com

Friday, August 13, 2010

How to Create a Quiz in Flash - ActionScript 3 Tutorial - PART 2

[Read PART 1 here]

In the first part of this lesson, we added the functionality that will allow the user to take the quiz - we added the questions and the correct answers, and we enabled the user to submit his or her own answers by using the input field and clicking on the submit button. All of that happens in frame 1 of our simple Flash quiz.

In this part of the How to Create a Quiz in Flash using ActionScript 3 tutorial, we will be working on the following:
  1. Displaying the user's answers alongside the correct answers
  2. Checking the user's answers and computing the score
  3. Displaying the user's score
All of these will happen in frame 2 of our Flash movie. So let's go ahead and take a look at the elements that we have on this frame. Select frame 2 of the main timeline and take a look at the stage. You will see that there are 9 dynamic text fields. Out of these 9, there are 8 dynamic text fields that are distributed into 2 columns. Each column has 4 dynamic text fields. The ones on the left column will be used to display the answers that the user gave. The ones on the right column will display the correct answers. So what we want to do here is to just show the user some feedback so that he or she can compare his or her answers with the correct ones. And then there's one more dynamic text field right below the 2 columns. This one will be used to display the user's score. The instance names of of the dynamic text fields are as follows:

TextFields for the USER's answers - userAnswer0_txt, userAnswer1_txt, userAnswer2_txt, userAnswer3_txt

TextFields for the CORRECT answers - correctAnswer0_txt, correctAnswer1_txt, correctAnswer2_txt, correctAnswer3_txt

TextField for the SCORE - score_txt

The TextField names for the answers have numbers in them so that they will match the index values of the answers in the arrays (aUserAnswers and aCorrectAnswers). userAnswer0_txt will be for aUserAnswers[0], userAnswer1_text will be for aUserAnswers[1], and so on...

So now let's go ahead and add the code. Make sure you select frame 2 of Actions layer and then go to the Actions Panel. Let's first create a number variable:
var nScore:Number = 0;
This variable is going to be used to store the user's score. I'm initializing it to 0 just in case the user does not get any answer correctly. If that happens then the score will just stay at 0. Ok, so we'll go back to that variable later. For now, let's work on the code that will display the answers in their respective text fields. We will get the data from the arrays using array access notation, and assign each piece of data to it's corresponding text field using the text property of the TextField class. We can do it this way:
userAnswer0_txt.text = aUserAnswers[0];
userAnswer1_txt.text = aUserAnswers[1];
userAnswer2_txt.text = aUserAnswers[2];
userAnswer3_txt.text = aUserAnswers[3];

correctAnswer0_txt.text = aCorrectAnswers[0];
correctAnswer1_txt.text = aCorrectAnswers[1];
correctAnswer2_txt.text = aCorrectAnswers[2];
correctAnswer3_txt.text = aCorrectAnswers[3];
If you add in this code and then test the movie, you should see all the answers displayed in the text fields after you answer all the questions. So at this point, we can actually move on to the next part of the code that will check whether the answers the user gave are correct. But before we do that, I'd like to show you another way of displaying the answers in the TextFields. Instead of manually assigning each item to its corresponding text field one by one, we'll use a for loop instead. So go ahead and remove or comment out the code above, and we'll try this other method of displaying the answers.

Ok, so in the first method, we displayed the answers this way:
textField.text = array[index];
ex.
correctAnswer0_txt.text = aCorrectAnswers[0];

For this other method that we're doing, we need to learn another way of targeting our TextField. For example, instead of typing in correctAnswer0_txt.text, we can replace correctAnswer0_txt with this["correctAnswer0_txt"] instead. So this is what we'll have:
this["correctAnswer0_txt"].text = aCorrectAnswers[0];

This is but another way of targeting our display objects (like Buttons, MovieClips and TextFields). Let's take a look at the example again:
this["correctAnswer0_txt"]

Here, since our code is placed in the main timeline, then the this keyword in this instance would refer to the main timeline. And then inside the square brackets, we place the name of the child object that we want to target (the name must be specified as a string, so it should be in quotation marks). So what we're doing here is we are telling Flash to target the correctAnswer0_txt TextField which can be found inside this (which in this example would be the main timeline).

So what is the benefit of writing it this way instead?
In this other method of targeting display objects, we are specifying the name of the instances as strings. Because we're doing it this way, we can replace the numbers in the names with variables instead. That way, we can just have that variable be incremented inside a for loop so that we don't have to hard code in the numbers for each text field (here you see why it's important that we numbered our text fields within their respective names). If this sounds a bit confusing, let's take a look at how the new text assignment statement will look inside of the for loop:
this["correctAnswer" + i + "_txt"].text = aCorrectAnswers[i];
Here, the numbers are replaced with the variable i. For the text field's name, the join operator (+) is used to combine the variable with the rest of the name. Then as the for loop updates, so does i, therefore allowing us to iterate through the text fields and the items in the arrays. Note that i is a number, and it is being combined with strings. In this example, there is no need to convert i into a string. Because the number data is being combined with strings, Flash will automatically convert that number data into a string. So now let's go ahead and write our for loop. We'll start with the variable i being equal to 0, and then as long as i is less then aQuestions.length, we'll keep the for loop running (you can also use aCorrectAnswers.length or aUserAnswers.length since they all have the same lengths anyway). So our for loop will look like this:
for(var i:Number = 0; i < aQuestions.length; i++)
{
     this["userAnswer" + i + "_txt"].text = aUserAnswers[i];
     this["correctAnswer" + i + "_txt"].text = aCorrectAnswers[i];
}
So the first time the for loop runs, i will be equal to 0, therefore aUserAnswers[0] and aCorrectAnswers[0] will be assigned to the userAnswer0_txt and correctAnswer0_txt TextFields respectively. Then as i updates, the rest of the items in the arrays will be assigned to their respective text fields as well.

Go ahead and test your movie and you should see that this will yield the same results as with the previous method that we used. This second method would be more efficient to use since we won't have to type in the text assignment statements one by one. Imagine if our quiz had 100 questions!

Ok, so now we can move on to the part that checks whether the user's answers are correct. So how do we write the code that will determine whether each answer is correct or not? If the answer that the user submitted is the same as the corresponding item in the aCorrectAnswers array, then it means that the user got the correct answer. We know that the user's answers are stored in the aUserAnswers array so we could just compare if the items in aUserAnswers match their partners (the ones with the same index value) in the aCorrectAnswers array. We'll use an if statement for that. For each match that is detected, we will give the user a point by incrementing nScore by 1 (nScore is that variable that we created earlier, which was initialized to 0). We will place that if statement in the for loop as well, since we want to go through each item in both of the arrays. So our updated for loop is now:
for(var i:Number = 0; i < aQuestions.length; i++) 
{
     this["userAnswer" + i + "_txt"].text = aUserAnswers[i];
     this["correctAnswer" + i + "_txt"].text = aCorrectAnswers[i];
     if(aUserAnswers[i] == aCorrectAnswers[i])
     {
          nScore++;
          trace(nScore);
     }
}
So what the if statement in the for loop will do is it will compare the items with the same index value from both arrays. Each time they match, then nScore gets incremented by 1. I've added in a trace statement so we could verify if the correct computation is being made. But there's still one more modification that we need to make with regard to the comparison of the answers. You see, when strings are being compared, the process is case-sensitive. So a lower case a will not be considered equal to an uppercase A. So what happens here is that the user may have submitted the correct word, but if the casing of even just one letter is different from the item in the aCorrectAnswers array, then it will not be considered equal and the user will not be given the corresponding point. In order to fix this, we need to make sure that when the items are compared, the strings from both arrays have identical casings. It doesn't matter if they are in uppercase or lowercase, the important thing is that they are the same. So what we can do is, we can convert the casing to either uppercase or lowercase when the items are being compared in the if statement. We can convert the items by using either the toUpperCase() or toLowerCase() methods of the String class. I'm going to use the toUpperCase() method. So let's go ahead and update the code:
for(var i:Number = 0; i < aQuestions.length; i++) 
{
     this["userAnswer" + i + "_txt"].text = aUserAnswers[i];
     this["correctAnswer" + i + "_txt"].text = aCorrectAnswers[i];
     if(aUserAnswers[i].toUpperCase() == aCorrectAnswers[i].toUpperCase()) 
     {
          nScore++;
          trace(nScore);
     }
}
So now, when each pair is compared, they will be converted into the same casing. Note that this will not affect how the words are displayed in the text fields. The conversion to upper case (or lower case, if that's what you chose), will only be for the if statement condition in this example.

So now, if you test the movie and try out the quiz again, you should see nScore update in the output window (provided that you get at least one answer right, if you don't get any answers right, then nScore never gets updated and won't get traced).

Lastly, we'd like to display the final score in the score_txt text field. We'll display the score once the for loop has finished checking all the answers. To check whether the for loop is finished, we can check whether i is equal to aQuestions.length - 1. If i is equal to the array length minus one, then it means that the for loop is already at the last item (we subtract the length by 1 because the index values start at 0, where as the length count starts at 1). So we'll use another if statement inside the for loop to check for that. If i is equal to aQuestions.length - 1, then we assign nScore to score_txt using the text property of the TextField class (be sure to convert nScore to a string using the toString() method). So let's go ahead and update the code:
for(var i:Number = 0; i < aQuestions.length; i++) 
{
     this["userAnswer" + i + "_txt"].text = aUserAnswers[i];
     this["correctAnswer" + i + "_txt"].text = aCorrectAnswers[i];
     if(aUserAnswers[i].toUpperCase() == aCorrectAnswers[i].toUpperCase()) 
     {
          nScore++;
     }     
     if(i == aQuestions.length - 1) 
     {
          score_txt.text = nScore.toString();
     }
}
So there you have it. Our quiz app is now complete.

Here is the code in full:

FRAME 1
stop();

var nQNumber:Number = 0;
var aQuestions:Array = new Array();
var aCorrectAnswers:Array = new Array("Jupiter", "Mars", "war", "Titan");
var aUserAnswers:Array = new Array();

aQuestions[0] = "What is the biggest planet in our solar system?";
aQuestions[1] = "Which planet in our solar system is the 4th planet from the sun?";
aQuestions[2] = "Mars is named after the Roman god of ___.";
aQuestions[3] = "What is the name of Saturn's largest moon?";

questions_txt.text = aQuestions[nQNumber];

submit_btn.addEventListener(MouseEvent.CLICK, quiz);

function quiz(e:MouseEvent):void 
{
     aUserAnswers.push(answers_txt.text);
     answers_txt.text = "";
     nQNumber++;
     if(nQNumber < aQuestions.length) 
     {
          questions_txt.text = aQuestions[nQNumber];
     }
     else
     {
          nextFrame();
     }
}
FRAME 2
var nScore:Number = 0;

for(var i:Number = 0; i < aQuestions.length; i++) 
{
     this["userAnswer" + i + "_txt"].text = aUserAnswers[i];
     this["correctAnswer" + i + "_txt"].text = aCorrectAnswers[i];
     if(aUserAnswers[i].toUpperCase() == aCorrectAnswers[i].toUpperCase()) 
     {
          nScore++;
     }
     if(i == aQuestions.length - 1) 
     {
          score_txt.text = nScore.toString();
     }
}
PREV: How to Create a Quiz in Flash - ActionScript 3 Tutorial - PART 1

Thursday, August 12, 2010

How to Create a Quiz in Flash - ActionScript 3 Tutorial - PART 1

Exercise File
Start_Quiz_App.fla

In this tutorial, we are going to learn how to create a quiz in Flash using ActionScript 3. We're going to make use of arrays and for loops in order to complete this project.

This tutorial comes with an exercise file (the download link can be found at the top of the article). Let's go ahead and open it and take a look at its contents. This Flash movie has 2 frames. The first frame contains the elements needed for the user to take the quiz. The second frame contains elements that will display the results (answers and scores). The Flash movie should start at frame 1, and stay there until the user finishes answering all the questions. Once the user is done, the Flash movie goes to frame 2 where the answers are checked and the score is given. Since the quiz and the results are separated into two different frames, then the code will be separated as well. Frame 1 will contain the code that is needed to run the quiz (displaying questions and storing the answers submitted by the user), while frame 2 will contain the code that is needed to check the answers, compute the results and display them.

So first, let's go ahead and work on frame 1. Frame 1 contains 3 objects:
  1. questions_txt - This is a Dynamic TextField that will be used to display the questions. In the Properties Inspector, this TextField has been set to be multilined.
  2. answers_txt - This is an Input TextField where the user will type in the answer. In the Properties Inspector, this TextField has been set to have a border and to be a single line TextField.
  3. submit_btn - This button will be used by the user to submit each answer.

The first thing we'll do is to stop the Flash movie on frame 1. If we don't, then the movie will just continue looping between frames 1 and 2. So select frame 1 of the actions layer, then go to the Actions Panel and put in a stop action.
stop();

Next, let's create a couple of objects:
stop();

var nQNumber:Number = 0;
var aQuestions:Array = new Array();
var aCorrectAnswers:Array = new Array("Jupiter", "Mars", "war", "Titan");
var aUserAnswers:Array = new Array();
What are these instances for?
nQNumber
We will be storing the quiz questions in an array. Each question will therefore be numbered starting at 0. This nQNumber variable will be used to move through each of those quiz questions. After the user answers one question, we will have this number increase by one and use that number to retrieve the next question.

aQuestions
This is the array that will be used to store the questions.

aCorrectAnswers
This is the array that stores all the correct answers. Of course we will have to be the ones to provide what the correct answers are, which is why this array is already populated with values. When the array was created, the values were also declared in the array constructor, therefore populating the array. The first value will have an index of 0, the second value will have an index of 1, and so on... You will notice that the aQuestions array, which is supposed to store the questions, has not been given any values yet. Don't worry, we'll provide those values shortly using array access notation instead.

aUserAnswers
This array will be used to store the answers given by the user. Each time the user (the quiz taker) submits an answer, then that answer will go into this array. So we just create this array first, but we don't put any values in it initially. We'll have to wait for what the user will input in the answer field and use those to populate this array instead.

Next, let's go ahead and create the questions. We've provided 4 answers when the aCorrectAnswers array was created, so we'll need to provide 4 questions as well. Make sure that the order of the questions corresponds to the order of the answers as well (question index 0 should match answer index 0, question index 1 should match answer index 1, and so on...). We will populate the aQuestions array using Array Access Notation:
var nQNumber:Number = 0;
var aQuestions:Array = new Array();
var aCorrectAnswers:Array = new Array("Jupiter", "Mars", "war", "Titan");
var aUserAnswers:Array = new Array();

aQuestions[0] = "What is the biggest planet in our solar system?";
aQuestions[1] = "Which planet in our solar system is the 4th planet from the sun?";
aQuestions[2] = "Mars is named after the Roman god of ___.";
aQuestions[3] = "What is the name of Saturn's largest moon?";

So now that we've created all the needed objects and have added all the questions and answers for this first part of our project, let's now go ahead and create the actual quiz functionality.

The first thing we'll do is display the first question in the questions_txt TextField. So what we can do is get aQuestions[0] (the 0 index corresponds to the first item in the array, and therefore the first question) and assign it to the text property of our TextField like so: questions_txt.text = aQuestions[0] . But the problem here is that we'd like to be able to update to the next question after the user finishes answering the first one. So instead of manually inputting the index number, we can make use of a variable instead. And then we can just update that variable whenever needed. This is where our nQNumber variable comes in. Initially, it has a value of 0, so typing in questions_txt.text = aQuestions[nQNumber] will be the the same as typing in questions_txt.text = aQuestions[0] . And then later on, we'll add some code that will increment the value of nQNumber every time the user submits an answer, thereby updating to the next question. But first, update your code so that the first question will be displayed in the questions_txt TextField:
aQuestions[0] = "What is the biggest planet in our solar system?";
aQuestions[1] = "Which planet in our solar system is the 4th planet from the sun?";
aQuestions[2] = "Mars is named after the Roman god of ___.";
aQuestions[3] = "What is the name of Saturn's largest moon?";

questions_txt.text = aQuestions[nQNumber];
So now, if you test the movie, you should see the first question displayed in the TextField.

And now that we have that, the next thing we need to fix is how our Flash movie is going to respond whenever a quiz taker types an answer in the input field and clicks on the submit button. So let's break it down. When the user clicks on the submit button, our flash movie should:
  1. get the user's answer (what he or she inputs in the answer field) and add it to the aUserAnswers array
  2. update to the next question

Since we want all this to happen whenever the user clicks on the button, we'll have to create a CLICK event handler for submit_btn. And let's give the event listener function the name quiz.
questions_txt.text = aQuestions[nQNumber];

submit_btn.addEventListener(MouseEvent.CLICK, quiz);

function quiz(e:MouseEvent):void 
{
     // This function will be responsible for:
     // 1. pushing the user's answers to the aCorrectAnswers array
     // 2. updating to the next question
}
Let's first work on how to get the user's answer and add it to the aUserAnswers array. To do that, we'll use the push() method of the array class. We'll get the contents of the answers_txt TextField using the text property, and then we'll pass that to the push() method like so: aUserAnswers.push(answers_txt.text) . So whatever text is inside the answers_txt TextField at the time the submit button is clicked will be added to the aUserAnswers array (the first value that is pushed to the array gets an index of 0, the next one after that will get an index of 1, and so on...). Then after each answer is pushed to the array, make the input TextField blank again, clearing it for the next question. So here's how the updated event listener function will look like:
function quiz(e:MouseEvent):void 
{
     aUserAnswers.push(answers_txt.text);
     answers_txt.text = ""; // This line puts an empty string, making the TextField appear empty again
}
So after the user submits the answer, the next question should then be displayed. To display the next question, we'll need to retrieve it from the aQuestions array and assign it to the questions_txt TextField. Recall that we used questions_txt.text = aQuestions[nQNumber] (where nQNumber is initially equal to 0) in order to display the first question. The next question that follows has an index of 1. So in order to retrieve that next question, we will need to increment the value of nQNumber by 1 so that it goes from 0 to 1. Once it is incremented, we'll then assign aQuestions[nQNumber] to the questions_txt TextField once again. And then the next time the user clicks on the submit button, then nQNumber gets incremented by one again, allowing us to retrieve the item with the next index, and so on... Here's the updated event listener function:
function quiz(e:MouseEvent):void 
{
     aUserAnswers.push(answers_txt.text);
     answers_txt.text = "";
     nQNumber++;
     questions_txt.text = aQuestions[nQNumber];
}
But wait! We aren't done yet. There are still some fixes that we need to put in this function. Recall that we have a limited number of questions. In this example, we only have 4. A problem will arise when the user clicks on the submit button for the fourth time.

Why will there be a problem? There are 4 questions, so naturally, the user will end up clicking the submit button for a total of 4 times.
Yes that is true. But the 4th time the user clicks on the button, nQNumber will be increased to 4. And recall that our array index numbers start at 0, not at 1. So the last question is actually at aQuestions[3]. And aQuestions[4] is actually for a fifth question. But there is no fifth question, and trying to retrieve one from the array will cause an error.

So what do we do then?
We'll need to put an if statement that will tell our Flash movie to no longer try to retrieve any more questions, once we get to the last one.

So let's do that now. We need to get the line questions_txt.text = aQuestions[nQNumber]; and place it in an if statement. The if statement needs to check if nQNumber is less than 4. If nQNumber is still less than 4, then go ahead and retrieve the next question. If it is no longer less than 4, then we don't want our code to retrieve any more questions. So we can type in if(nQNumber < 4) as our condition. But... that's actually not what we'll do. Instead of typing in 4, we'll use aQuestions.length instead. The length property of the array class tells us how many items there are in the array. So if an array has 5 items, then the array length will be 5. If it has 6 items, then the length will be 6. In this particular example, our array has 4 items, and therefore has an array length of 4.

Why can't I just type in 4? Is there a benefit to using the length property instead?
Well, no one is stopping you from hard-coding in a value of 4, but the great thing about making use of the length property instead is that if you decide to add more questions (or maybe remove some questions), then you won't have to go back to the if statement and hard-code in a new value. The length property will automatically update itself once you add or reduce items in the array. This will be especially useful if you had numerous items in your code that need to reference how long your array is. Imagine if you hard-coded the data numerous times in different parts of your code. Then every time you decide to add or subtract items to the array, then you'll have to go back to all those hard-coded parts of your code, and manually change the value. That would not be very efficient.

So our updated event listener function will now look like this:
function quiz(e:MouseEvent):void 
{
     aUserAnswers.push(answers_txt.text);
     answers_txt.text = "";
     nQNumber++;
     if(nQNumber < aQuestions.length) 
     {
          questions_txt.text = aQuestions[nQNumber];
     }
}

Great! So once the user gets through all the questions, what do we do next?
We then tell the Flash movie to go to the next frame where the user will get the quiz results. To do that, we just add an else clause to our if statement that will instruct our Flash movie to go the the next frame. We will use the nextFrame() method of the MovieClip class to move the playhead to the next frame. So basically, what we're telling our Flash movie is that if nQNumber is no longer less than aQuestions.length, then it means that we don't have anymore questions and the quiz is done, and it can now move to frame 2 where the results will be displayed. So finally, our updated event listener function will look like this:
function quiz(e:MouseEvent):void 
{
     aUserAnswers.push(answers_txt.text);
     answers_txt.text = "";
     nQNumber++;
     if(nQNumber < aQuestions.length) 
     {
          questions_txt.text = aQuestions[nQNumber];
     }
     else 
     {
          nextFrame();
     }
}

In the next part of this tutorial, we will work on the code that will display the answers that the user submitted alongside the correct answers. We will also work on the code that will check the answers and calculate and display the user's score.

Here's the frame 1 code in full:
stop();

var nQNumber:Number = 0;
var aQuestions:Array = new Array();
var aCorrectAnswers:Array = new Array("Jupiter", "Mars", "war", "Titan");
var aUserAnswers:Array = new Array();

aQuestions[0] = "What is the biggest planet in our solar system?";
aQuestions[1] = "Which planet in our solar system is the 4th planet from the sun?";
aQuestions[2] = "Mars is named after the Roman god of ___.";
aQuestions[3] = "What is the name of Saturn's largest moon?";

questions_txt.text = aQuestions[nQNumber];

submit_btn.addEventListener(MouseEvent.CLICK, quiz);

function quiz(e:MouseEvent):void 
{
     aUserAnswers.push(answers_txt.text);
     answers_txt.text = "";
     nQNumber++;
     if(nQNumber < aQuestions.length) 
     {
          questions_txt.text = aQuestions[nQNumber];
     } 
     else 
     {
          nextFrame();
     }
}

NEXT: How to Create a Quiz in Flash - ActionScript 3 Tutorial - PART 2

Thursday, August 5, 2010

Spacelight

Here's a short animation done in Flash submitted by one of my students. It's about a big space battle. Hope you like it!

by Patrick Edward C. Reyes
ADMU BS MIS '11

Audio: multibeat2 by sampleconstruct from soundsnap.com

Monday, August 2, 2010

Completing The Project: Adding Text Scrolling

This article is continued from Formatting Externally Loaded Text in Flash ActionScript 3.0. In this last part, we will be completing the sample project by adding the text scrolling functionality.

You can add vertical text scrolling to a TextField by using the scrollV property of the TextField class. To understand the scrollV property, you must first know that each line in a multiline TextField is assigned a number starting at 1. The scrollV property refers to the line number that is at the top most part of the TextField. Initially, scrollV has a value of 1. This means that the first line is at the top most part of the TextField. If we change the scrollV property to 2, then the second line (which is the line below line 1) will move up to the top most part of the TextField, and line 1 will no longer be visible as it moves outside of the TextField's area in order to give way to line 2. And if we change the scrollV property to 3, then line 3 (which is the line below line 2) moves to the top most part of the TextField, pushing line 2 out of sight as well. To make line 2 move back to the top most part of the TextField, then we'll just have to change the scrollV property of the TextField back to 2. Notice that as the scrollV value increases, the lower lines move up. When the lower lines move up, we're actually scrolling down since more and more of the lower lines will become visible as scrollV increases. And as we decrease the value of the scrollV property, then the higher lines move back down and become visible again. So as we decrease the scrollV property, we are actually scrolling up.

So how do we increase or decrease the scrollV property?
We can use the increment and decrement operators.
Ex.
myTextField.scrollV++;
or
myTextField.scrollV--;

So to complete our project, we'll add click event handlers to the buttons that we have on stage (up_btn and down_btn). up_btn is the button with the arrow pointing up and will be used for scrolling the text up. So for up_btn, we will be decrementing the scrollV property (scrollV--). down_btn will do the opposite (scrolling down) so we will be incrementing the scrollV property for this one instead (scrollV++). So let's go ahead and add the following lines to our existing code (we can just add these at the end):
// Every time the up_btn button is clicked, the scrollV property
// gets decremented by 1
up_btn.addEventListener(MouseEvent.CLICK, scrollUp);

function scrollUp(e:MouseEvent):void 
{
     myTextField.scrollV--;
}

// Every time the down_btn button is clicked, the scrollV property
// gets incremented by 1
down_btn.addEventListener(MouseEvent.CLICK, scrollDown);

function scrollDown(e:MouseEvent):void 
{
     myTextField.scrollV++;
}
Test the movie, and click on the scroll buttons. You should see that you now have been able to add text scrolling functionality for your TextField.

PREV: Formatting Externally Loaded Text In ActionScript 3.0

Formatting Externally Loaded Text In Flash ActionScript 3.0

This article is a continuation of the Loading External Text Files In Flash ActionScript 3.0 tutorial. In this lesson, we'll be using a TextFormat object to style our externally loaded text. So let's go back to the code and create the ff:
  • a TextFormat object (let's name it textStyle)
  • some text formatting properties (let's change the font and the size)
var myTextField:TextField = new TextField();
var textURL:URLRequest = new URLRequest("summer.txt");
var textLoader:URLLoader = new URLLoader();

// This next line creates the TextFormat object which will be used to
// change the text formatting
var textStyle:TextFormat = new TextFormat();

addChild(myTextField);

// Add in some formatting properties using the TextFormat object.
// Let's change the font to Verdana and the size to 14
textStyle.font = "Verdana";
textStyle.size = 14;

myTextField.border = true;
myTextField.multiline = true;
myTextField.wordWrap = true;
myTextField.width = 215;
myTextField.height = 225;
myTextField.x = 300;
myTextField. y = 50;

After creating the TextFormat object and setting some properties, we'll then need to apply the TextFormat object to our TextField. Otherwise, we won't see any formatting changes. To assign the TextFormat object to the TextField, we'll use the setTextFormat() method of the TextField class. The TextFormat object is passed to this method as a parameter. That's how it gets assigned to a specific TextField. Also, the setTextFormat() method must be used only after the text has been assigned to the TextField and not before. So in this example, we'll need to add this inside the displayText function, right after the line that says myTextField.text = e.target.data; .
function displayText(e:Event):void 
{
     myTextField.text = e.target.data;
     // This next line assigns the textStyle TextFormat object
     // to the TextField with the instance name myTextField
     myTextField.setTextFormat(textStyle); 
}
So now, if you test the movie, you should see the formatting changes applied to the text.

Here's the code in full:
import flash.text.TextField;
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.text.TextFormat;

var myTextField:TextField = new TextField();
var textURL:URLRequest = new URLRequest("summer.txt");
var textLoader:URLLoader = new URLLoader();
var textStyle:TextFormat = new TextFormat();

addChild(myTextField);

textStyle.font = "Verdana";
textStyle.size = 14;

myTextField.border = true;
myTextField.multiline = true;
myTextField.wordWrap = true;
myTextField.width = 215;
myTextField.height = 225;
myTextField.x = 300;
myTextField. y = 50;

textLoader.addEventListener(Event.COMPLETE, displayText);

function displayText(e:Event):void 
{
     myTextField.text = e.target.data;
     myTextField.setTextFormat(textStyle);
}

textLoader.load(textURL);
You'll also notice that all the text does not fit inside the TextField anymore (since we made the font size larger). In the next part, we'll fix this by learning how to add a scrolling functionality for our TextField.

PREV: Loading External Text Files In Flash ActionScript 3.0
NEXT: Completing The Project: Adding Text Scrolling