Saturday, January 10, 2009

Tutorial 6: writing a preloader for AS3. Argh! Urgh!

Click here to download the full project.

Needlessly long introduction:

This tutorial was supposed to be about creating new ships, and I was happily trundling down that road when a person called bluemagica in the flash kit forums asked me why I was exporting my blueLightFighterImage symbol on the first frame (when you tick the export for Actionscript box in the properties menu, the export on first frame box is also ticked automatically) thereby instantly revealing my pitiful ignorance because I had to ask what the whole "export on first frame" option actually meant.

Then Pazil came along and informed me that the export on first frame option means that the .swf will load up this symbol right at the start, before anything happens. This is bad. Why is it bad? Trust me, it's bad. Really, just believe me. Can't you just listen to me and cease this endless questioning? Fine, I'll explain.

You've probably played a few flash games already. If you haven't, I order you immediately to go play Pillage the Village which is probably the best thing to ever happen to me in my life.

Anyway, so you've played these games, and you've noticed that at the beginning you have a little screen with a nice graphic and a progress bar showing you how much of the game has been loaded. In the funky parlance of our trade, this is called the preloader.

I hadn't thought about this problem. Before the Tragedy I hadn't had any problems because all my symbols were vector graphics. I chose vector graphics because they are so small - at the cost of a bit more processor crunching. My game never went above 70k, so there was next to no load time. But I appreciate that some of you might prefer to use great big bitmaps for your ships and whatnot, and don't forget that later we'll be wanting sound and maybe music, and all this needs to be loaded. So here was a problem that needed to be nipped in the bud.

The unfortunate thing about the export on first frame option is that everything will be loaded before the preloader starts. This results in a blank white screen for as long as the game is loading, during which you'll lose some of your potential players, some of whom might become fans, some of whom might become groupies, which let's face it, is why we're programming, right? Chicks. Yup.

So we don't want these library symbols to be exported on the first frame. We want them exported after the preloader starts.

This is where I became upset.

Why I became upset:

At the beginning of this series of tutorials I proudly declared that I would not be using the main timeline. It would all be gloriously Object Orientated fun. Now OOP is in contrast to sequential programming, and a timeline by it's very nature is sequential. So booooo timeline, I say.

It turns out that if you untick the export on first frame option, the symbol simply will not be embedded into your .swf unless it is included in the timeline. I searched for countless minutes for a way to export library symbols with AS3, but to no avail. I wanted something where in the document class I'd have:

create preloader
start importing library symbols (while updating preloader)
when everything has been loaded, start the game.

But there's no action script function for "load library symbols". The only way to tell the compiler that I want those symbols involved in the game is to manually place them onto the stage at a frame which isn't frame one. Uurruurrrgh! Yucky.

I really don't like this. It feels wrong, somehow. Dirty. Unethical even. But hey, I'm sick of fruitless google searches, so this is how we're going to do things. After hours of experimentation (far more than were necessary, more on that later) this is what I came up with:

Making the preloader:

So first of all, let's go to our blueLightFighterImage symbol, and un-tick that export on first frame box. Then in the main time-line we have to - urgh - insert a keyframe on frame 2. Within that frame, we'll place our blueLightFighter Image. Doesn't matter where, or what rotation, just so long as it's there. This frame will never actually be seen by anyone - but it will be checked by the compiler, which will then load it into memory. It's so hacky it makes me want to cry.

Even worse, just to rub salt into the wound, we could right click on frame one, go to add actions, and noooo! code in the main timeline! We write:

stop();

Admittedly, I could actually just write this in the Document class, but I really feel like wallowing in self-pity, succumbing to the daemons of user-friendly interfaces.

nooooooooooooo!

We then need to add an empty keyframe to frame 3. I'll explain why later.

While we're modifying our blueLightFighterImage, I'm also going to set the base class to Sprite, not MovieClip - I originally had it as MovieClip so that I could animate the ship explosion, but I've decided against that, and I have some funky plans. Making the ship a Sprite is easier on the memory. Bare in mind that this means back in the Ship.as file we need to modify var shipImage:MovieClip to var shipImage:Sprite otherwise we get errors... I've also made a couple of other small changes in the Ship class, such as private variables which won't have a getter or setter applied no longer have the underscore before their names. Check out the zip file of the entire project to have a look around if you want.

don't export in first frame, and set the base class to sprite

Now that icky wysiwyg nonsense is done, we can go back into the beautiful, pure simplicity of code. We're going to write a preloader class!

The pre-loader class:

After scouring the internet, I've borrowed heavily from a post by a person called plutocrat on a thread in the actionscript.org forums. I suggest you have a read to learn about potential issues and whatnot.

The preloader knows how much has been loaded by accessing the root.loaderInfo class. I want the preloader to be a separate entity, so I'm going to have to use spaceGame.instance (remember tutorial 3?) to access the root (or stage - I'm not entirely sure what the difference between root and stage is).

Also, this is obviously a class that I'm going to be using often - for future games. Though the preloader for each game will presumably be different, the basics are the same. So I write a preloader class with a default display (all it does is create a black bar that extends across the bottom of the screen) which functions that can be overridden (more on that later) by a class which extends the preloader. This new class can be specific to the game, and have all the funky graphics it wants. Such is the beauty of OOP.

I'll make a new package called preloaders which will contain both the generic preloader and later the one specific to this game.

So this is my Preloader class (I don't capitalise the L because p is a prefix so I consider preloader to be one word. You might prefer to call it preLoader, but then you need to be consistent. So a function called unlock should be called unLock... similarly I need to be consistent in my naming).

Here's the class:

////////////////////////////////////////////////////////////////////////////////
//
// AWOOGAMUFFIN
// Copyright er... I don't understand copyright. I think uploading this code
// possibly robs me of all rights. Just be nice - if you use my code, mention
// where you got it from, yeah?
//
// NOTICE: Awoogamuffin permits you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
// Hahahaha, I just copied that from the conventions page (like everything I
// do). There's no license agreement. Stop looking for it.
//
// PRELOADER - something I plan to recycle for each game I create
// I have borrowed heavily from code written by user plutocrat provided
// on actionscript.org forums on this thread:
// http://www.actionscript.org/forums/showthread.php3?t=137349
//
////////////////////////////////////////////////////////////////////////////////

package preloaders
{
import flash.display.*;
import flash.events.*;

public class Preloader extends MovieClip
{
//--------------------------------------------------------------------------
//
// VARIABLES
//
//--------------------------------------------------------------------------

protected var display:Sprite = new Sprite();

//--------------------------------------------------------------------------
//
// CONSTRUCTOR
//
//--------------------------------------------------------------------------

public function Preloader()
{
createDisplay();
addChild(display);

//accessing the root through my document class instance
SpaceGame.instance.root.loaderInfo.addEventListener(ProgressEvent.PROGRESS, progressHandler);
SpaceGame.instance.root.loaderInfo.addEventListener(Event.COMPLETE, completeHandler);
}

//--------------------------------------------------------------------------
//
// METHODS
//
//--------------------------------------------------------------------------


/* the following functions just provide a basic default. With later games,
I'll be writing preladers that inherit from this one, and override the
following functions to create displays specific to that game */

public function createDisplay()
{
//to be created in child classes. Here it will just be a rectangle defined
//in the next function
}

public function updateDisplay(percent:Number)
{
//thanks to plutocrat for revealing to me this "with" thing
with (display.graphics)
{
clear();
beginFill(0x000000);
drawRect(10,stage.stageHeight - 20,percent/100 * (stage.stageWidth - 10),10);
}
}

public function removeDisplay()
{
removeChild(display);
}

//--------------------------------------------------------------------------
//
// EVENT HANDLERS
//
//--------------------------------------------------------------------------

public function progressHandler(e:ProgressEvent)
{
updateDisplay(e.bytesLoaded/e.bytesTotal * 100);
}

public function completeHandler(e:Event)
{
removeDisplay();
dispatchEvent(new Event(Event.COMPLETE));
}
}
}


Talking a bit about Events:

Something of interest in this class is my use of events. The loaderInfo class automatically sends out an event whenever it makes progress, and also when it completes loading (it dispatches several other events, such as initialisation and errors, which you can read about in the Flash documentation). Event.COMPLETE refers to a static string variable. If you write trace(Event.COMPLETE) anywhere in your code (you need to have imported flash.events.*) you'll get "complete". That's all it is - a string. Why use Event.COMPLETE then? Why not just type

addEventListener("complete", completeHandler).

Well, that would actually work. The problem is the very real possibility that you make a typo and write:

addEventListener("compltee", completeHandler).

This means your event listener will be waiting for the string "compltee" to be dispatched before it calls completeHandler, and obviously this will never happen, and your code will not work. You won't get any warning either - the compiler doesn't know that "compltee" is wrong, so you're none the wiser. On the other hand, if you write:

addEventListener(Event.COMPLTEE, completeHandler)

The compiler will see that in the Event class there is no static const COMPLTEE and will throw an error, informing you immediately of your mistake. It's just a time-saver to be honest, but in this trade, time saving can drastically improve your mood.

I've also used dispatchEvent - when the preloader has finished, it shouts out "I've finished" with the line dispatchEvent(Event.COMPLETE). There's no reason for me not to use the static const Event.COMPLETE - in fact, it increases readability, and keeps the code consistent.

I'll be discussing events in greater detail in a future tutorial.

Back in the SpaceGame.as we'll create a preloader instance, and listen for that Event.COMPLETE before we start the game. I've written a new function startGame() which contains the code that used to be in the SpaceGame constructor.

Also, when the preloader has finished, we need to jump to frame 3, because the symbols we put in frame 2 can only be used in later frames (oh how desperately sequential!). So we bight the bullet and write gotoAndStop(3). Read about goto statements to see how bad they are. Wikipedia has a whole section on "criticism of goto usage".

Here's the spaceGame class:

////////////////////////////////////////////////////////////////////////////////
//
// AWOOGAMUFFIN
// Copyright er... I don't understand copyright. I think uploading this code
// possibly robs me of all rights. Just be nice - if you use my code, mention
// where you got it from, yeah?
//
// NOTICE: Awoogamuffin permits you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
// Hahahaha, I just copied that from the conventions page (like everything I
// do). There's no license agreement. Stop looking for it.
//
// DOCUMENT CLASS - hopefully mostly empty at all times
//
////////////////////////////////////////////////////////////////////////////////

package
{

import flash.display.*;
import flash.events.*;

import ships.Ship;
import Maths.Vector2D;
import preloaders.*;

public class SpaceGame extends MovieClip
{
//--------------------------------------------------------------------------
//
// STATIC VARIABLES (CLASS PROPERTIES)
//
//--------------------------------------------------------------------------

/*this will allow other classes to access the instance
of the document class created at the beginning of the game */
private static var _instance:SpaceGame;

//--------------------------------------------------------------------------
//
// STATIC METHODS (CLASS METHODS)
//
//--------------------------------------------------------------------------

public static function get instance():SpaceGame
{
return _instance;
}

//--------------------------------------------------------------------------
//
// VARIABLES
//
//--------------------------------------------------------------------------

private var preloader:Preloader;

//--------------------------------------------------------------------------
//
// CONSTRUCTOR
//
//--------------------------------------------------------------------------

public function SpaceGame()
{
_instance = this;

preloader = new Preloader();
addChild(preloader);

preloader.addEventListener(Event.COMPLETE, preloaderCompleteHandler);
}

//--------------------------------------------------------------------------
//
// METHODS
//
//--------------------------------------------------------------------------

public function startGame()
{
var myShip:Ship = new Ship();
addChild(myShip);
}

//--------------------------------------------------------------------------
//
// EVENT HANDLERS
//
//--------------------------------------------------------------------------

public function preloaderCompleteHandler(e:Event)
{
//always remove event listeners so that Garbage Collection can work
preloader.removeEventListener(Event.COMPLETE, preloaderCompleteHandler);
//don't need the preloader anymore
removeChild(preloader);
preloader = null;

//have to do this for the symbols to be accessible. Ugly ugly ugly
gotoAndStop(3);

//let's get on with it!
startGame();
}
}
}


Great. But when we test the game, we see no preloader. How do check it's working?

Testing the preloader. Awoogamuffin wastes his precious time:

Sometimes my noob status really costs me a lot of time and energy. There are certain aspects of Flash that you just need to know about, and I don't. An example is the "disable keyboard shortcuts" option in the control menu when testing a movie. In fact, I'm going to go back to tutorial 2 to tell people they need to do that for the A key to register in the test movie.

In this case, it was the fact that I was unaware that in the View menu while testing a movie, you can simulate download. It's brilliant. You can select download speed to see how long it would take, and obviously it allows you to test your preloader! Whee. Unfortunately for me I had a perfectly functional preloader for a long time, but thought it wasn't working because I couldn't get it to show up. Then I stumbled across this post in the NewGrounds forum, and all became clear.

Now there's an option I wish I'd known about earlier


Also, my little blueLightFighterImage is tiny. So I go ahead and stick a great big image in the library (remembering to drag it onto the stage at frame 2), and test it with the "simulate Download" option. It seems to work! Wahoo! We have a preloader.

Later on, I'll go ahead an extend the preloader to make something funky for this game. But that'll be later. First, we actually need a game.

1 comment:

  1. I am not sure because I haven't used Flash in a while but I thought that by ticking "Export for Actionscript" the graphic will automatically be exported/embedded into the .swf file.

    ReplyDelete