29.11.09

Flex 3 / AS3 : Save User Interface for Games

Source Code:
The commented code for this save interface can be found here.

Introduction:
I'm currently in the process of developing using the Pushbutton Engine, and it's going to be a long time before I can write a tutorial on the ideas/process involved in creating the save interface, so I've decided to just present a tutorial for using it in this post. Also if , if version 1.0 of Pushbutton Engine still doesn't have support specifically for savedata I will port this code to be compatible with the component framework.

Rundown of Classes:
  • ISaveState : This is the interface that should be implemented by objects that you intend to use as save states.
  • ExampleSaveState : An example implementation of ISaveState, used for the demo in Main.mxml.
  • SaveManager : This class contains the code for saving and loading.
  • Main.mxml : A messy, poorly labeled, but functional demonstration of the features of SaveManager.
Features of SaveManager:
  1. Ability to save objects to local storage.
  2. Ability to enumerate and load objects from local storage.
  3. Ability to delete objects from local storage.
  4. AutoSave/Load/Delete feature that allows the class to be used without a user interface.
  5. Multiple save slots, with a code specified max number of slots.
  6. Alerts to handle user confirmation for overwrites and deletes.
  7. Data displayed adapts according to current user interface specified.
SaveManager Constructor:
  • showAlerts - Boolean determines if alerts should pop up during use saveClass. This should probably be set to false when autosave is used so that the "Confirm Overwrite" dialog doesn't pop up.
  • saveClass - Class representing the class of the saved data. This is required by actionscript serialization so that the object can be successfully serialezed / deserialized from a SharedObject. Without it casting would throw type errors because type info would be lost.
  • targetSave - The object you will be saving if this instance is going to be used for saving, can also be set later using targetSaveState()
  • slotList - A component that extends the flex ListBase class. This parameter will be used for displaying the save slots.
  • saveButton - A button that will cause a save to the currently selected slot, or the autosave slot if no slot is selected.
  • loadButton - A button that will load the currently selected slot, or the autosave slot if no slot is selected.
  • deleteButton -A button that will delete the currently selected slot, or the autosave slot if no slot is selected.

Dynamic UI:
The UI adjusts depending on which component parameters are set to null.

  1. If slotList is null, only the autoSave save slot will be available to the buttons, because there is no way to select a save slot.
  2. If saveButton is null, the Create New Save... entry won't appear in the slotlist.

Demonstration:
Main.mxml defines a bunch of controls that demonstrate the SaveManagerClass.

  1. The top row of buttons determines which features are available in the current SaveManager. Clicking buttons will hide or show controls according to their nullity.
  2. The first list shows all of the save states.
  3. The save button will save the "create save info" text fields to the currently selected slot (or autosave slot).
  4. The load button loads the currently selected (or autosave) slot.
  5. The delete button deletes the currently selected (or autosave) slot.
  6. The three text inputs are for creating save data members to demonstrate saving.
  7. The data grid shows the object currently stored in targetSaveState of the SaveManager (it will change when slots are loaded)

Usage Notes:
ISaveState.as
- Implementing classes must have a parameterless constructor in order for serialization to work.

The commented code for this save interface can be found here.

5.8.09

AS3: How to Create a Cancelable Event

How to Create a Cancelable Event

Explanation:
The easiest way to explain a cancelable event is by example. Hopefully the following movie loads. In case it doesn't, following is a description:

  • There is a TextField which displays "Points Available: 5". The 5 represents an int in code that keeps track of the points available.
  • There are two Spinners (Spinners spinners are a text field that displays a number, and a right and left arrow button to increase or decrease the number displayed). Both Spinners start with the value "0". The available points can be distributed to the Spinners.

These two behaviors are achieved using cancelable events:

  1. When a Spinner is at 0 it can no longer be decreased.
  2. When there are 0 points available the Spinners can no longer increase.




Creating the Spinner:

Creating a cancelable event begins in the class which dispatches the event. In this case that is the Spinner class with its "SCROLL_RIGHT" and "SCROLL_LEFT" events. In this code _btnLeft and _btnRight are Buttons with their position relating to Left and Right. I won't run through the code for laying out the buttons and textfield, but if you don't know how to do it, you can download the full source at the end of this tutorial and read comments. This code goes in the Spinner class:

    private function addEventListeners():void
{
// Event called when left button is clicked
_btnLeft.addEventListener(MouseEvent.CLICK, startLeftScroll);
// Event called when right button is clicked
_btnRight.addEventListener(MouseEvent.CLICK, startRightScroll);
}pre>


addEventListeners() is called once, at the creation of the Spinner class. It sets up event listeners for when the Left and Right Buttons are clicked. These clicks will cause the Spinner class to dispatch SCROLL events, as shown in the following code:
private function startRightScroll(e:Event):void
{
// Add a listener for the dispatched event to allow cancel
// behavior.
addEventListener("SCROLL_RIGHT", endRightScroll);

// Dispatch the event
dispatchEvent(new Event("SCROLL_RIGHT", false, true));
}

private function startLeftScroll(e:Event):void
{
// Add a listener for the dispatched event to allow cancel
// behavior.
addEventListener("SCROLL_LEFT", endLeftScroll);

// Dispatch the event
dispatchEvent(new Event("SCROLL_LEFT", false, true));
}


In the previous code, an event listener is added for the event that is about to be dispatched. It is important to do things in this order so that the Spinner class is the last object to be notified about a SCROLL event, giving the other classes a chance to mark the event as cancelled. It is also important to note that when dispatching the events, the "cancelable" parameter is set to "true". Finally the code that actually controls if the behavior occurs or not is contained in the endLeftScroll() and endRightScroll() methods.
public function endRightScroll(e:Event):void
{
// Remove the event listener
removeEventListener("SCROLL_RIGHT", endRightScroll);

// If the default behavior hasn't been cancelled
if (e.isDefaultPrevented()==false)
{
// Increase the value and refresh the textbox
_value += 1;
updateValue();
}
}

private function endLeftScroll(e:Event):void
{
// If the default behavior hasn't been cancelled
if (e.isDefaultPrevented()==false)
{
// Decrease the value and refresh the textbox
_value -= 1;
updateValue();
}
}



In these methods, A call is first made to removeEventListener(). As previously mentioned, this is done because you want to add a new event listener each time the event is dispatched, so that the Spinner object will always be the last object to get notified of the SCROLL event. The next thing to check is e.isDefaultPrevented(). If the event has been canceled, that means another class has called e.preventDefault(), which will mark isDefaultPrevented() as "true". So if isDefaultPrevented() == false, then you complete the default behavior of the event. In this case, the response to the behavior being cancelled is to just exit the method without making any changes.

Canceling the Event:

The spinner class has now been created in a way which will allow the SCROLL_RIGHT and SCROLL_LEFT events to be cancelled, all that remains is to create the code which cancels them when necessary. In the Main class _spinner1 and _spinner2 are Spinner instances, and _points is the number of points available to be distributed. After creating the controls, addEventListener() is called on the Spinners as follows:

   private function addEventListeners():void
{
// Listen for left or right scrolling on spinner 1
_spinner1.addEventListener("SCROLL_RIGHT", pointAdded);
_spinner1.addEventListener("SCROLL_LEFT", pointSubtracted);

// Listen for left or right scrolling on spinner 2
_spinner2.addEventListener("SCROLL_RIGHT", pointAdded);
_spinner2.addEventListener("SCROLL_LEFT", pointSubtracted);
}


Next pointAdded and pointSubtracted are created with if conditions which determine if the SCROLL events should be cancelled:

private function pointAdded(e:Event):void
{
// If there are points availiable
if (_points > 0)
{
// Subtract a point and update the text
_points -= 1;
updateText();
}else {
// If there are no points available
// prevent the default behavior of the
// event (cancel it).
e.preventDefault();
}
}

private function pointSubtracted(e:Event):void
{
// If the spinner value is higher than 0
if (Spinner(e.target).value > 0)
{
// Make a point available and update the text
_points += 1;
updateText();
}else {
// If the spinner is at 0
// prevent the default behavior of the
// event (cancel it).
e.preventDefault();
}
}



The zipped source code to this tutorial can be found here.

If you have comments / questions / suggestions, let me know, this is my most complicated tutorial currently and I'm trying to work on making it more clear, thanks in advance for any input.

25.7.09

AS3: Basic Testing With FUnit in FlashDevelop

Basic Testing With FUnit

Testing is an often overlooked, but important part, of developing reliable applications (in any language). In this tutorial I will cover a very basic setup of FUnit in the FlashDevelop environment. I decided to make this blog because I couldn't find a reliable tutorial for the version of FUnit (0.70.0410), probably because it is in alpha stages.

What You Need:

  • FUnit: You can find it here. As of the time I'm writing this it is still in alpha. The FAQ page does say it is generally follows NUnit for structuring, so hopefully this tutorial will remain useful in the future versions.
  • FlashDevelop and Related Files: If you don't have FlashDevelop set up for programming yet, check out this tutorial.


Purpose of Unit Testing:

The goal of testing is to guarantee the accuracy of your code. The basic idea is that you pass known arguments to each of your methods, and compare values to the output and affected objects to see if each method behaves correctly.

FUnit Classes:

The basic classes of FUnit that will be dealt with in this tutorial are:

  1. TestSuite - This will load the tests in your classes in order to put them together so the TestRunner can run them.
  2. TestRunner - This is the class that actually runs your tests and returns the output. There are a few types of TestRunner's, but the only one that will be dealt with in this tutorial is "funit.core.TestRunner"
  3. DebugTestListener - This class gives a readable debug output of your test results. There are other graphical TestRunners that could eliminate the need to use this class, but as far as I can tell the graphical output is under heavy development and I don't want this tutorial to be outdated a day after I finish it.

User Created Classes:

  1. Main - The Main class created by FlashDevelop when you make a new AS3 project.
  2. ProjectTestSuite - This class will create a collection of all tests to be run by the TestRunner.
  3. BasicClass - This class will have a method that does a basic operation which we will test.
  4. BasicClassTest - This class will contain the tests for BasicClass.

Preparing the Project:

  1. Setup the project properties so that the debug output will display correctly. This may not always be necessary, but with FUnit 0.70.0410 and FlashDevelop 3.0.2 RTM it is. For some reason when they movie is loaded as a document in FlashDevelop the debug output never happens, so you have to change it to play externally. To change how the movie is played, click Project > Properties..., set Platform target to Flash Player 10, and change the "Test Movie" dropdown menu to "Play in external player".
  2. In order to use FUnit, FUnit.swc needs to be placed somewhere in the project (I put it in lib\FUnit). Next in FlashDevelop Project window, right click FUnit.swc and select "Add to Library". Now all of the classes of FUnit should be available in your project.
Making a Test Suite:

In my opinion, the easiest way to make sure all of your tests get run, is to make a Test Suite of your own.

  package
{
import funit.core.TestSuite;

public class ProjectTestSuite extends TestSuite
{

public function ProjectTestSuite()
{
// Add the Basic Test Class tests to this suite.
add(BasicClassTest);
}

}

}

To create the TestSuite, you simply create a class that extends TestSuite. In order to specify which tests the suite contains, make calls to add() with all of the classes containing tests as arguments.

Creating The Test Class:
Ideally unit tests should be created before the classes they test. In theory, creating tests that guarantee your program is functionally sound, and then passing them all by coding the program should create a functionally sound program. If you code first it's much easier to accidentally create code that is difficult to test, or tests that use knowledge about how the code works, which doesn't neccessarily check functionality.

The class to be tested in this tutorial will have only 2 methods. add(a:int, b:int):int and getLastResult():int. add() will add two numbers, store their result in a private class var, and return the result. getLastResult() will return the last result determined by the object.

Some points to consider about how these methods will work:

  1. If getLastResult() is called before add() is called for the first time, it throws an error.
  2. After a call to add(), the number returned, getLastResult() and the sum of the arguments added, should all be equal.
  3. After a second call to add() getLastResult() should change to reflect the result of the second call.

There are definitely more thing that could be tested for in this class, but I feel like these three major point should take away most of the chance for problems to occur. Each of these seperate situations should be its own test, resulting in the following code:


 package
{

import funit.framework.Assert;

[TestFixture]
public class BasicClassTest
{

// This object will be used by each test method
private var testObject:BasicClass;

public function BasicClassTest()
{

}

/**
* This method will reset testObject to the new
* instance each time a new test is run.
*/
[SetUp]
public function setup() : void
{
testObject = new BasicClass();
}

/**
* Tests calling getLastResult() before there is
* any result.
*/
[Test]
[ExpectedError("Error")]
public function testadd_error() : void
{
// Make an illegal call to getLastResult()
testObject.getLastResult();
}

/**
* Tests the functionality of add() and getLastResult()
*/
[Test]
public function testadd() : void
{
// Test the add and getLastResult() functions
Assert.areEqual(10, testObject.add(3, 7));
Assert.areEqual(10, testObject.getLastResult());
}

/**
* Tests the functionality of add() and getLastResult()
*/
[Test]
public function testadd_twice() : void
{
// Test the add and getLastResult() functions
testObject.add(3, 7);
Assert.areEqual(17, testObject.add(8, 9));
Assert.areEqual(17, testObject.getLastResult());
}
}

}

FUnit code line by line:

  • 6. [TestFixture] is metadata that FUnit uses to process this class (essentially as if it were extending the class TestFixture). It is necessary on classes like this, intended to be included in a suite.
  • 11. An empty instance of our class to be tested, this is used later in the code.
  • 22. [SetUp] This metadata tells the TestRunner to run this method before each test is run. The running order would be setup() ... test ... setup() ... test ... setup() ... etc. It will be used for each test in this class.
  • 25. testObject = new BasicClass() Before each test is run, this will place a new instance of BasicClass in testObject. This is useful because it means you won't have to create BasicClass individually somewhere in each of your tests. A note about using private members in tests: It is bad coding to have tests that rely on the results of other tests. This is importantly because many testing api's do not guarantee the order your tests will be run in.
  • 32. [Test] This tells FUnit that this method is a Test method. You will see it above each method that is used to test.
  • 33. [ExpectedError("Error")] This means that we expect an error of class Error to occur during this method call. If an error does not occur, then the test will fail.
  • 37. testObject.getLastResult() This is an illegal call because add() has not been called previously on the testObject instance. This should cause an error to be thrown and the test to pass.
  • 47. Assert.areEquals(10, testObject.add(3, 7)) This is the heart of most unit testing. It checks the equality of the number 10, and the value returned from the add function. If they are equal then the test passes, otherwise it fails. Assert contains a number of other ways to compare objects and primitives that can all be viewed in the FUnit documentation.
  • 48. This line guarantees that the getLastResult() function returns correctly after add() has been called.
  • 59. This line calls add() for the second time in this method.
  • 60. This line guarantees that the value of getLastResult() has been correctly updated after the second call to add().

Creating the class:

Now that the test is coded, the defined behavior can be used to write the actual class. There is nothing special about this class so I am just going to post it assuming that if you are dealing with testing, you understand AS3 well enough to make a simple class:


 package
{

public class BasicClass
{

private var lastResult:int = 0;
private var initialized:Boolean = false;

public function BasicClass()
{

}

/**
* Adds two numbers and puts the result in lastResult
* @param a number 1
* @param b number 2
* @return a + b
*/
public function add(a:int, b:int):int
{
// add() has been called so set initialized to true
initialized = true;
// Save the sum
lastResult = a + b;
// Return the sum
return lastResult;
}

/**
* Get property
*
* @return The last sum calculated by this instance.
*/
public function getLastResult():int
{
// If add() has not been called, throw an error
if (initialized != true)
{
throw new Error("An addition has not previously occurred");
}
// Otherwise return the last sum
return lastResult;
}

}

}

Get it running:
All that is left is to start up the TestRunner in the Main class and run the movie in debug mode. Following is the line by line:

 package
{
import flash.display.Sprite;
import flash.events.Event;
import funit.core.TestRunner;
import funit.listeners.automation.DebugTestListener;

public class Main extends Sprite
{

public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
// Run the tests
runTests();
}

private function runTests():void
{
// Create a new TestRunner which will run the tests
var runner:TestRunner = new TestRunner();
// Create an instance of our test suite
var suite:ProjectTestSuite = new ProjectTestSuite();
// Load the suite into the runner
runner.load(suite);
// Run the loaded suite and output the results to Debug
runner.run( new DebugTestListener() );
}

}

}
  • 1-23. All standard FlashDevelop AS3 loading stuff, check here if you don't understand it.
  • 28. Create a new TestRunner (The class that actually runs the tests)
  • 30. Create an instance of the suite we created.
  • 32. Load our suite (Which thereby loads all tests added to the suite) into the test runner.
  • 34. Run the tests. The argument "DebugTestListener()" is an FUnit class which causes the results of the test to be shown in the Debug Console. For information on other forms of output, read the updates on the FUnit site.

Hopefully you've got everything working... if not, the source for this project can be found here.

Feel free to contact me with any comments or suggestions.

12.5.09

AS3: Dynamically Loading Sounds in FlashDevelop

Dynamically Loading Sounds in FlashDevelop

Dynamically loading sounds in an AS3 movies allows you to store files seperately from your movie. Depending on where and how your sound is stored there may be significant time involved in loading it. Another method of using sound in your movie by embedding it directly into your movie can be found here.

The Set Up:

  • Either upload sound to your own webspace, or find the address to one online to use for testing. The sound must be formatted as a mp3 to be loaded into a movie.
  • Create a new AS3 project space in FlashDevelop.

Non-blocking image load:

  • ... Non-Blocking?:
    Loading an image without Blocking means that while the sound is being loaded, code continues to run. This is a problem if the application demands that the sound plays immediately when you call Sound.play(). However, blocking sound loading takes some foresight in AS3, and non-blocking sound loading is adequate for many situations. If you need your sound to play immediately when you call Sound.play() and are using this method, make sure you give your sound adequate time to load before calling play().

The Code:

   // entry point
var loadedSound:Sound = new Sound(new URLRequest("http://sites.google.com/site/programmingetc/Home/sploosh.mp3")); // Begin loading sound from the web
loadedSound.play(); // Sound will play after it has finished loading.
Line By Line:


  1. Lines up with the "//entry point" comment that should be in the default as3 project in FlashDevelop.
  2. Create a sound object. By providing it's constructor with a URLRequest, it will begin downloading the sound immediately, alternately you could call loadedSound.load(URLRequest).
  3. Play the sound. Techinically this queue's it up to be played once it is finished loading.

.zip source for this project can be found here

11.5.09

AS3: Embedding Sounds in FlashDevelop

Embedding Sounds in FlashDevelop


Embedding sounds in an AS3 movie will give fast access with no preloading. As an alternative, the same sounds can be used in multiple movies. In order to update sounds without rebuilding your project, or to save space by using the same images in multiple movies, you may want to also check out a tutorial on how to load sounds from an external source here.

The Set Up:
  • Get yourself a soundclip. Flash supports many types of embedded sound including:

  • AAC, AIFF, MP3, AVI, WAV, AU

  • Create a new AS3 project space in FlashDevelop.

  • Right click on your "src" folder, in the dropdown menu click "add >> new folder"

  • Change the name of your New Folder to snd.

  • Now add your soundclip to the project in one of two ways:

  • A) Find your soundclip in the windows filesystem and drag it directly into your project and drop it on your "snd" folder.

  • B) Right click the "snd" folder, click "add >> existing item", select the soundclip you want to add.

Note: Creating the "snd" folder is not necessary, it is just good to get in the habit of organizing your projects. Alternately you could just store everything directly in the "src" folder.


Getting your image in the movie:

  1. Add the Embed tag:
    You need to create an embed tag in order to make your soundclip accessible to the movie:
    [Embed(source='snd/sound.mp3')]
    "snd" corresponds to the folder the clip is stored in, and "sound.mp3" should be replaced with the name of your file.


  2. Create a class for the Embed tag:
    In order to use the embed tag, a class variable must be defined directly below it. This class variable is instantiated to create the embedded sound. The signature of a variable used for embedding can vary, the example below is a class variable and should be added after the opening brackets of your class, before any function definitions:
    [[Embed(source='snd/sound.mp3')]

    private const embeddedSound:Class;

  3. Create an instance an embedded sound:
    In order to create a useable object from a soundclip class, you simply create a new instance if the soundclip's class, and cast it to an instance of Sound. This code should go after the //entry point comment in your AS3 project.
    var soundClip:Sound = new embeddedSound(); // Create an instance of the embedded sound

  4. Play the sound:
    All that is left is to play the sound. This code can be inserted directly below the code from step 3:
    soundClip.play(); // Play the sound

.zip source for this project can be found here

AS3: Dynamically Loading Images in FlashDevelop

Dynamically Loading Images in FlashDevelop with AS3

Dynamically loading images in an AS3 movies allows you to store images seperately from your movie. Depending on where and how your image is stored there may be significant time involved in loading an image. Another method of using images in your movie by embedding them directly into your movie can be found here.

The Set Up:
  • Either upload an image do your own webspace, or find the address to one online to use for testing. The image must be formatted as a gif, jpg, or png.
  • Create a new AS3 project space in FlashDevelop.

Non-blocking image load:

  • ... Non-Blocking?:
    Loading an image without Blocking means that while the image is being loaded, code continues to run. This is a problem if the application demands that the image displays immediately after you call addChild(). However, blocking image loading takes some foresight in AS3, and non-blocking image loading is adequate for many situations. Blocking image loading will be covered in a future tutorial.

The Code:

// entry point
var imgInstance:Loader = new Loader(); // Create a new loader object
imgInstance.load(new URLRequest("http://sites.google.com/site/programmingetc/Home/image.png")); // Begin loading the image from web
addChild(imgInstance); // Display the image
Line By Line:

  1. Lines up with the "//entry point" comment that should be in the default as3 project in FlashDevelop.
  2. Create a loader object. It will be used to load the image, and can also be used as a DisplayObject for displaying the loaded image.
  3. Call the load method, with a URL parameter that points to an image (hosted on my google webspace).
  4. Add the loader instance to the sprite. The image will be displayed when it loads.

.zip source for this project can be found here

9.5.09

AS3: Embedding Images in FlashDevelop with AS3

Embedding Images in FlashDevelop with AS3

Embedding images in an AS3 movie will give faster access to images without preloading. As an alternative, the same images can be used in multiple movies. In order to update images without rebuilding your project, or save space by using the same images in multiple movies, you may want to also check out a tutorial on how to load images from an external source here.

The Set Up:

  • Get yourself an image. I haven't found a definite confirmation of formats supported for embedding anywhere, but from personal use I know "gif/jpg/png/svg" filetypes are supported. SVG is a vector graphic file, which I used Inkscape (a free open source vector art program) to create.
  • Create a new AS3 project space in FlashDevelop.
  • Right click on your "src" folder, in the dropdown menu click "add >> new folder"
  • Change the name of your New Folder to img.
  • Now add your image to the project in one of two ways:
  • A) Find your image in the windows filesystem and drag it directly into your project and drop it on your "img" folder.
  • B) Right click the "img" folder, click "add >> existing item", select the image you want to add.

Note: Creating the "img" folder is not necessary, it is just good to get in the habit of organizing your projects. Alternately you could just store everything directly in the "src" folder.

Getting your image in the movie:

  1. Add the Embed tag:
    FlashDevelop makes it easy to generate the necessary code to embed images. Place the typing cursor on an empty line above your main function. Next, in the Project View, right click on your image and click "Insert Into Document". FlashDevelop should add something like this at the cursor:
    [Embed(source='img/image.png')]
  2. Create a class for the Embed tag:
    In order to use the embed tag, a class variable must be defined directly below it. This class variable is instantiated to create the embedded image. The signature of a variable used for embedding can vary, the example below is a class variable and should be added after the opening brackets of your class, before any function definitions:
    [[Embed(source='img/image.png')]
    private const embeddedImage:Class;
  3. Create an instance an embedded image:
    In order to create a useable object from an image class, you simply create a new instance if the image class, and cast it to an instance of DisplayObject. This code should go after the //entry point comment in your AS3 project.
    // entry point
    var imgInstance:DisplayObject = new embeddedImage();
  4. Display the image:
    All that is left is to display the image. The position and rotation can be adjusted first, or they can be left to default (zero). This code can be inserted directly below the code from step 3:
       imgInstance.x = 50; // Set x coordinate of image
    imgInstance.y = 45; // Set y coordinate of image
    imgInstance.rotation = 45; // Set rotation to 45 degrees
    addChild(imgInstance); // Add this image to the Sprite object

.zip source for this project can be found here