Flex & Actionscript Tutorial: Adding Game Actions

Written by:  • Edited by: Linda Richter
Updated Dec 15, 2009

In part 3 we will add some graphics resources and display them on the screen.

When writing a new program there is always a point where you first get to see the fruits of your labour. With the state and rendering “plumbing” now done we can start doing some fun stuff by adding graphics to our game and displaying them on the screen. But before we do let’s take a look at what changes we have to make in the main.mxml file.

Getting to the Fun Part

<?xml version="1.0" encoding="utf-8"?>

<mx:Application

xmlns:mx="http://www.adobe.com/2006/mxml"

layout="absolute"

width="600"

height="400"

frameRate="100"

creationComplete="creationComplete()"

enterFrame="enterFrame(event)"

currentState="MainMenu">

<mx:states>

<mx:State

name="Game"

enterState="enterGame(event)"

exitState="exitGame(event)">

</mx:State>

<mx:State name="MainMenu">

<mx:AddChild relativeTo="{myCanvas}" position="lastChild">

<mx:Button x="525" y="368" label="Start" id="btnStart" click="startGameClicked(event)"/>

</mx:AddChild>

</mx:State>

</mx:states>

<mx:Canvas x="0" y="0" width="100%" height="100%" id="myCanvas"/>

<mx:Script>

<![CDATA[

protected var inGame:Boolean = false;

public function creationComplete():void

{

}

public function enterFrame(event:Event):void

{

if (inGame)

{

GameObjectManager.Instance.enterFrame();

myCanvas.graphics.clear();

myCanvas.graphics.beginBitmapFill(GameObjectManager.Instance.backBuffer, null, false, false);

myCanvas.graphics.drawRect(0, 0, this.width, this.height);

myCanvas.graphics.endFill();

}

}

protected function startGameClicked(event:Event):void

{

currentState = "Game"

}

protected function enterGame(event:Event):void

{

GameObjectManager.Instance.startup();

inGame = true;

}

protected function exitGame(event:Event):void

{

GameObjectManager.Instance.shutdown();

inGame = false;

}

]]>

</mx:Script>

</mx:Application>

There is only one change made to the exitState function which calls the GameObjectManager shutdown function. This will allow the GameObjectManager to clean up its resources when we leave the Game state. Now let’s move onto the changes to the GameObjectManager.

GameObjectManager.as

package

{

import flash.display.*;

import mx.collections.*;

import mx.core.*;

public class GameObjectManager

{

// double buffer

public var backBuffer:BitmapData;

// colour to use to clear backbuffer with

public var clearColor:uint = 0xFF0043AB;

// static instance

protected static var instance:GameObjectManager = null;

// the last frame time

protected var lastFrame:Date;

// a collection of the GameObjects

protected var gameObjects:ArrayCollection = new ArrayCollection();

// a collection where new GameObjects are placed, to avoid adding items

// to gameObjects while in the gameObjects collection while it is in a loop

protected var newGameObjects:ArrayCollection = new ArrayCollection();

// a collection where removed GameObjects are placed, to avoid removing items

// to gameObjects while in the gameObjects collection while it is in a loop

protected var removedGameObjects:ArrayCollection = new ArrayCollection();

static public function get Instance():GameObjectManager

{

if ( instance == null )

instance = new GameObjectManager();

return instance;

}

public function GameObjectManager()

{

if ( instance != null )

throw new Error( "Only one Singleton instance should be instantiated" );

backBuffer = new BitmapData(Application.application.width, Application.application.height, false);

}

public function startup():void

{

lastFrame = new Date();

new Bounce().startupBounce();

}

public function shutdown():void

{

shutdownAll();

}

public function enterFrame():void

{

// Calculate the time since the last frame

var thisFrame:Date = new Date();

var seconds:Number = (thisFrame.getTime() - lastFrame.getTime())/1000.0;

lastFrame = thisFrame;

removeDeletedGameObjects();

insertNewGameObjects();

// now allow objects to update themselves

for each (var gameObject:GameObject in gameObjects)

{

if (gameObject.inuse)

gameObject.enterFrame(seconds);

}

drawObjects();

}

protected function drawObjects():void

{

backBuffer.fillRect(backBuffer.rect, clearColor);

// draw the objects

for each (var gameObject:GameObject in gameObjects)

{

if (gameObject.inuse)

gameObject.copyToBackBuffer(backBuffer);

}

}

public function addGameObject(gameObject:GameObject):void

{

newGameObjects.addItem(gameObject);

}

public function removeGameObject(gameObject:GameObject):void

{

removedGameObjects.addItem(gameObject);

}

protected function shutdownAll():void

{

// don't dispose objects twice

for each (var gameObject:GameObject in gameObjects)

{

var found:Boolean = false;

for each (var removedObject:GameObject in removedGameObjects)

{

if (removedObject == gameObject)

{

found = true;

break;

}

}

if (!found)

gameObject.shutdown();

}

}

protected function insertNewGameObjects():void

{

for each (var gameObject:GameObject in newGameObjects)

{

for (var i:int = 0; i < gameObjects.length; ++i)

{

if (gameObjects.getItemAt(i).zOrder > gameObject.zOrder ||

gameObjects.getItemAt(i).zOrder == -1)

break;

}

gameObjects.addItemAt(gameObject, i);

}

newGameObjects.removeAll();

}

protected function removeDeletedGameObjects():void

{

// insert the object accordng to it's z position

for each (var removedObject:GameObject in removedGameObjects)

{

var i:int = 0;

for (i = 0; i < gameObjects.length; ++i)

{

if (gameObjects.getItemAt(i) == removedObject)

{

gameObjects.removeItemAt(i);

break;

}

}

}

removedGameObjects.removeAll();

}

}

}

Here we have added 3 new variables: gameObjects, newGameObjects and removedGameObjects. The gameObjects collection will hold all the GameObjects that exist in the game (the GameObject class is used to represent any element in the game, and is described below). GameObjectManager will loop through this collection to update and render the GameObjects. The other two collections, newGameObjects and removedGameObjects, hold GameObjects that have been created or removed during the render loop. The reason for this is because it is almost always a bad idea to modify a collection while you are looping through it. The following code is an example of what you want to avoid.

for each (var gameObject:GameObject in gameObjects)

{

// this is a bad idea…

gameObjects.addItemAt(1, new GameObject());

gameObjects.removeItemAt(2, new GameObject());

}

This is not just a limitation of ActionScript or Flex; a number of programming languages expressly prevent the modification of a collection while looping over it. Even if it is possible to modify a collection inside a loop it is still a bad idea because the code becomes very hard to debug if something goes wrong. We avoid this situation by adding all newly created GameObjects into the newGameObjects collection (via the addGameObject function) and adding any GameObjects that have been shutdown into the removedGameObjects collection (via the removeGameObject function). These two collections then get “synced” with the main gameObjects collection before we enter a loop through the insertNewGameObjects and removeDeletedGameObjects functions.

To the startup function we add code to create and initialise a Bounce object. This object will not be part of the final game, but exists as a demonstration of how to extend the GameObject class to create an element within the game. The code for the Bounce class will be detailed below.

Our shutdown function, which used to be empty, now calls shutdownAll. ShutdownAll in turn calls shutdown on all GameObjects that have been created. This way we can clean up all GameObjects with one call to the GameObjectManagers shutdown function when leaving the Game state (via the exitGame function in our Application class).

Enterframe has been updated to include calls to the insertNewGameObjects and removeDeletedGameObjects, which as mentioned before allow the gameObjects collection to reflect any GameObjects that have been started or shutdown. We also loop over the gameObjects collection and call enterFrame on all the GameObjects. The enterFrame function in the GameObject class is where the GameObject (and any class that extends it) will update itself. For example an enemy will update its position on the screen during a call to enterFrame. Finally we make a call to drawObjects, which prepares the back buffer and then calls copyToBackBuffer on all GameObjects. It’s in the copyToBackBuffer function that a GameObject will draw itself to the frame.

Now that we have seen how the GameObjectManager manages a collection of GameObjects, let’s look at the GameObject class.

Showing page 1 of 4

Comments

Showing all 19 comments
 
chrisstout Jul 11, 2011 7:08 PM
brownplane.png
Anyonymous, the brownplane.png is in the spritelib zip, it's part of a larger png. You could use an image manipulation program to extract it. You can also download the source for this part of the tutorial here:
http://www.brighthub.com/hubfolio/matthew-casperson/media/p/49174.aspx

The brownplane.png is in the media folder of the source.
chrisstout Jul 11, 2011 5:12 PM
Depreciated Items
After changing ../media/brownplane.png to media/brownplane.png, the file compiled and works fine. Though I got the following output from the compiler (Compiling with Flex 4.5.1):

/home/chris/Downloads/flexfighters3/GameObjectManager.as(39): col: 44 Warning: 'application' has been deprecated since 4.0. Please use 'FlexGlobals.topLevelApplication'.
backBuffer = new BitmapData(Application.application.width, Application.application.height, false);
/home/chris/Downloads/flexfighters3/GameObjectManager.as(39): col: 75 Warning: 'application' has been deprecated since 4.0. Please use 'FlexGlobals.topLevelApplication'.
backBuffer = new BitmapData(Application.application.width, Application.application.height, false);
/home/chris/Downloads/flexfighters3/Bounce.as(36): col: 34 Warning: 'application' has been deprecated since 4.0. Please use 'FlexGlobals.topLevelApplication'.
if (position.x >= Application.application.width - graphics.bitmap.width) ^
/home/chris/Downloads/flexfighters3/Bounce.as(41): col: 34 Warning: 'application' has been deprecated since 4.0. Please use 'FlexGlobals.topLevelApplication'.
if (position.y >= Application.application.height - graphics.bitmap.height)

After making the following changes it worked fine:
GameObjectManager.as line 39: backBuffer = new BitmapData(FlexGlobals.topLevelApplication.width, FlexGlobals.topLevelApplication.height, false);

Bounce.as line 36: if (position.x >= FlexGlobals.topLevelApplication.width - graphics.bitmap.width)
Bounce.as line 41: if (position.y >= FlexGlobals.topLevelApplication.height - graphics.bitmap.height)
chrisstout Jul 11, 2011 4:54 PM
Compiling Errors on Fedora Core 14 Linux
Matthew, the tutorial is great, thanks.

I'm compiling on Fedora Core 14. When compiling, on both my code and the source downloaded from here, I was getting the following error:

/home/chris/Downloads/flexfighters3/ResourceManager.as(7): col: 3: Error: unable to resolve '../media/brownplane.png' for transcoding
[Embed(source="../media/brownplane.png")]
/home/chris/Downloads/flexfighters3/ResourceManager.as(7): col: 3: Error: Unable to transcode ../media/brownplane.png.

I was able to fix it by changing line 7 of ResourceManager.as from "[Embed(source="../media/brownplane.png")]" to"[Embed(source="media/brownplane.png")]"
Seth Dec 15, 2010 11:35 PM
brownplane.png
where can I get brownplane.png? It doesn't seem to be included in the zipped download from spritelib.
Ian Mar 17, 2010 7:14 AM
enterFrame
Thanks for these great tutorials which are helping me get to grips with OO for the first time.

Can you explain why enterFrame within Bounce.as calls super.enterFrame?

My understanding is that that would correspond to calling GameObject.enterFrame. But that method doesn't do anything because as you explained the implementation happens in the specific game object's enterFrame. But that's Bounce.enterFrame! So there's some circularity in description here (I realise there's no circularity in the code execution).

I was drawn to this issue by the subsequent tutorial wherein Player.enterFrame does nothing but call super.enterFrame...which does nothing.

I'm confused, so thanks in anticipation of any help.
Lyuben Feb 12, 2010 6:38 AM
Type Coercion failed: FIXED
I'm sorry, I found the problem and it was really hard to notice typo-bug. That happened because I wrote the code while looking at it instead of giving it copy paste and at the line to add the new game object:

gameObjects.addItemAt(gameObject, i);

I added gameObjects (not gameObject) and it became collection in the collection. Sorry for bothering, everything is ok and compiling!

Thanks and best wishes
Lyuben Feb 11, 2010 4:46 PM
Type Coercion failed
Hello, I just started to learn flex before a week and have a problem to run the code from chapter 4.

The error happens when traversing the ArrayCollection gameObjects in the enterFrame method. I receive the following error:

Type Coercion failed: cannot convert mx.collections::ArrayCollection@1fece801 to mygame.GameObject

I searched all around google and forums and could not find a way to work around it. It seems that there's some casting problem. Have you tried to run your code - I mean the code from this chapter 4? I'm using the latest SDK and Flex Builder 3.

Thanks in advance for any tip and best wishes!
Meg Dec 3, 2009 7:40 PM
Fixing the "Unable to Transcode" issue in Windows
For anyone else having the "brownplane" error, remember that different OSes use different path separators. In Windows, it's \, not /. Editing the line to this fixed the problem for me:

[Embed(source="\\media\\brownplane.png")]

(The double \\ is to escape \.)
Aryadi Oct 19, 2009 11:30 PM
It worked
It worked Matthew! it compiled just fine and the swf is playable errorlessly

thanks for the help! :D
Matthew Casperson Oct 18, 2009 2:49 AM
RE: Flex & Actionscript Tutorial: Adding Game Actions
That error just means that Flex can't find the image. Make sure the relative file names are valid, or you could copy the images to the same folder as the *.as files and reference them like:

[Embed(resource="brownplane.png")]
Aryadi Oct 16, 2009 11:50 PM
A compile time error
wow that was fast!

anyway, it turned out to be a typo (maybe I was sleepy, I coded it last night before go to sleep)

but then another error showed up at compile time, it's this:

D:\Flex app\tutorial dari brighthub\ResourceManager.as(7): col: 4: Error: unable to resolve '../media/brownplane.png' for transcoding

[Embed(resource="../media/brownplane.png")]

D:\flex app\tutorial dari brighthub\ResourceManager.as(7): col: 4: Error: unable to transcode ..media/brownplane.png


there, I don't know what's wrong I copy-pasted your code from line 7 but this compile time error still occured
Matthew Casperson Oct 16, 2009 11:13 PM
Error
I'm not sure. Try downloading the source code (http://www.brighthub.com/hubfolio/matthew-casperson/media/p/49174.aspx) and see if that works.
Aryadi Oct 16, 2009 10:52 PM
An error probably in the game state
I can compile the game correctly, but after I clicked on the start button an error showed up

TypeError: Error #1115: BrownPlane is not a constructor.
at ResourceManager$cinit()
at global$init()
at Bounce/startupBounce()
at GameObjectManager/startup()
at main/enterGame()
at main/___main_State1_enterState()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.states::State/http://www.adobe.com/2006/flex/mx/internal::dispatchEnterState()
at mx.core::UIComponent/applyState()
at mx.core::UIComponent/commitCurrentState()
at mx.core::UIComponent/setCurrentState()
at mx.core::UIComponent/set currentState()
at main/startGameClicked()
at main/__btnStart_click()

after that the brown plane didnt' showed up neither the blue color that used to showed up

when I did the previous tutorial it was okay the error came up after I finished with this part of lesson
Matthew Casperson Oct 15, 2009 5:11 PM
ZOrder
The order of the baseObjects ArrayCollection, held by the GameObjectManager, is determined when new BaseObjects are created and then added to the baseObjects collection in the insertNewBaseObjects function.

To modify the zorder when an object is in use you would have to sort the baseObjects collection again. You could add a function to the GameObjectManager to manually trigger the sorting of the baseObjects collection after you modify an objects zorder.
EternalFaith Oct 15, 2009 2:01 PM
zOrder
How can i change the zOrder of a element while it is inuse?
Federico Oct 13, 2009 7:58 AM
Resolved!
Ok I have resolved my issue.

for the first message simply i have missed the "public class GameObject" declaration (!) for the second problem i have deleted this declaration "import mx.effects.easing.Bounce;"

Bye :-)
Federico Oct 12, 2009 1:22 PM
can't compile
Hi all!

please give my an hand with my problem: i can't compile.
When I try using mxmlc command it responds with "Error: unable to open"
I've also tried with Flex Builder 3 (that I used for writing the code) but it found 2 errors in code:
"A file found in a source-path can not have more than one externally visible definition" (referring to GameObject.as)
"Can not resolve a multiname reference unambiguously. Bounce and mx.effects.easing:Bounce" (referring to GameObjectManager.as)

Please help me :''''(
Thanks anyway for this great tutorial!

Bye
aki Oct 1, 2009 4:20 AM
brownplane.png
For people coming to see this tutorial in the future, you might want to link the brownplane.png after the ResourceManager.as definition. Had to go download and extract the zip just for that. Of course any png would work, but it's just a minor inconvenience.

(And btw, you typoed "GraohicsResource" in the second to last chapter.)
paul Aug 3, 2009 12:52 AM
help
how would u add a generated image .. anything like this?:

protected function createBitmapData(image:DarawingObject):BitmapData
{
var bitmap:BitmapData = new bitmap.beginFill(color);
bitmap.drawCircle(0, 0, radius);
bitmap.endFill();
bitmap.draw(image);
return bitmap;
}
 
blog comments powered by Disqus
Email to a friend