AS3 Starling Tower Defense Tutorial – Part 1 – External File Loading

I’ve finally finished (well… 95%) with my AS3 Starling Game Engine Demo so I can finally start writing tutorials on how you can create a Tower Defense (TD) style game using Starling. Since I have so much info planned, and there’s so much code, and I’m a developer, and I think there’s a vast lack of intermediate/advanced AS3 tutorials on the web, this will probably not be the best tutorial series if you are a beginning coder. None of this stuff is incredibly complex or “expert” or anything, but I’ll probably skip over a lot of the very basics with AS3 in general and Starling in specific.

Before you start, feel free to check out the finished product of my AS3 Starling TD Demo.
You can also find all of the code used in srcview.
Or you can download the whole project zipped up.
Or check out the repo on Bitbucket

**Updated 8/9/13 added the ZFLoader.as class and commentary since I removed the old posts from my old engine series.

If you’re a beginner, I would encourage you to use the resources I myself used to learn both AS3 (over the last 6ish years) and Starling (over the past 2 months):

Goals

I really want this stressed heavily up front. I am not a designer. My photoshop skills extend about as far as T-Rex arms. I am a developer that loves to dabble in Photoshop. This does not make my dabbles “good.” My lady is the artsy designer type and has not had anything to do with this tutorial series or game demo except to mock my dropshadow/glow SKILLZ! The point of this demo is to demonstrate the following codemonkey goals. This demo is not really meant to be something fun to play yet. Minimal balancing has been done. This is simply getting the code chops together and tutorials done so that you can learn a few things and take this knowledge (and even code) and build something that IS fun and is an actual enjoyable game experience. I will be taking this very code and using it to start a TD game myself (with the lady actually making things pretty and stylish).

Please be kind regarding the tragic state of my UI. It’s functional, damnit! 😀

In creating this tutorial, I had a few goals that I wanted to achieve from this game demo tutorial. And I have accomplished nearly all of them and Will accomplish these by the last tutorial.

Topics Covered

In this post I’ll cover:

Game Project Structure

Every project has its own structure. I haven’t come across any real “Best Practices” when it comes to structuring a game project in AS3. A lot of tutorials show snippets of game projects, or small classes but in a very loose (if not non-existent) OOP structure.

Here’s the general structure of my project inside the src/ folder and some general notes on why:

There are many other folders inside the src/ folder but those are all 3rd party libs that I’ll point out eventually.

Notes Before We Begin

I’ve tried to copy the code in the examples straight from the code you can find where it is hosted at BitBucket – ZFStarlingTutorial. For formatting purposes I have shortened tabs to 3 spaces and there’s a good chance that I may post functions and code out of the order in which they actually appear in the file to better group together functions that share the topic I’m discussing. Feel free to check out that whole repo to see the code in its (sometimes-changing) entirety. Also, some code shown may have lines missing from the core codebase found at the repo because what I’m discussing in the tutorial may have nothing to do with those lines of code, so I may remove them to try to keep this massive beast as fairly concise as possible.

Let’s Do This!

One of the things that tripped me up the hardest when making the transition to Starling from regular AS3 was External Loading. I got a trial subscription to Lynda to check out Lee Brimlow’s Starling game tutorial. That tutorial had everything being [Embed] tag loaded. There was no way to handle progress or really manage what you’re loading at that point. It was great for the tutorial, and that single series really jumpstarted my Starling skills and got me rolling along. But hey, we’re fancy developers wanting to make hardcore games right? Right?! So we need a way to load assets, to get the progress of those loading assets, and other fancy things like that.

I’ve been a huge fan of GreenSock’s TweenMax/Lite library since way back, and any chance I get to include some of his libs into my code, I go for it. Why reinvent the wheel right? LoaderMax is his loading lib that he’s created, and it’s a huge time-saver. I wrote a wrapper called ZFLoader that basically is like a loader manager sort of class. I will explain the ZFLoader below.

Game.as

com/zf/core/Game.as is the core class that begins the Game. It holds the game-wide ZFLoader instance, SharedObjectManager instance, SoundManager instance, and handles State for the whole game demo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public function Game() {
   super();
   soundMgr = new SoundManager();		
   addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
 
private function onAddedToStage(evt:Event):void {
   removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
 
   so.init();
 
   // set the initial state
   changeState(GAME_LOAD_STATE);
 
   _addUpdateListener();
}
 
public function changeState(state:int):void {
   var removed:Boolean = false;
 
   if(currentState != null) {
      _removeUpdateListener();
      currentState.destroy();
      removeChild(Sprite(currentState));
      currentState = null;
      removed = true;	
   }
 
   switch(state) {
      case GAME_LOAD_STATE:
         currentState = new GameLoad(this);
         break;
 
      case MENU_STATE:
         currentState = new Menu(this);
         break;
 
      case MAP_SELECT_STATE:
         currentState = new MapSelect(this);
         break;
 
      case GAME_LOAD_STATE:
         currentState = new GameLoad(this);
         break;
 
      case MAP_LOAD_STATE:
         currentState = new MapLoad(this);
         break;
 
      case PLAY_STATE:
         currentState = new Play(this);
         break;
 
      case GAME_OVER_STATE:
         currentState = new GameOver(this);
         break;
   }
 
   addChild(Sprite(currentState));
 
   if(removed) {
      // Add update listeners back
      _addUpdateListener();
      removed = false;
   }
}

GameLoad.as

com/zf/states/GameLoad.as is implements IZFState, which is just a simple implementation I created to specify that classes implementing IZFState will all have update() and destroy() functions. The GameLoad State handles loading my initial game assets, sound assets, etc. Anything that I need to have from Frame 1 until the user closes their browser gets handled here. This includes non-map-specific sounds, button textures, fonts, backgrounds, and further JSON data files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public function GameLoad(game:Game) {
   _game = game;
   addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
 
private function onAddedToStage(evt:Event):void {
   removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
 
   _progBar = new ProgressBar(500, 35);
   _progBar.x = 150;
   _progBar.y = 400;
   addChild(_progBar);
 
   Game.zfLoader.onProgress.add(onProgress);
   Assets.onInitialLoadComplete.add(onLoadComplete);
 
   Assets.loadInitialAssets();
}
 
public function onProgress(ratio:Number):void {
   _progBar.ratio = ratio;
}
 
public function onLoadComplete():void {
   Game.zfLoader.onProgress.remove(onProgress);
   Assets.onInitialLoadComplete.remove(onLoadComplete);
 
   Assets.init();
   _progBar.removeFromParent(true);
   _progBar = null;
 
   _game.changeState(Game.MENU_STATE);
}

I really wish I had saved or bookmarked wherever I got the ProgressBar code from. I know I copy/pasted it from some example somewhere, but I don’t remember now where it was from. Apologies for not giving credit where it’s due.

A quick note about the changeState function, were this going to be a more robust engine (as you might build on top of this code), you may or may not want to add valid States that a State may or may not change to. For example, from this GameLoad state, I would not want to allow myself to ever try to _game.changeState(Game.GAME_OVER_STATE); I shouldn’t be able to switch to a GameOver State, I haven’t even played the game yet nor even seen a title menu! So in my State classes I might have an allowableStates array or something similar just to help ensure I don’t accidentally try to jump to a state I shouldn’t be able to get to.

For more on State Machines and AS3:

ZFLoader.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package com.zf.loaders
{
   import com.greensock.events.LoaderEvent;
   import com.greensock.loading.DataLoader;
   import com.greensock.loading.ImageLoader;
   import com.greensock.loading.LoaderMax;
   import com.greensock.loading.MP3Loader;
   import com.greensock.loading.XMLLoader;
   import com.greensock.loading.display.ContentDisplay;
   import com.zf.core.Config;
   import com.zf.utils.FileUtils;
 
   import org.osflash.signals.Signal;
 
public class ZFLoader
{
   private var className:String = 'ZFLoader';
 
   private static var instance	 : ZFLoader;
   private static var allowInstantiation	: Boolean;
 
   public var queue:LoaderMax;
   public var onProgress:Signal;
   public var onComplete:Signal;
 
   private var callBackArray:Array;
 
   public static function getInstance():ZFLoader {
      if (instance == null) {
         allowInstantiation = true;
         instance = new ZFLoader();
         allowInstantiation = false;
      }
      return instance;
   }
 
   public function ZFLoader() {
      if (!allowInstantiation) {
         throw new Error("Error: Instantiation failed: Use ZFLoader.getInstance() instead of new.");
      } else {
         initQueue();
      }
   }
 
   private function initQueue():void  {
      onProgress = new Signal();
      onComplete = new Signal();
      queue =  new LoaderMax({
         name:"mainQueue",
         onProgress:onProgressHandler,
         onComplete:onCompleteHandler,
         onError:onErrorHandler,
         autoLoad:true
      });
      callBackArray = [];
   }
 
   public function addToLoad(path:String, cb:Function, id:String = '', includeAssetPath:Boolean = true, startQueueLoad:Boolean = true, opts:Object = null):void  {
      var fileName:String = FileUtils.getFilenameFromPath(path),
          ext:String = FileUtils.getExtFromFilename(path);
      // if id was not supplied, use the filename minus extension
      if(id == '') {
         id = fileName;
      }
      // Set up the fullPath for the asset
      var fullPath:String = FileUtils.getFullPath(path, includeAssetPath, ext);
 
      var useRawContent:Boolean = false;
      if(ext == Config.IMG_JPG
         || ext == Config.IMG_GIF
         || ext == Config.IMG_PNG) 
      {
         useRawContent=true;
      }
 
      // handle callback queue
      callBackArray.push({
         'fileName': fileName,
         'cb': cb,
         'id': id,
         'useRawContent': useRawContent,
         'options': opts
      });
 
      Config.log(className, 'addToLoad', "Adding callback function for " + path + " to callBackArray index: " + (callBackArray.length - 1 ).toString());
      Config.log(className, 'addToLoad', "FileName: " + fileName + " || FullPath: " + fullPath + " || ext: " + ext);
 
      switch(ext) {
         // EXTENSIONS
         case Config.DATA_XML:
         case Config.DATA_PEX:
            queue.append(new XMLLoader(fullPath, {'name':id}));
            break;
 
         case Config.IMG_JPG:
         case Config.IMG_PNG:
         case Config.IMG_GIF:
            queue.append(new ImageLoader(fullPath, {'name':id}));
            break;
 
         case Config.DATA_JSON:
         case Config.DATA_FNT:
            queue.append(new DataLoader(fullPath, {'name':id}));
            break;
 
         case Config.DATA_MP3:
            queue.append(new MP3Loader(fullPath, {'name':id, 'autoPlay': false}));
            break;
      }
 
      if(startQueueLoad) {
         queue.load();
      }
   }
 
   public function addCallbackToQueueEvent(eventName:String, callback:Function):void {
      queue.addEventListener(eventName, callback);
   }
 
   public function removeCallbackToQueueEvent(eventName:String, callback:Function):void {
      queue.removeEventListener(eventName, callback);
   }
 
   public function onCompleteHandler(event:LoaderEvent):void {
      Config.log(className, 'onCompleteHandler', "Beginning to process " + event.target);
 
      // Array of Objects used to temporarily store things that have not fully been loaded yet
      // and are not ready for callback
      var nextPassArray:Array = [];
      // item will hold the data from callBackArray that we're processing
      var item:Object;
 
      while(callBackArray.length > 0)  {
         // remove the item from the array so we dont try to process it again
         // save it in item so we can process it there
         item = callBackArray.shift();
 
         Config.log(className, 'onCompleteHandler', "Processing fileName: " + item.id);
 
         // holds the content we've just loaded. may be an image (ContentDisplay) or xml or other data type
         var contentData:* = queue.getContent(item.id);
 
         // if contentData has not loaded yet, save the item and continue
         if((contentData is ContentDisplay && contentData.rawContent == undefined) || contentData == undefined)
         {
            Config.log(className, 'onCompleteHandler', "Moving " + item.id + " to the Next Pass");
            nextPassArray.push(item);
         }
         else
         {
            var data:*;
            if(item.useRawContent) {
               data = contentData.rawContent;
            } else {
               data = contentData;
            }
            item.cb(new CallBackObject(item.id, data, item.options));
         }
      }
      callBackArray = nextPassArray;
 
      if(callBackArray.length == 0) {
         onComplete.dispatch();
         Config.log(className, 'onCompleteHandler', event.target + " is complete!");
      }
   }
 
   public function onProgressHandler(event:LoaderEvent):void {
      Config.log(className, 'onProgressHandler', "progress: " + event.target.progress);
      onProgress.dispatch(event.target.progress);
   }
 
   public function onErrorHandler(event:LoaderEvent):void {
      Config.logError(className, 'onErrorHandler', "error occured with " + event.target + ": " + event.text);
   }
}
}

That’s the loading process. Now let’s go look at Assets and see what’s happening there.

Assets.as

com/zf/core/Assets.as is the core class that handles my demo’s art, sound, font, and JSON data assets. In mobile dev, this might be your R (Resources) class. Right off the bat, you’ll notice it’s filled with loads of public static var‘s. Yup. Before I cleaned this file up, I actually had double the number of variables because I was loading in the jpg/png assets into a Bitmap variable, then later in init() I was converting them to a Texture class. Thankfully I realized that I could skip the duplicate memory overhead and we’ll see in a bit how I saved some RAM.

If you scroll up just a bit, you’ll remember that from GameLoad.as, when that State gets added to stage, it calls Assets.loadInitialAssest(). It’s a very short function:

1
2
3
4
public static function loadInitialAssets():void {
   Game.zfLoader.addToLoad('initialAssets.json', onInitialAssetsLoadComplete);
   Game.zfLoader.addToLoad('sounds.json', onInitialSoundsLoadComplete);
}

I have two main initial files that act as a sort of “manifest” of assets to load. I’m experimenting with TreeFortress’ SoundAS sound manager and it has its own way to load sounds into its internal variables. SoundAS has a load() function and it will load your sound assets for you! However, at this time, it only has an onSoundLoadComplete and an onSoundLoadError Signal that it dispatches. They have not added any onSoundProgress type Signal, so I had no easy way to tie in my progress bar with SoundAS loading my assets. So, I’ll simply load them myself here then stuff them into SoundAS.

initialAssets.json and sounds.json (found at src/assets/initialAssets.json & src/assets/sounds.json) are simple JSON files that look like this:

initialAssets.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
   "files": [
      {
         "id": "logo",
         "convertToTexture": true,
         "file": "assets/images/logo.png"
      },
	  {
         "id": "atlas",
         "file": "assets/images/atlas.png"
      },
      {
         "id": "atlasXML",
         "file": "assets/images/atlas.xml"
      }
}

sounds.json

1
2
3
4
5
6
7
8
9
10
11
{
   "files": [
      {
         "id": "ding1",
         "file": "assets/sounds/ding1.mp3"
      },
      {
         "id": "escape1",
         "file": "assets/sounds/escape1.mp3"
      }
}

These are simply files that consist more or less of an array of “id” and “file” metadata. In initialAssets you’ll also see the extra “convertToTexture” key on several of the items, but not all. This is part of my optimization to not have to keep Bitmap versions of the files.

When initialAssets.json is done loading, it calls onInitialAssetsLoadComplete(). Likewise, when sounds.json finishes, it calls onInitialSoundsLoadComplete():

From Assets.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public static function onInitialAssetsLoadComplete(cbo:CallBackObject):void {
   var initAssets:Object = Utils.JSONDecode(cbo.data);
   _itemsToLoad = initAssets.files.length;
   var obj:Object;
   for(var i:int = 0; i < _itemsToLoad; i++) {
      obj = initAssets.files[i];
      Game.zfLoader.addToLoad(obj.file, onAssetLoadComplete, obj.id, false, false, obj)
   }
   Game.zfLoader.queue.load();
}
 
public static function onInitialSoundsLoadComplete(cbo:CallBackObject):void {
   var sndAssets:Object = Utils.JSONDecode(cbo.data);
   _soundsToLoad = sndAssets.files.length;
 
   var obj:Object;
   for(var i:int = 0; i < _soundsToLoad; i++) {
      obj = sndAssets.files[i];
      Game.zfLoader.addToLoad(obj.file, onSoundAssetLoadComplete, obj.id, false, false)
   }
   Game.zfLoader.queue.load();
}
 
public static function onAssetLoadComplete(cbo:CallBackObject):void {
   _itemsToLoad--;
   Config.log('Assets', 'onAssetLoadComplete', 'LoadComplete: ' + cbo.name + " -- _itemsToLoad: " + _itemsToLoad);
 
   if(cbo.options != null 
      && cbo.options.hasOwnProperty('convertToTexture') && cbo.options.convertToTexture) 
   {
      // Add a T to the end of the file id, and auto convert it from bitmap
      Assets[cbo.name + 'T'] = Texture.fromBitmap(cbo.data);
   } 
   else 
   {
      Assets[cbo.name] = cbo.data;
   }
 
   if(_itemsToLoad == 0) {
      _itemLoadComplete = true;
      _checkReady();
   }
}
 
public static function onSoundAssetLoadComplete(cbo:CallBackObject):void {
   _soundsToLoad--;
   Config.log('Assets', 'onSoundAssetLoadComplete', 'LoadComplete: ' + cbo.name + " -- Size: " + cbo.data.bytesTotal + " -- _soundsToLoad: " + _soundsToLoad);
   Game.soundMgr.addSound(cbo.name, cbo.data);
 
   if(_soundsToLoad == 0) {
      _soundLoadComplete = true;
      _checkReady();
   }
}
 
public static function init():void {
   ta = new TextureAtlas(Texture.fromBitmap(atlas), XML(atlasXML));
 
   TextField.registerBitmapFont(new BitmapFont(Texture.fromBitmap(calistoMT), XML(calistoMTXML)));
   TextField.registerBitmapFont(new BitmapFont(Texture.fromBitmap(wizztaB), XML(wizztaXML)));
 
   towerData = Utils.JSONDecode(towerDataJSON);
   mapData = new MapData(Utils.JSONDecode(mapSelectJSON));
}

Before my Madden-esque play-by-play (line-by-line) breakdown of this block of code, a quick note about the CallBackObject class. It can be found in com/zf/loaders/CallBackObject.as and it is simply a ‘struct’ sort of data structure. I just wanted a very clearly-defined class that would have a name, data, and options member so I could call them later. BOOM! Back in the game…

Wrapping Up

Whew! We’ve finished loading all the assets we’ll need to actually start the game. There is still some more loading after a user has selected a map to play, but it’s only map-specific data. Tile data and enemy wave stuff. All of our sounds, spritesheets, fonts, and a vast majority of the art assets and heavy lifting background-sized jpg/png files have been loaded here. It’s smooth sailing with hopefully a very limited amount of loading/processing in the future so the Player can jump straight into maps and start killing badguys!

This was probably the most boring way to kick off this tutorial series. You know you came for Tower and Enemies! That’s the real meat of a TD game. Soon…. soon… I’m glad we got this out of the way because it was tricky trying to learn Starling and figure out how to use LoaderMax to load assets and get it to play nice with Starling. But now that that’s done… we’ll get into the good stuff next time!

Again, feel free to check out the finished product of my AS3 Starling TD Demo.
You can also find all of the code used in srcview.
Or you can download the whole project zipped up.
Or check out the repo on Bitbucket

Share

Geolocation Gotcha

I’ve been working on an Air for Android app for the past month or so and I’ve been finding some significant little holes in the current version of the Air/Android SDK. I’ll start with the latest fist-shaking issue I’ve come across.

The Geolocation class, only supported on Mobile applications, is a small, straightforward class with a quirk or two up it’s sleeve.  The following code example snippet comes from Adobe’s livedocs for the class (slightly modified):

1
2
3
4
5
6
7
8
9
10
if (Geolocation.isSupported)
{
       geo = new Geolocation();
       geo.setRequestedUpdateInterval(100);
       geo.addEventListener(GeolocationEvent.UPDATE, geolocationUpdateHandler);
}
else
{
       trace( "No geolocation support." );
}

This is a pretty straightforward way to use the Geolocation class. Check to see if Geolocation is supported on the mobile device, if it is, create a new Geolocation instance, set the interval of how long you want it to check the GPS sensors and update your GPS coordinates (every 100ms in this example), and add an event listener/handler function listening for when those coordinates have been updated.

The problem here is: What happens when you have a mobile device that HAS gps capabilities, but the user has disabled the GPS for privacy/security reasons? Geolocation.isSupported returns TRUE because the phone does in fact have Geolocation capabilities. But your code will never reach the geolocationUpdateHandler function because the user has disabled GPS Geolocation. You will not receive an error, you will not hear a peep from your app, you will not pass Go and you most certainly will not collect anything near $200.

There is one more significant check we need to do that should have been added to Adobe’s example to make it complete.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
if (Geolocation.isSupported)
{
       geo = new Geolocation();
       if( geo.muted )
       {
              trace( "Sorry, your paranoid user has disabled GPS Geolocation" );
              // Handle disabled GPS Geolocation stuff here
       }
       else
       {
              geo.setRequestedUpdateInterval(100);
              geo.addEventListener(GeolocationEvent.UPDATE, geolocationUpdateHandler);
       }
}
else
{
       trace( "No geolocation support." );
}

Adobe added a very tricky and (in my opinion) mislabeled parameter, “muted” to the Geolocation instance (Not as a static class variable, like ‘isSupported’, but to the actual instance). I don’t know why they call it ‘muted’ but the variable is TRUE when the user has DISABLED access to GPS Geolocation, and FALSE when everything is cool and everyone is happy and you can actually use Geolocation how you want within the limits of satellite technology.

Tricksy little adobe hobbitses….

I had looked forever for some sort of Error event to fire… maybe a GeolocationEvent.ERROR or something that let you know you could not use the geolocation services, and this blog began as me pointing out how Adobe must’ve made a mistake. But no, no mistakes, just a really weird class that doesn’t seem to follow the conventional model of other Adobe classes.

I hope this helps.

Update – 18th Jan 2011 –
I did some further testing with this “muted” flag. As long as you have “Use wireless networks” or “Use GPS satellites” (verbiage from Droid X Settings -> Location & Security ) checked, muted will be FALSE and you can use the Geolocation class just fine. If both are unchecked, muted will be TRUE and you need to tell the user to turn stuff on. Makes sense.
That is all.

Share

Air, Android, and cookies

What: In using an Android app, I need to send an authentication request (username/password) to the server and the server needs to send me back a cookie.  We all know how you would use JavaScript to grab cookie/session data and then you could send it right into flash.

Problem: How or where would you find cookie being sent to you in your Air/Android app that uses no browser?

Solution:  Headers can be found in the HTTPStatusEvent object!  Hurray!  It took me way too long to find the answer to this issue.  Everyone on google is happy to tell you “Hey there’s a manageCookies setting on the URLRequest object!”  Great, how do you find the damn headers?  In fact, as you’ll see in the following code, you dont even need to mess with that manageCookies setting.  I set it to true and got headers.  I set it to false and god headers.  I didn’t include it at all and got headers.  Sweet.
Read More

Share

AS3 Why Your Bitmap Doesn’t Care About Your Input!

Over the past couple of years of doing AS3 coding, it has frustrated me to no end as to why I couldn’t create a new Bitmap object and add Mouse Event Listeners to it.  As I first got into optimizing AS3 display code, trying to shy away from MovieClips everywhere, I started using Sprites more.  As I started learning about the efficiency and pixel-level control of the Bitmap/BitmapData class, I started using those classes more and more.  And every once in a while, I would want to add Mouse Event Listeners to a Bitmap.  Every time during those every once in a while times… it never worked.

Read More

Share

3 Lessons Learned in Noob Collaborative Game Development

I’m currently working on a 2D RPG Game Engine ( think Zelda or FinalFantasyII/IV ) in AS3. At the start of this project, as typical projects with friends go, it seems like a completely easy task. “Hey you know AS3… can you write us a quick game engine for this idea we have?” Sure… I’ve finished a simple little game before, I can totally do that.

Take 1:

From previous (limited) game dev experience I know I’ll need to set up a heirarchy of classes to handle Player Characters, Enemies, Scenes, Buttons, etc. So, thinking things out logically (read: noobishly (?) ) the first thing the team will want to see is going to be a the big-picture, visual framework. I sit down and start hammering out a SceneManager to handle scene changes easily. I’ve created an IZFScene interface to standardize the methods of all of my Scene objects for the SceneManager to manipulate. I’ve got my placeholder backgrounds that I tossed into the Flash Library from photoshop ( or MSPaint ) and I’m off and running creating smooth fade-in/fade-out transitions between scenes. The engine process looks like:

Read More

Share

AS3 – AMFPHP High Score Database

This is a little tutorial covering using ActionScript 3, PHP and AMFPHP to create a MySQL-based High Score Database.  You should have some familiarity with each as this isn’t exactly a “Beginner’s How-To.” For a recent game project I’ve been working on, one of the requirements was a simple High Score Database. After finishing it, I thought I’d post about how I went about coding it. Let’s jump right in with the ActionScript first…

So from the game’s .as files, the idea was to display a DataGrid that shows all the scores submitted to the database. I also wanted to create a ScoresDB class that handles all of my database calls and parses the database results, all ready to be added to the DataGrid.

So that we’re all on the same page, Main.as will refer to the main class that handles adding the DataGrid to the stage, and handles other game functions. ScoresDB.as will refer to the ScoresDB class that handles the AMFPHP/PHP/MySQL calls. HighScore.php will refer to the AMFPHP Service that actually interacts with the MySQL database and returns result sets.

Read More

Share

AS3 – AMFPHP – Secure Flash/MySQL Database Calls

I decided to split the original blog post into two separate posts as “Secure” Flash/MySQL DB calls is fairly short, and it was scattered about in a post more on how to set up a High Score DB with AMFPHP.

So this will be a couple of very specific tips and things to set up when adding any sort of user-entered data from flash ( or PHP! ) to touch your database. You know the rule… never trust any data. Always make sure you strictly data type variables and typecast user-entered variables.

First up, as the user enters data into Flash, via an input TextField, use the .restrict setter to restrict characters entered to only characters that you need.  This is the first layer of protection against SQL injection attacks , and just follows the same sort of common sense “best practices” type of coding as datatyping variables.


nameInputTxt.restrict = "A-Z a-z 0-9";

This will restrict the characters allowed in this textField to only alpha-numeric, capitals and lower case. This excludes potential Injection-prone characters like the single apostrophe ” ‘ ” and semi-colon ” ; ” keys.

After that data gets entered, we’re going to send those variables thru AMFPHP into our PHP Class.  In the case of our High Scores Database example, we’re sending both the nameInputTxt data, as well as an integer based score value which gets handled by the following PHP code:


function addScore( $pName , $pScore )
{

$created = date( "Y-m-d H:i:s");
$cleanName = mysql_real_escape_string( $pName );
$cleanScore = intval( $pScore );

return mysql_query( "INSERT INTO $this-&amp;amp;amp;gt;table SET `name` = '{$cleanName}' , `score` = $cleanScore , `created` = '{$created}' ");

}

You’ll see the $cleanName and $cleanScore variables a couple of lines into the function. For String type user-entered data, always run it through PHP’s mysql_real_escape_string() function. If somehow a single apostrophe made it this far, PHP will automatically “escape” the apostrophe adding a back-slash before: \’ instead of a dangerous ‘

As far as $pScore goes, we’ll send it thru PHP’s intval() function which will truncate any decimal portions as well as attempt to return an integer value for any data it comes across. This means if something crazy happened and malicious String code made it this far, if intval() could not find the proper integer to represent the data, it will return 0. And submitting a zero, even though it might be wrong, is infinitely better than having DROP TABLE code injected into the call.

That’s it

For More info on securing the actual AMFPHP install and files, check out Lee Brimlow’s Flash Blog post, AMFPHP Security Basics

Share

FishEyeMenu Class Update… v1.1

Thanks to a comment posted by doggy, I’ve updated the FishEyeMenu class to listen for MouseDown events and keep track of selected items.  

New Functions:

  • public function get selected():*
  • public function get lastSelected():* 
  • public function set selected( clickedItemEvent:MouseEvent ):void  
New Event Type
  • FishEyeMenu.SELECTED_CHANGED – Triggered upon a change in selected menu item
Once you’ve pushed an item into the FishEyeMenu object, it keeps track of it’s own MouseDown events on the items in it’s array.  When a user clicks on a menu item, it sets the _lastSelected property to whatever Was selected, and sets the _currentSelected property to whatever menu item was clicked.  

You can now add an event listener for SELECTED_CHANGED which will dispatch upon MouseDown on a menu item.

Please note, the getters for selected and lastSelected are going to return the actual Object that you pushed to the menu.  So it will return a reference to the actual TextField or MovieClip or Sprite or whatever you’re using in the menu.  If you check the Example FLA, you’ll see this code as an example

// in the main function
fishEyeMenu.addEventListener( FishEyeMenu.SELECTED_CHANGED , changedHandler );
 
//later in the code:
/**
* Simple test of usage, fishEyeMenu.selected returns the object selected
* so it's just like calling the actual object that was clicked last and
* you can set whatever properties that object has.
* If this were a MovieClip, you could use fishEyeMenu.selected.gotoAndStop()
***/
private function changedHandler( e:* )
{
trace( "Selected Item Changed to : " + fishEyeMenu.selected.name );
trace( "Selected Item Changed to : " + fishEyeMenu.lastSelected.name );

fishEyeMenu.selected.x +=20;

}

In the simple example, calling fishEyeMenu.selected.x += 20; just moves the object you clicked over 20 pixels to the right (+20). But you could also use fishEyeMenu.selected.gotoAndStop( “Selected” ); if you had pushed several Movie Clips into FishEyeMenu… and they had a frame with framelabel “Selected”.  Post comments if you’d like… it only helps make things better.

 

Download FishEyeMenu v1.1 from GoogleCode

View Updated Documentation

Share

Preloader to Game Transition with Stop Propagation

This will be a quick post on some gotcha’s to keep an eye out for when loading your ActionScript 3 game swf from a preloader swf. Nothing revolutionary here, back in mid-December, I had posted a blog on ActionScript 3’s Event Handling and the stopPropagation method and just the other day working on the code for a new game project, I ran into a situation where I should’ve used the method, didn’t, and ran into some issues.

Note: If you’re here looking for code on how to write a flash preloader, Lee Brimlow’s video tutorial on Preloading in AS3 gives a beautiful example. When I was originally learning how to code a preloader, that tutorial, and a couple of other tutorials around the net that escape my memory at the moment, were absolutely perfect.

Read More

Share

AS3 Dictionary Class vs. Array vs. Object!

It’s some sort of crazy nerdtastic Data Structure Deathmatch!

The Adobe Actionscript 3 Dictionary Class Vs Object Vs Array!  Man it’s awesome to have a language to work in, just like a great RPG where everyone’s got their strengths and weaknesses and it all works together to make awesome flashlove all over the web.

“What is a Dictionary Class and Why Should I read this post?”

Here’s the results from my testing right up front and I feel like it’s a good rough estimate of where each excells.

Read More

Share

Stop Propagating! Pt. 1 AS3, Event Handling, and Lil Timmy

I’ve periodically run into issues playing around with EventListeners where I want to remove one EL, and add another in the same function, something similar to the following code below.  First, a quick note on what’s going on in the code so we’re all on the same page.  I have a movie clip “defensive structure,” say, a tower for a Tower Defense game.  It has been added to the stage and I attached an EL to this movie clip of the tower when I added it to the stage, listening for a MOUSE_DOWN event on said tower.

Since then, I’ve added a few other movie clips INTO the tower Movie Clip.  The first issue I ran into was that normally I’ll use “e.target” as the “hey what did I click” variable to trace out or find out just what exactly was clicked.  e.target was pointing at a child of my tower movieclip, which gave me a number of errors like, “this function does not belong to this movie clip,” because functions belong to classes, and I’m targeting just a simple sprite or movie clip.

However, e.currentTarget returns the containing DisplayObjectContainer of e.target.  Usually e.currentTarget would trace out to be [object Stage], but since I specifically added e.target as a child of my defense movie clip, e.currentTarget is returning the actual object I need, the Defense object.

So, let’s take a look:

Read More

Share

Fisheye Menu Using AS3 and TweenLite

A friend of mine that enjoys bitching until I help her with whatever popped into her head that moment messages me tonight.  “Hey for my site [that you’ll make for me for free because i’m a girl and your friend] I want a menu system like that one I showed you [which you’re supposed to magically remember out of all the sites you’ve seen on the whole internet in your life].” After she re-sent me the link, a beautiful site called ilovedust I took a look at the slick little fish-eye style menu and said, “Yeah that’s Animation, I do code.  Not pretty stuff like that.”

After 5 minutes of receiving complaining message (but mostly once my game was finished that I was playing at Kongregate, I actually thought about what was happening in that menu, and after about 20 minutes of coding, had a workable menu.  I thought I’d post the code here and make a little tutorial out of it.

I love TweenLite.  And TweenMax.  And pretty much everything at GreenSock.  Once you learn the class, it makes everything so easy.  For those that are here who are AS2 coders… a) Upgrade!  and b) They also have AS2 versions of all of their code.  There are a number of other Tween libraries to choose from. Some perform better than others.  But TweenLite was the first for me that ‘made sense’ in my budding AS3 days, which still continue.

This is a quick little example of making a 5 menu-item menu that does stuff when you mouse-over.  You could probably think of a million ways to optimize this code; putting the menu items in a loop to initialize and things like that.  I’m just going to lay it out, and you can make your own better.

Read More

Share