8bitrocket.com
9May/133

Coding By Google – AS3 to PHP with Simple MYSQL DB Query and Data Return Tutorial (Free Code)

By Jeff Fulton (@8bitrocket on twitter)

Coding By Google - AS3 to PHP with Simple MYSQL DB Query and Data Return Tutorial (Free Code)

Here at Producto Studios, I use Google to look up code all day long.  Yes, I have written books on Javascript, Flash, Actionscript, and HTML5, but I still find myself looking stuff up all the time. There just is too much to know when trying to fulfill the requirements of a project.

So, a few days ago a needed to Call a PHP script from AS3, hit MySql,  and return back some data.  Nothing fancy, but not something I have not done in a while (since my Zynga days). I used a couple different resources to cobble together the code, but not any one that had everything I needed.  so I thought in the sense a fairness in sharing,  I would present this basic version (that could probably be much more secure, etc) in case anyone needed a starter in how to accomplish this task.

The PHP

I am by no means a PHP superstar.  I grew up on Perl, moved to vbscript, then to .net, then hid from server side coding until I had to learn a little PHP a couple years back. I think it is an excellent language though and use it as often as I can.

Our example is going to accept in an email address, check to make sure it is a valid email (hopefully this will solve most SQL injection problems, but if there is a better way, please let me know).  It will then look up a user in the MySQL Database with the email address. If the user exists, it will pass back the first and last name of the user.  Since this is pure PHP, any script, even JQUERY could call it and use it.  For this example though, Flash AS3 is going to be the m method used.

The Server Side PHP Script

You will have to provide your own database IP address or DNS name, along with the user, pass and db name.

  1. <?php
  2. $email = $_REQUEST['email'];
  3. $returnString="retval=";
  4. if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
  5.    $mysqli= new mysqli("xxx.xxx.xxx.xxx", "user", "pass", "db");
  6.    $sql="SELECT *FROM `user` WHERE email = '".$email."'";
  7.    $res=mysqli_query($mysqli, $sql);
  8.    if ($res) {
  9.       while ($newArray=mysqli_fetch_array($res, MYSQLI_ASSOC)) {
  10.       $first = $newArray['first'];
  11.       $last = $newArray['last'];
  12.       $returnString=$returnString."&first=".$first."&last=".$last;
  13.    }
  14.    echo $returnString;
  15. }
  16.    mysqli_free_result($res);
  17.    mysqli_close($mysqli);
  18. }else{
  19.    echo $returnString;
  20. }
  21. ?>

The Client Side Actionscript

This is the AS3 version of the script used to call the PHP.

 

  1. package {
  2.   import flash.events.*;
  3.   import flash.net.URLLoader;
  4.   import flash.net.URLRequest;
  5.   import flash.net.URLVariables;
  6.   import flash.net.URLLoaderDataFormat;
  7.   import flash.net.URLRequestMethod;
  8.   import flash.display.MovieClip;
  9.   public class Main extends MovieClip{
  10.     private var EMAIL_REGEX: RegExp = /^[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}$/i;
  11.     private var formattedEmailAddress:String;
  12.     public function Main() {
  13.       trace("test as3 to php to mysql and back");
  14.       var email="Teff@Test.com";
  15.       formattedEmailAddress=email.toLowerCase();
  16.       if (formattedEmailAddress.match(EMAIL_REGEX)) {
  17.         trace("good email address format");
  18.         trace("checking database for email address...");
  19.         checkDbForEmail();
  20.       }else{
  21.         trace("bad email address format");
  22.       }
  23.     }
  24.     private function checkDbForEmail():void {
  25.       var url="http://addresstoyourserver/doesemailexist_tutorial.php";
  26.       var loader : URLLoader = new URLLoader;
  27.       var urlreq:URLRequest = new URLRequest(url);
  28.       var urlvars: URLVariables = new URLVariables;
  29.       loader.dataFormat = URLLoaderDataFormat.VARIABLES;
  30.       urlreq.method = URLRequestMethod.POST;
  31.       urlvars.email = formattedEmailAddress;
  32.       urlreq.data = urlvars;
  33.        loader.addEventListener(Event.COMPLETE, completed);
  34.       trace("calling loader");
  35.       loader.load(urlreq);
  36.     }
  37.     private function completed(event:Event): void{
  38.       trace("php event") ;
  39.       var loader2: URLLoader = URLLoader(event.target);
  40.       trace("first=", loader2.data.first);
  41.       var firstname:String=loader2.data.first;
  42.       var lastname:String=loader2.data.last;
  43.       if (firstname == null || firstname =="") {
  44.         trace("Sorry, that email does not exist in our database");
  45.       }else{
  46.        trace("user exists");
  47.       }
  48.     }
  49.   }
  50. }

That's all there is to it. I'm not christening this as the only or the best way to accoimplish this task, just one way that works and is moderately secure.  If you have a better way, please sent it over and I'll credit you and post your additions and comments.

Jeff Fulton is the Chief Technology Officer at Producto Studios, and co-author of The Essential Guide To Flash Games,as well as The HTML5 Canvas 1st and 2nd editions.
He can be reached at info[at]8bitrocket.com and at the @8bitrocket twitter address.

11Feb/110

Quick Guide To wmode And Flash Embedding

When embedding Flash .SWFs in HTML, there are several choices you can make for the wmode parameter.  I've always been a bit confused about which was which, so I set out to today to pull together as much information as possible on the subject.  Here is a quick run down of the choices, and what they mean for your Flash .swf.

wmode=window : Usually the default option.  This puts the Flash movie on top of all other content on the HTML page. This means Flash won't play well with other HTML elements, and it won't adhere to z-index ordering.  In some cases, this mode will net you the best performance for your Flash game or application. This appears to be the safest option for getting your Flash to work properly in the majority of web browsers.

wmode=opaque : This mode is supposed to let Flash play well within an HTML page and adhere to z-index  ordering.   This option will disable hardware GPU rendering and default to software rendering. This mode is not recommended for use with mobile platforms displaying video. This mode also sometimes interferes with capturing keyboard events in Flash.   At the same time, some game developers have found that opaque nets them smoother frame rates.

wmode=transparent : If the background of your Flash movie is transparent, the HTML page will show through. Just like opaque, this option is supposed to play well with other HTML elements and has it's uses in the arena.  However, this option takes a significant amount of processing power to render the transparency.   Like opaque, this option will automatically disable hardware GPU rendering and default to software rendering, is not  is not recommended for use with mobile platforms displaying video and sometimes interferes with capturing keyboard events in Flash.

wmode=gpu : Uses the GPU for hardware accelerated rendering.  This may sound like a good idea, but that is not always the case.  Although running on the GPU can be very fast, in some cases loading and reloading graphics data to the GPU can be costly and time consuming.  You need to test this with your apps and the target devices to make sure it is beneficial to performance.

wmode=direct : This mode bypasses  the browser when rednering. This mode is required for Flash "StageVideo" to work in Flash player 10.2.  This mode appears to not play well with some HTML elements (similar to wmode=window).

This is what I could find today. If you have any additions, subtractions, or updates to this, please tell use in the comments below.

7Feb/110

Game Programming Tool Kit: A Binary Search in AS3

Every game programmer needs a basic set of tools to use in building his/her games. In this series we will cover everything from basic computer science algorithms and simple design patterns to more complex artificial intelligence implementations. If it can be used some how, some way in game development, then it is ripe for discussion and implementation in this series.

A Binary Search In AS3

With a binary search we can find an element in a sorted array faster (conceivably) than looping through the array  comparing the value we are looking for with the value of each element in the array.  I say conceivably because there are certain circumstances where the loop can be faster. Those cases would be when the value you are search for happens to be near the beginning of your search.

The version we are going to look at is a classic "needle in the haystack" style problem.  We are going to create and array called the "hayStack". It will contain a sorted list of numbers from the smallest to the largest. We will create a second integer variable called the "needle". The needle will be a value that exists in the array. We will use a classic example of recursion to search the "hayStack" for the needle value.

The recursive function, called "arraySearch()" will accept in four parameters:

1. The needle value

2. The hayStack array

3. The first element to use in the search. In our example we will search the entire array, so this value is 0, but it can be any value as long as it is not greater than the last parameter in the search.

4. The last element to use in the search. In our example this will be the hayStack.length-1, but it can be any value as long as it is greater than the first parameter.

The arraySearch() function will first find the middle of the array and then check to make sure that the last value is greater than the first value passed in.

If the value in the hayStack at the middle index is greater than the needle value then the arraySearch() function will recursively call itself but pass in middle-1 as the new last value.

If the value in the hayStack at the middle index is less than the needle value then the arraySearch() function will recursively call itself, but pass the middle+1 as the new first value.

Finally the middle value will be passed back as the correct index of the needle value.

[cc lang="javascript" width="550"]

package
{
import flash.display.Sprite;
/**
* ...
* @author Jeff Fulton
*/
public class BinarySearch extends Sprite
{

public function BinarySearch()
{
var needle:int = 55;
var hayStack:Array = [1, 2, 3, 4, 5, 6, 10, 21, 32, 33, 34, 41, 47, 51, 52, 53, 54, 55, 66, 69, 71, 73, 76, 90];
var location:int = arraySearch(needle, hayStack, 0, hayStack.length - 1);

trace(location);
}

private function arraySearch(needle:int, hayStack:Array, first:int, last:int):int {

var middle:int = Math.floor((last + first)/2);
if (first > last) {
trace("first > last");
// check for error in data passed in
return -1;
} else if (hayStack[middle] > needle) {
return arraySearch(needle, hayStack, first, middle-1);
} else if (hayStack[middle] < needle) {
return arraySearch(needle, hayStack, middle+1, last);
} else {
return middle;
}

}

}
}

[/cc]

Executing this code will result in the value "17" being traced out as 55 is the 17th (0-relative) element in the hayStack array.

Now, try to set the first value to 10 and the last value to 9.
[cc lang="javascript" width="550"]
var location:int = arraySearch(needle, hayStack, 10, 9);
[/cc]

The result will be a "-1" returned because the first value is greater than the last value.

Next, change the needle to the value "0" (or any other value that is not in the hayStack() array. Change the arraySearch() call back to it original format:
[cc lang="javascript" width="550"]
var needle:int = 0;
var hayStack:Array = [1, 2, 3, 4, 5, 6, 10, 21, 32, 33, 34, 41, 47,51, 52, 53, 54, 55, 66, 69, 71, 73, 76, 90];
var location:int = arraySearch(needle, hayStack, 0, hayStack.length - 1);
[/cc]

If the value is not found, eventually the recursion will result in first > last and a -1 will result.

The recursive function can be replaced with an iterative version:

[cc lang="javascript" width="550"]

private function arraySearchIteration(needle:int, hayStack:Array, first:int, last:int):int {
var middleFound:Boolean = false;
var middle:int;
while (!middleFound) {
middle = Math.floor((last + first) / 2);

if (needle == hayStack[middle] || first > last) {
middleFound = true;
}else if (needle > hayStack[middle]) {
first++;
}else {
last = middle-1;
}

}

return(middle);

}
[/cc]

That's it for a simple binary search. This is relatively simple, yet powerful technique that can be applied to solve any number of game related problems from looking for the correct MovieClip to remove from the game screen to looking up words in a  word list. One problem with this technique though is that it requires a sorted list. In the future we will examine using a hash table to solve this problem.  While using a hash table is a much faster (in some cases) than a binary search, it is much more complex than the simple recursion/iteration that we have constructed here.

5Feb/110

Shorter, More Game Focused Tutorials Targeting More Technologies Are Coming

OK, now that our HTML5 Canvas book is in production, we are planning a return to game tutorials that have been so popular here.  However, in 2011, people want more than just AS3 tutorials.  Keeping look back at this space for shorter, more precise code snippets and tutorials on game algorithms and solutions to common problems.   We will present them in AS3, JavaScript/Canvas and/or Corona Lau for mobile.

19Aug/100

Tutorial: Turn Your Flash Game Into A Google Chrome Web App (beta)

While the Google Chrome App Store will not be available until October 2010, you can still package-up your apps and test them as Chrome Web Apps.   Google has some extensive documentation on the subject, but we are going to slice it down and show you the easiest path to success.

Step 1: Get The Right Browser...Windows Only!

Yep, the only Chrome build that supports the installation of Chrome Web Apps, right now, is Chrome for Windows.  You will have to subscribe to the Chrome "Dev Channel" and then download the browser.  This is really simple.  I didn't really "subscribe" at all, I just downloaded the dev channel version of Chrome to my Windows machine.

Step 2: Set-up The Browser To Load Apps

After you have downloaded Chrome, you need to create a short-cut in Windows so you can change the path to the Chrome.exe executable.  You need to do this so you can add the "--enable-apps" option to the execution path.  This option allows you to test the "loading extensions" functionality which is the basis of the Chrome Web App technology.

Here is an example of the link to put in the Target box of a short-cut in Windows XP:

[cc lang="javascript" width="550"]

"C:\Documents and Settings\username\Local Settings\Application Data\Google\Chrome\Application\chrome.exe" -enable-apps

[/cc]

Step 3: Prepare Your Flash Game

Now you need to get all the assets together in a folder/directory that you will provide as part of the Chrome App installable package.  This includes:

  1. .swf file
  2. An HTML file that holds your .swf
  3. a 24x24 px .png icon
  4. a 124x124.png icon
  5. Any other assets the .swf needs to load externally

For this example, I decided to use our game "Palindromes Plus". First I found the Mochi version of palindromesplus.swf and the converted the cartridge icon image we use into  24x24 and 124x124 icons.

I then created a palindromesplus.html page.  To make it simple, I made the background of the HTML page black, and centered the game on the screen.

Step 4: Create Your Manifest.json File

The manifest.json file is required for every Chrome Web App.  It describes  meta-data about the game you are creating, and where to find some essential assets.  Most of this file is  (like name, description, version) self-explanatory.   However, a couple items could use a little sunlight to make them clear:

  • "app:launch" section:  The "app:launch" section contained describes how this app will execute:
  • web_url: This is the complete URL to an app that exists on the web.  You can deploy an app a s a Chrome web app without embedding the code in your directory/.crx file (more on that later)
  • local_path : a relative path to the html file that will launch your app.  This is required for a "server-less" app that can run offline.  This is the style we are creating for our Flash game.
  • "permissions" section:  This section defines some security constraints around your app for specific Chrome APIs and storage.   This is where you would place urls that your app might call for web services and xml rpc requests. We don't really use this, so we left the default from the Google example ( here)
    • notifications : Allows the app to use the proposed HTML5 notifications
    • unlimited_storage : Allows for unlimited HTML5 Server-side storage.

There are other options you can read about all the manifest.json file options here: http://code.google.com/chrome/extensions/manifest.html

[cc lang="javascript" width="550"]
{
"name": "Palindromes Plus",
"description": "Unscramble The Palindromes",
"version": "1",
"app": {
"launch": {
"local_path": "palindromesplus.html"
}
},
"icons": {
"24": "24.png",
"128": "128.png"
},
"permissions": [
"unlimited_storage",
"notifications"
]
}

[/cc]

Step 5: Create Your Application Folder

After you have your manifest.json, .html file, .swf, and and .png file files,  you need to create a folder file to hold them all.  Create a folder and place all of the files inside of it.

Step 6: Launch The Chrome Developer Version

Find the shortcut you created to the Chrome Browser  with the "-enable-apps" command line option and launch it.

Step 7: Install The Extension (Chrome Web App)

  1. Find the Wrench icon in the upper right hand corner of Chrome and click it.
  2. Choose The Tools Menu
  3. Choose The Extension menu
  4. Click the (+) next to Developer to open Developer mode

You should see the following screen:

To load your extension, click the [load packed extension] button and find the folder you put the files into.  Once you find it and click [OK], the extension will load.

Now, to try the Chrome Web App, simply click on a New Tab, and you should see the icon you created:

Click the icon to play the game.

Step 8: Create a .crx File

A .CRX file will be needed to distribute your app in the Chrome App Store.  This is very easy to create.  Follow the directions to get back to the developer mode.   Then click the [pack extension] button.

This will bring-up the following screen:

For the "extension root directory", find the folder you created with the manifest.json, etc.

Click [OK]

A .crx and .pem license file will be created for your app.  Hold on to these for when the Google App Store is ready to accept new applications, or for when Google Chrome can load packed extensions (not available yet).

A .crx is simple a .zip file with the extension changed.  You should be able make these without using Chrome very easily.

That's It...But It's Still Beta

Google says all of this could change wholly or partly by the time the Google App Store launches, but we thought it would be cool to give some idea of what the process and work flow will look like once the Chrome Web App Store is ready for action.

Here is a zipped directory for the Palindromes Plus extension: palindromes.zip that you use to try this on your own.  It includes the .swf, a sample manifest.json file, and icons.

Here is the .crx  abd pem files we created for Palindromes Plus:  palindromes.crx and palindroms.pem (.zip)


6Aug/100

Space Gate Enemy Path Tutorial by Barnaby Byrne

Barnaby Byrne (Aka Badger Manufacture) has just released is latest retro inspired Flash Masterpiece, Space Gate, at Odo Games. I was some impressed with the games and the intricate enemy patterns within that I asked if he would be kind enough to explain to our readers exactly how it is implemented. Luckily he agreed.  We are very happy to now present to you his tutorial:

Here is what Barnaby has to say:

It's exciting that after such a break from game development in the engineering industry designing buildings, I am now really enjoying it again.  I've often been asked how I do the enemy paths for my shooters, and like Jeff I am a firm believer in sharing rather than hoarding knowledge.

This game of mine SpaceGate has been a long time coming.  I started coding Space Invaders clones way back in the 80s on the ZX Spectrum here in the UK.  The game is really just a natural progression of that game with a few elements of Blaster Mines, Galaga, Xenon II and bullet hell thrown into the mix.  I have studied every skill on this site and in the 8bitrocket book The Essential Guide to Flash Games and hopefully my game will demonstrate what is possible if you follow the tutorials with some useful insight into my shoot em up spawning technique too.  Where relevant I have provided links to tutorials on this site where I first learnt the blitting and memory optimisation techniques.

Supplied files in this tutorial

Download The Files Here

Main.as

The main class has functionality for loading the maps, moving the camera, and caching, updating and rendering all the graphics.

Ship.as

Provides an update function that moves the ships along targets[] using a targetlength[] to specify each targets length in frames.

Tile.as

Simple object with no update function, only stores each tiles xpos, ypos and ID.

Tilemap.txt

Exported from mappy, all the tiles positions and tile id (only 2 used) in the example level.

Spritemap.txt

Each enemy waves start position.

ship1.png

Enemy ship graphics

tiles.png

Tile strip

Overview

Enemies are drawn onto a sprite layer in an editor such as mappy.  When it comes time to export the map I tend to use csv maps instead of xml, but I appreciate the divide on this.  Multi-layer maps are still possible with csv although attaching meta data to objects is not so well catered for.  The resulting csv file can vary from editor to editor, and if you are using maps with a different format just rewrite the loadLevel() function.

The tilemaps, xml, and bitmapdata tutorial on this site for loading maps is also a good resourcet if you are unsure about this process.  If you want to match the format in this tutorial you may want to get an exporter script for Mappy or even learn lua, it's scripting language.  At the very least I'd suggest reading around the subject of csv vs xml.  Each enemy in the csv file represents an wave of enemies in the game, so when designing the level I only had to place about 15 or 20 enemy ships per level, each one spawns 8,10 or 12 enemy ships by the engine.  You are encouraged to open the supplied text files (Tilemap.txt and Spritemap.txt) if only to appreciate how little data is actually in the sprite map but also to appreciate how the map is loaded and functions with the rest of the code.

Preparing the main class

First of all the main canvas (backbufferdata) is initialised and added as a child to the stage.

[cc lang="javascript" width="550"]
//----------------------------------------------------------------------------------------
// Screen
//----------------------------------------------------------------------------------------
private var screenWidth:int=320;
private var screenHeight:int=300;
private var backBuffer:Bitmap;
private var backBufferData:BitmapData=new BitmapData(320,300, false , 0x111563);
private var blitRect:Rectangle=new Rectangle(0,0,0,0);
private var point:Point = new Point(0, 0);
private var backgroundRect:Rectangle;
private var backgroundBD:BitmapData = new BitmapData(320,300, false , 0x111563);
private var backgroundPoint:Point = new Point(0, 0);
private var gameTimer:Timer;
...
backBuffer = new Bitmap(backBufferData);
backBuffer.y=0;
addChild(backBuffer);
[/cc]

Before loading the map there are a few steps to take in order to embed the png graphics and store them frame by frame in arrays.

[cc lang="javascript" width="550"]
cacheBlitObject(ship1BmpData,ship1BmpDataArray,24);
cacheTileStrip(stripBmpData,stripBmpDataArray,16);
cacheTrigVelocities();
[/cc]

cacheBlitObject uses a single frame of bitmap data to rotate itself 90 times and populate it's second parameter a bitmap data array.  That is more than sufficient to achieve smooth snake like movement.

generateRotation provides this functionality by using a standard matrix rotate also explained elsewhere in more detail on this site.

cacheTileStrip uses a whole strip to populate it's second parameter, also a bitmap data array.

cacheTrigValues does all the cos and sin calculations in advance and stores them so we don't ever have to do real time trig for the snake movement.

[cc lang="javascript" width="550"]
public function cacheBlitObject(bitmapDataToCache:BitmapData,arrayToCacheTo:Array,widthOfEnemy:int):void
{
// caches 90 frames at 4 degrees each

var sourceX:int=0;
var sourceY:int=0;
var sourceRect:Rectangle=new Rectangle(sourceX,sourceY,widthOfEnemy,widthOfEnemy);
var destPoint:Point=new Point(0,0);

for(i=0; i<91; i++)
{
arrayToCacheTo[i] = new BitmapData(widthOfEnemy, widthOfEnemy, true, 0x00000000);
sourceX=0;
sourceY=0;
sourceRect=new Rectangle(sourceX,sourceY,widthOfEnemy,widthOfEnemy);
arrayToCacheTo[i].copyPixels(bitmapDataToCache,sourceRect,destPoint, null, null, true);
arrayToCacheTo[i]=generateRotation(arrayToCacheTo[i],i*4);
}
}

//----------------------------------------------------------------------------------------
// generateRotation
//----------------------------------------------------------------------------------------

private function generateRotation(DATA:BitmapData,ROTATETO:int):BitmapData
{
var degrees:int=ROTATETO;
var angle_in_radians:Number = Math.PI * 2 * (ROTATETO / 360);
var rotationMatrix:Matrix = new Matrix();
var width:int=DATA.width/2;
rotationMatrix.translate(-width,-width);
rotationMatrix.rotate(angle_in_radians);
rotationMatrix.translate(width,width);
var matrixImage:BitmapData = new BitmapData(width*2,width*2, true, 0x00000000);
matrixImage.draw(DATA, rotationMatrix);
return matrixImage;
}

//----------------------------------------------------------------------------------------
// cacheTileStrip
//
// Paramaters:
// bitmapDataToCache - the bitmap data to cache
// arrayToCacheTo - the array to store the bitmap data cache
//----------------------------------------------------------------------------------------

public function cacheTileStrip(bitmapDataToCache:BitmapData,arrayToCacheTo:Array,widthOfTile:int):void
{
var sourceX:int=0;
var sourceY:int=0;
var sourceRect:Rectangle=new Rectangle(sourceX,sourceY,widthOfTile,widthOfTile);
var destPoint:Point=new Point(0,0);

for(i=0; i<(bitmapDataToCache.width/widthOfTile); i++)
{
arrayToCacheTo[i] = new BitmapData(widthOfTile, widthOfTile, true, 0x00000000);
sourceX=i*widthOfTile;
sourceY=0;
sourceRect=new Rectangle(sourceX,sourceY,widthOfTile,widthOfTile);
arrayToCacheTo[i].copyPixels(bitmapDataToCache,sourceRect,destPoint, null, null, true);
}
}

//----------------------------------------------------------------------------------------
// cacheTrigVelocities
//
// caches velocities from 0 to 360 degrees
// to avoid any real time cos or sin calculations
//----------------------------------------------------------------------------------------

public function cacheTrigVelocities():void
{
for (var ctr:int=0;ctr<361;ctr++)
{
rotnCacheX.push(Math.cos(2.0*Math.PI*(ctr-90)/360.0));
rotnCacheY.push(Math.sin(2.0*Math.PI*(ctr-90)/360.0));
}
}

[/cc]

A timer system is employed to establish recurrence of the main loop.  The tile strip is cached frame by frame, whereas the single frame ship.png is cached then rotated 90 times (once for each 4 degrees).  You can find out more about caching bitmapdata and it's benefits elsewhere on this site.  It has become second nature for me now that I have studied and practised Jeff's tutorials to such an extent.

Next, I created 2 object pools.  The idea is to avoid using the 'new' operator in real time, and also to restrict the number ever created.  Since I know I'll never have more than 400 tiles and never have more than 100 enemies I can confidently restrict these object pools in advance.

[cc lang="javascript" width="550"]
prepareSpritePool();
prepareTilePool();

//----------------------------------------------------------------------------------------
// prepareSpritePool
//
// set up some blit objects
// the game will cycle through this capped pool
//----------------------------------------------------------------------------------------

public function prepareSpritePool():void
{
for(i=0;i<maxNoSpritesOnScreen;i++)
{
spritePool[i]=new Ship(rotnCacheX,rotnCacheY);
}
}

//----------------------------------------------------------------------------------------
// prepareTilePool
//
// set up some tile objects
// the game will cycle through this capped pool
//----------------------------------------------------------------------------------------

public function prepareTilePool():void
{
for(i=0;i<maxNoTilesOnScreen;i++)
{
tilePool[i]=new Tile();
}
}

[/cc]

Now that we have cached all our graphics and trigonometric calculations, it's time to load the map.

We load the map layer by layer, tiles first:

[cc lang="javascript" width="550"]
//----------------------------------------------------------------------------------------
// loadLevel
//
// loads the map
//----------------------------------------------------------------------------------------

public function loadLevel():void
{
noMapTiles=-1;

for(i=0;i<10000;i++)
{
tilesMapPositionsX[i]=-100;
tilesMapPositionsY[i]=0;
tilesMapIDs[i]=0;
}

var cols:Array=new Array();
var rows:Array = new Array();

var widthInTiles:int;
var heightInTiles:int;

rows=TilesMapData.split("\n");

heightInTiles = rows.length;

for(r = 0; r < heightInTiles; r++)
{
cols = rows[r].split(",");
widthInTiles = cols.length;

for(c = 0; c < widthInTiles; c++)
{
if((uint(cols[c]))>0) // found a tile
{
noMapTiles++;
tilesMapPositionsX[noMapTiles]=(c*16);
tilesMapPositionsY[noMapTiles]=r*16;
tilesMapIDs[noMapTiles]=int(cols[c]);
}
}
}
currentTileMapItem=noMapTiles;
...

[/cc]

Then the sprites:

[cc lang="javascript" width="550"]
...
noMapSprites=-1;

for(i=0;i<10000;i++)
{
spritesMapPositionsX[i]=-100;
spritesMapPositionsY[i]=0;
spritesMapIDs[i]=0;
}

cols=new Array();
rows = new Array();

rows=SpriteMapData.split("\n");

heightInTiles = rows.length;

for(r = 0; r < heightInTiles; r++)
{
cols = rows[r].split(",");
widthInTiles = cols.length;

for(c = 0; c < widthInTiles; c++) { if((int(cols[c]))>0) // found a sprite
{
noMapSprites++;
spritesMapPositionsX[noMapSprites]=(c*16);
spritesMapPositionsY[noMapSprites]=r*16;
spritesMapIDs[noMapSprites]=int(cols[c]);
}
}
}
currentSpriteMapItem=noMapSprites;
}

[/cc]

Now that we have all the tiles and sprite locations and ids stored in arrays we can examine how the main loop accesses them.  The currentTileMapItem and currentSpriteMapItem are now equal to the total number of tiles and sprites in the map.  We can lookup the current tile and sprite at the top of this stack and see if it's y coordinate is less than 2 pixels higher than the top of the screen (the camerYpos).  If so we spawn a wave of enemies, keeping track of the wave number so that we can do different spawning code for each.

[cc lang="javascript" width="550"]
//----------------------------------------------------------------------------------------
//
//
// UPDATE
//
// calls updateBlitObjects and renderBlitObjects
//
//
//----------------------------------------------------------------------------------------

public function update(e:Event):void
{
updateBlitObjects();
renderBlitObjects();
}

//----------------------------------------------------------------------------------------
// updateBlitObjects
//
// calls each blit objects update function
//----------------------------------------------------------------------------------------

public function updateBlitObjects():void
{
cameraYpos--;
checkMap();
for each(var ship:Ship in spritesOnScreen)
{
ship.update();
ship.update();
}
checkForSplice();
}

//----------------------------------------------------------------------------------------
// checkMap
//
// checks to see if the cameray is approaching a new tile or enemy
//----------------------------------------------------------------------------------------

public function checkMap():void
{
while(tilesMapPositionsY[currentTileMapItem]>=cameraYpos-16 && currentTileMapItem>1)
{
tilePool[currentTilePoolItem].xpos=tilesMapPositionsX[currentTileMapItem];
tilePool[currentTilePoolItem].ypos=tilesMapPositionsY[currentTileMapItem];
tilePool[currentTilePoolItem].id=tilesMapIDs[currentTileMapItem];

tilesOnScreen.push(tilePool[currentTilePoolItem]);

currentTilePoolItem++;
if(currentTilePoolItem>maxNoTilesOnScreen-1)currentTilePoolItem=0;
if(currentTileMapItem>1)currentTileMapItem--;
}

while(spritesMapPositionsY[currentSpriteMapItem]>=cameraYpos-16 && currentSpriteMapItem>1)
{
spritePool[currentSpritePoolItem].xpos=spritesMapPositionsX[currentSpriteMapItem];
spritePool[currentSpritePoolItem].ypos=spritesMapPositionsY[currentSpriteMapItem];
spritePool[currentSpritePoolItem].frameIndex=0;

currentWaveNumber++;

for(i=0;i<10;i++)
{
if(currentWaveNumber==1)
{
var spacing:int=35;
spritePool[currentSpritePoolItem].targets=[3,15,10,15,10,3];
spritePool[currentSpritePoolItem].targetlengths=[50+(i*spacing),150,150,50,50,500];
spritePool[currentSpritePoolItem].xpos=160-12;
spritePool[currentSpritePoolItem].ypos=(i*spacing)*-1;
}
else if(currentWaveNumber==2)
{
if(i<5)
{
spacing=25;
spritePool[currentSpritePoolItem].targets=[1,14,11,14,11,1];
spritePool[currentSpritePoolItem].targetlengths=[50+(i*spacing),150,150,50,50,500];
spritePool[currentSpritePoolItem].xpos=340+(i*spacing);
spritePool[currentSpritePoolItem].ypos=150;
}
else
{
spacing=25;
spritePool[currentSpritePoolItem].targets=[2,10,15,10,15,2];
spritePool[currentSpritePoolItem].targetlengths=[50+(i*spacing),150,150,50,50,500];
spritePool[currentSpritePoolItem].xpos=-40-(i*spacing);
spritePool[currentSpritePoolItem].ypos=50;
}
}
if(currentWaveNumber==3)
{
spacing=35;
spritePool[currentSpritePoolItem].targets=[3,11,13,11,13,3];
spritePool[currentSpritePoolItem].targetlengths=[50+(i*spacing),150,150,50,50,500];
spritePool[currentSpritePoolItem].xpos=160-12;
spritePool[currentSpritePoolItem].ypos=(i*spacing)*-1;
}

spritePool[currentSpritePoolItem].currentstep=0;
spritePool[currentSpritePoolItem].distancethisstep=0;

spritesOnScreen.push(spritePool[currentSpritePoolItem]);

currentSpritePoolItem++;
if(currentSpritePoolItem>maxNoSpritesOnScreen-1)currentSpritePoolItem=0;
}
if(currentSpriteMapItem>1)currentSpriteMapItem--;
}
}

//----------------------------------------------------------------------------------------
// checkForSplice
//
// see if there are any enemies to remove from the on screen array
//----------------------------------------------------------------------------------------

public function checkForSplice():void
{
i=-1;
for each(var ship:Ship in spritesOnScreen)
{
i++;
if(ship.currentstep>0 && (ship.ypos>300 || ship.ypos<-30 || ship.xpos<0 || ship.xpos>320))
{
spritesOnScreen.splice(i,1);
}
}
i=-1;
for each(var tile:Tile in tilesOnScreen)
{
i++;
if(tile.ypos>cameraYpos+300)
{
tilesOnScreen.splice(i,1);
}
}

}

[/cc]

Let's isolate one of these spawns as an example and see how the map position is translated into a screen position.

Check map is called once every frame tick, although if you wanted to optimise you could change this so it's called only once every two frame ticks.  I actually use duality in my main loops although in this example I have not.  In SpaceGate, I use a task number so if task==1 do collisions if task==2 do checkMap.  Duality is another topic though but who knows I may get the chance to blog on that sometime too.

checkMap is called by our update function and if it finds a ship in the map close enough to the camera it spawns a new wave.  The wave is assigned targets and targetlengths.  Each of the batch is given a fresh counter (distancethisstep) and has it's currentstep defaulted to 0.

[cc lang="javascript" width="550"]
public function checkMap():void
{
while(spritesMapPositionsY[currentSpriteMapItem]>=cameraYpos-16 && currentSpriteMapItem>1)
{
spritePool[currentSpritePoolItem].xpos=spritesMapPositionsX[currentSpriteMapItem];
spritePool[currentSpritePoolItem].ypos=spritesMapPositionsY[currentSpriteMapItem];
spritePool[currentSpritePoolItem].frameIndex=0;

currentWaveNumber++;

for(i=0;i<10;i++)
{
if(currentWaveNumber==1)
{
var spacing:int=35;
spritePool[currentSpritePoolItem].targets=[3,15,10,15,10,3];
spritePool[currentSpritePoolItem].targetlengths=[50+(i*spacing),150,150,50,50,500];
spritePool[currentSpritePoolItem].xpos=160-12;
spritePool[currentSpritePoolItem].ypos=(i*spacing)*-1;
}
spritePool[currentSpritePoolItem].currentstep=0;
spritePool[currentSpritePoolItem].distancethisstep=0;

spritesOnScreen.push(spritePool[currentSpritePoolItem]);

currentSpritePoolItem++;
if(currentSpritePoolItem>maxNoSpritesOnScreen-1)currentSpritePoolItem=0;
}
..
if(currentSpriteMapItem>1)currentSpriteMapItem--;
[/cc]

spritePool

is the array of type Ship that we created in advance. The spawn method

above assigns however many of these is needed

to the spriteOnScreen array.

The Ship pool members are updated twice per frame tick, so let's have a look at the Ship class, the only other class of any

considerable length in the example.

[cc lang="javascript" width="550"]
public class Ship
{
// GLOBAL VARIABLES

public var xvelocity:Number;
public var yvelocity:Number;
public var xpos:Number;
public var ypos:Number;
public var width:int;
public var height:int;
public var health:int;
public var frameIndex:int;
public var currentstep:int=0;
public var distancethisstep:int=0;
public var targets:Array;
public var rotFrameIndex:int=0;
public var mvctr:int=0;
public var targetlengths:Array;
public var radians:Number;
public var radius:Number=0.05;
private var rotnCacheX:Vector.<Number>=new Vector.<Number>();
private var rotnCacheY:Vector.<Number>=new Vector.<Number>();
public var angle:int=0;
public var rotateframes:int=360;
public var qucircle:int=rotateframes/4;
public var hacircle:int=rotateframes/2;
public var tqcircle:int=qucircle+hacircle;
public var fucircle:int=rotateframes;
public var step:int=0;
public var rotinc:int=1;
public var temp:int=1;
public var targetArray:Vector.<Array>=new Vector.<Array>();
[/cc]

Most of the work is done in the update function.

As we know, in the example wave, our objects have these characteristics:

[cc lang="javascript" width="550"]
for(i=0;i<10;i++)
{
if(currentWaveNumber==1)
{
var spacing:int=35;
spritePool[currentSpritePoolItem].targets=[3,15,10,15,10,3];
spritePool[currentSpritePoolItem].targetlengths=[50+(i*spacing),150,150,50,50,500];
}
}
spritePool[currentSpritePoolItem].currentstep=0;
spritePool[currentSpritePoolItem].distancethisstep=0;
[/cc]

Now we update them like this:

[cc lang="javascript" width="550"]
public function update():void
{
mvctr+=1;
distancethisstep++;
if(distancethisstep>=targetlengths[currentstep])
{

step=currentstep++;
mvctr=distancethisstep=0;
}
temp=((distancethisstep/targetlengths[currentstep])*qucircle);
if(targets[currentstep]==1)
{
xpos-=1;
angle=270;
}
else if(targets[currentstep]==2)
{
xpos+=1;
angle=90;
}
else if(targets[currentstep]==3)
{
ypos+=1;
angle=180;
}
else if(targets[currentstep]==4)
{
ypos-=1;
angle=270;
}
else if(targets[currentstep]==10)// q1 clockwise
{
temp+=hacircle;
xpos+=rotnCacheY[temp]; ypos+=rotnCacheX[temp]*-1;
}
else if(targets[currentstep]==11) // q2 clockwise
{
temp+=tqcircle;
xpos+=rotnCacheY[temp]; ypos+=rotnCacheX[temp]*-1;
}
else if(targets[currentstep]==12) // q3 clockwise
{
temp+=0;
xpos+=rotnCacheY[temp]; ypos+=rotnCacheX[temp]*-1;
}
else if(targets[currentstep]==13) // q4 clockwise
{
temp+=qucircle;
xpos+=rotnCacheY[temp]; ypos+=rotnCacheX[temp]*-1;
}
else if(targets[currentstep]==14) // q1 anticlockwise
{
temp+=0;
xpos+=rotnCacheY[temp]; ypos+=rotnCacheX[temp];
}
else if(targets[currentstep]==15)// q2 anticlockwise
{
temp+=qucircle; //*qucircle for cached values;
xpos+=rotnCacheY[temp]; ypos+=rotnCacheX[temp];
}
else if(targets[currentstep]==16) // q3 anticlockwise
{
temp+=hacircle;
xpos+=rotnCacheY[temp]; ypos+=rotnCacheX[temp];
}
else if(targets[currentstep]==17)// q4 anticlockwise
{
temp+=tqcircle;
xpos+=rotnCacheY[temp]; ypos+=rotnCacheX[temp];
}

if ((targets[currentstep]>=10) && (targets[currentstep]<=13)) {angle=180+temp+qucircle;} if(angle>(fucircle-1)){angle-=(fucircle);}

if((targets[currentstep]>=14) && (targets[currentstep]<=17)) {angle=180+qucircle-temp;}

if(angle<0){angle+=(fucircle);} if(angle>(fucircle-1)){angle-=(fucircle-1);}

while(angle<0)angle+=360; while(angle>360)angle-=360;
}

[/cc]

The system makes use of the paradigm of quadrants, so that each 90° turn is actually a single target.

I used the codes 1-4 for straight lines, 10-13 for clockwise targets and 14-17 for anti-clockwise.

Each ship's current distance along it's current target is increased by 1 when

update is called. Ship's also keep track of which target they are on.

The distance that the ship has travelled along it's current target is

divided by the target length, and then multiplied by 90°, then used as

the objects current angle.

This angle is also used to move the ship using the rotation cache's we sent to the ship when instatiating:

[cc lang="javascript" width="550"]
public function Ship(ROTATIONSX:Vector.,ROTATIONSY:Vector.)
{
rotnCacheX=ROTATIONSX;
rotnCacheY=ROTATIONSY;
...
[/cc]

The system makes it fairly easy to create interesting looking enemy waves

and allows for rapid development. You can try changing the target[]

array and targetlengths[] for completely different patterns.
I hope that you find it useful and any questions will gladly be answered,

as long as you show me the awesome games you make with it.

BadgerManufactureInc

I want to thank Barnaby for taking the time to explain how this works. I have been planning to create a system like this and his advice and generous view on code sharing will certainly help me and others that want to create blit patterns such as these. Now, go play Space Gate. You will not be disappointed.

1May/100

Tutorial: Exploring the AS3 Bitmap Class Lesson 3 – Scale from the center with a Matrix.

Tutorial: Exploring the AS3 Bitmap Class Lesson 3 - Scale from the center with a Matrix.

In this third lesson we will explore a method of scaling the object from Lesson 1 and Lesson 2 using a Matrix operation. Like rotation, we could simple apply the scaleX and scaleY properties of the Bitmap object, but that would result in the scale operation starting from the top-left corner rather than the center. We could also place the Bitmap inside a Sprite object, and move it to -.5* width for x and -.5*height for y and the scale the Sprite. The focus of these lessons though is working with the raw Bitmap object to eliminate the over-head of using the Sprite class as the Bitmap container. So, we will scale using a Matrix operation. After creating a new Matrix for this scale operation we will add in the dynamic tile sheet animation and rotation Matrix back into the code to demonstrate all of them working in conjunction.

We will be using the dynamic tile sheet we created in Lesson 1, so if you have not familiarized yourself with that lesson and find yourself getting lost it would be a good idea to go back and take a look. You might also want to examine the Matrix rotation operations we looked at in Lesson 2 as we will not cover that subject in detail here.

Lesson 3 will comprise parts 7,8, and 9 of this series.

Part 7: Scaling a Bitmap with a Matrix

Scaling a Bitmap object with a Matrix is very similar to rotating it with a Matrix. To ensure that we are scaling around the center point of the object we will need to "translate" it to -.5*width for x and -.5*height for y, then scale it, then move it back to is original position on the screen.

We will be using two new class variables for part 7.

[cc lang="javascript" width="550"]
private var bitmapScaleMatrix:Matrix;
private var bitmapScale:Number = 1;
[/cc]

The bitmapScaleMatrix is a Matrix class instance that will be used to apply our Matrix scale operations to the BitmapToDisplay object. The bitmapScale variable will hold the current scale of the BitmapToDisplay. This will be updated in each frame tick. It is important to note that we need this variable because we cannot use the scaleX and scaleY attributes of the BitmapToDisplay. This single variable replaces them both in our example. If you want to have separate x and y scales then you simply need a second variable.

Inside our runGame() function we will scale the object on each frame tick. We will start with a scale of 1 and increase it by .1 until it reaches 2 and then drop it back down to .1 to start over again. Similar to the rotation in Lesson 2, we need to call the identity() function on the bitmapScaleMatrix before on each frame tick to reset it before we apply the new scale.

[cc lang="javascript" width="550"]bitmapScaleMatrix.identity(); //resets the matrix

bitmapScaleMatrix.translate(-16,-16);
bitmapScaleMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleMatrix.translate(startLocation.x+16, startLocation.y+16);
bitmapToDisplay.transform.matrix = bitmapScaleMatrix;
bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}

[/cc]

Let's step through this code:

1. We reset the Matrix with the identity() call.
2. We translate the so that the center of our 32x32 Bitmap is at the 0,0 origin. We do this with by translating to -16,-16
3. We call the scale() function of the Matrix and pass in the bitmapScale class variable for both the x scale and y scale.
4. We translate back to the original screen position for the Bitmap object plus the 16 by 16 pixels we used in the translation.
5. We apply the Matrix by setting the Bitmap.transform.matrix to = the bitmapScaleMatrix
6. We bump up of bitmapScale by .1 and check to make sure it is not larger than the maximum (2). If it is, we set it back to .1.

Here is the complete code for the "Part7" class. The new lines of code in part 7 are indicated and to remove the rotation and tile sheet animation we have commented out some lines. You don't need to add those in now, but we will be adding them in in parts 8 and 9.

[cc lang="javascript" width="550"]
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Transform

/**
* ...
* @author Jeff Fulton
*/

public class Part7 extends Sprite {
private var bitmapToDisplay:Bitmap;

private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
//private var animationDelay:int = 5;
//private var animationCount:int = 0;

//private var bitmapRotation:int = 0;
//private var bitmapRotationMatrix:Matrix;
private var startLocation:Point = new Point(84, 84);

//*** new in part 7
private var bitmapScaleMatrix:Matrix;
private var bitmapScale:Number = 1;

public function Part7():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
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);
bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
bitmapToDisplay.x = startLocation.x;
bitmapToDisplay.y = startLocation.y;
addChild(bitmapToDisplay);
createBitmapData();
//*** new in part 7
bitmapScaleMatrix= new Matrix();
//bitmapRotationMatrix= new Matrix();
bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;
bitmapToDisplay.smoothing = true; //makes it look better when rotates
addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}
//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay,_
new Rectangle(0, 0, 32, 32), new Point(32, 0));
//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {

bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
}
}

private function runGame(e:Event):void {
bitmapScaleMatrix.identity(); //resets the matrix
bitmapScaleMatrix.translate(-16,-16);
bitmapScaleMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleMatrix.translate(startLocation.x+16, startLocation.y+16);
bitmapToDisplay.transform.matrix = bitmapScaleMatrix;
bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}
//not needed in part 7

/*
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}
var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapRotationMatrix.identity(); //resets the matrix

bitmapRotationMatrix.translate(-16,-16);
bitmapRotationMatrix.rotate(angleInRadians);
bitmapRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapRotationMatrix;

trace(bitmapToDisplay.x + "," + bitmapToDisplay.y + "," + bitmapRotation );
*/
//end not needed in part 7
}
}
}

[/cc]

Notice the long line under the //copy ship to next tile comment. This line has been broken with an "_" and should be reattached if you are going to cut and paste the code.

Here is the working swf file:

As you can see, the ship stays in its position and the scale starts from the center of the object. It loops though scale sizes .1 to 2 in increments of .1.

Part 8: Adding Dynamic Tilesheet Animation back into the mix

For this section we will simply be adding the animation with the scrollRect of the bitmapToDisplay back into the code. It is currently commented our in the runGame() function from part 7.

[cc lang="javascript" width="550"]
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
}

[/cc]

For a refresher, here is the explanation from Lesson 1 part 3.

When then animationCount == animationDelay we will change the x value of the bitmapScrollRect. The value is either 0 (the start x of the first tile) or 32 (the start x of the second tile). It just jumps back and forth between these two values every 5 frames. This isn't the most sophisticated animation technique, but it works fine for this example.

Notice that if there is a change to the bitmapScrollRect, we have to re-apply it to the bitmapToDisplay.scrollRect. The scrollRect property of a display object cannot be changed by reference, it must be re-applied as needed.

All of the above code will be inside a simple runGame function that we will create. This function is called on each frame by a simple EnterFrame event.

Simpily uncomment those lines from the Part7.as class file and you will have the animation back in to the code. Here is the what the runGame function should now look like.
You can save this new changed version as Part8.as. Make sure to change the class name to Part8 and constructor name to Part8 in the code it you choose to do this.

[cc lang="javascript" width="550"]
private function runGame(e:Event):void {
bitmapScaleMatrix.identity(); //resets the matrix
bitmapScaleMatrix.translate(-16,-16);
bitmapScaleMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleMatrix.translate(startLocation.x+16, startLocation.y+16);
bitmapToDisplay.transform.matrix = bitmapScaleMatrix;
bitmapScale += .1;

if (bitmapScale > 2) {
bitmapScale = .1;
}

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}

//not needed in part 8
/*
//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapRotationMatrix.identity(); //resets the matrix

bitmapRotationMatrix.translate(-16,-16);
bitmapRotationMatrix.rotate(angleInRadians);
bitmapRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapRotationMatrix;

trace(bitmapToDisplay.x + "," + bitmapToDisplay.y + "," + bitmapRotation );
*/
//end not needed in part8
}

[/cc]

Here is the working swf file:

As you can see in the above example, we can simulate animation by changing the x value of the scrollRect property of a display object and at the same time we can scale the Bitmap object using the Matrix operations.

Part 9: Combining Scale and Rotation into a single Matrix

We covered the rotation portion in detail in the Lesson 2. To combine both rotation and scale into a single operation we will be creating a new single Matrix for both and will be removing the original two matrices we created: bitmapRotationMatrix and bitmapScaleMatrix. We will call the new Matrix the bitmapScaleRotationMatrix. In the variable definition section of the Part9.as file we will create this class level variable:

[cc lang="javascript" width="550"]
//*** new in part 9
private var bitmapScaleRotationMatrix:Matrix;
[/cc]

Since we only need a single Matrix for both we will have a single initial assignment in our init() function.

[cc lang="javascript" width="550"]
//*** new in part 9
bitmapScaleRotationMatrix= new Matrix();

[/cc]

Our runGame function will be completely new. It will combine the rotation and scale matrix operations with the existing dynamic tile sheet animation we uncommented in part 8.

[cc lang="javascript" width="550"]
private function runGame(e:Event):void {
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}

//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapScaleRotationMatrix.identity(); //resets the matrix

bitmapScaleRotationMatrix.translate(-16,-16);
bitmapScaleRotationMatrix.rotate(angleInRadians);
bitmapScaleRotationMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapScaleRotationMatrix;

trace(bitmapToDisplay.x + "," + bitmapToDisplay.y + "," + bitmapRotation );

bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}
}

[/cc]

Here are the highlights of this combined rotation and scale code:

1. First, ignore the animation section as it was covered in Lesson 1
2. Take a look at Lesson 2 for detailed information on the rotation Matrix operation..
3. We start by calling he identity() method of the bitmapScaleRotationMatrix to reset it on each frame tick.
4. We do the now familiar translate operation once for both scale and rotate.
5. We rotate.
6. We scale. (these can be done in any order)
7. We translate back to the original position.
8. We the apply bitmapScaleRotationMatrix to our bitmapToDisplay

Here is the complete code for part 9.

[cc lang="javascript" width="550"]
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Transform

/**
* ...
* @author Jeff Fulton
*/
public class Part9 extends Sprite {

private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
private var animationDelay:int = 5;
private var animationCount:int = 0;

private var bitmapRotation:int = 0;
private var startLocation:Point = new Point(84, 84);

//*** new in part 9
private var bitmapScaleRotationMatrix:Matrix;
private var bitmapScale:Number = 1;

public function Part9():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
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);

bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
bitmapToDisplay.x = startLocation.x;
bitmapToDisplay.y = startLocation.y;
addChild(bitmapToDisplay);

createBitmapData();

//*** new in part 9
bitmapScaleRotationMatrix= new Matrix();

bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;

bitmapToDisplay.smoothing = true; //makes it look better when rotates

addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}

//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, _
new Rectangle(0, 0, 32, 32), new Point(32, 0));

//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);

}
}

private function runGame(e:Event):void {

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}

//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapScaleRotationMatrix.identity(); //resets the matrix

bitmapScaleRotationMatrix.translate(-16,-16);
bitmapScaleRotationMatrix.rotate(angleInRadians);
bitmapScaleRotationMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleRotationMatrix.translate(startLocation.x+16, _
startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapScaleRotationMatrix;

trace(bitmapToDisplay.x + "," + bitmapToDisplay.y + "," + bitmapRotation );

bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}
}
}
}

[/cc]

Notice the long lines like the one under the //copy ship to next tile comment. This line has been broken with an "_" and should be reattached if you are going to cut and paste the code.

Here is the working swf file:

In Lesson 4 we will start to create a BlitBitmap class that we can use standalone, or with in the Essential Flash Games Book framework.

14Apr/100

Tutorial: Exploring the AS3 Bitmap Class Lesson 2 – Rotation with and without a Matrix

Tutorial: Exploring the AS3 Bitmap Class Lesson 2 - Rotation and with and without a Matrix.

In this second lesson we will explore a couple different methods for rotating the Bitmap object from Lesson 1. We will first try simply using Bitmap.rotation property. Next we will do the same operation using a Matrix and finally we will add the dynamic tile-sheet animation to the Matrix rotation. Lesson 2 will be comprised on 3 separate parts (Parts 4,5,and 6 in this series).

We will be using the dynamic tile sheet we created in Lesson 1, so is you have not familiarized yourself with that lesson and find yourself getting lost it would be a good idea to go back and take a look.

Part 4: Rotating a Bitmap object directly

The Bitmap class (just like all display objects) has it's own rotation property. You would think that this would make rotating the contents of the object a trivial matter. In a sense, it is. You just simply set the rotation property to a new value

The problem you will see is that we cannot rotate the BitmapData object inside the Bitmap from any point other than the top-left corner. We will be adding a single line of code to the final code from Part 3 (inside the runGame) function:

[cc lang="javascript" width="550"]
//normal rotation will not work as it rotates on the top left corner
bitmapToDisplay.rotation += 10;

[/cc]

This code will simply rotate the Bitmap by 10 degrees on each frame tick. We will also be moving the bitmapToDisplay to a new position (84x,84y) so the object won't clip off the viewable screen during the rotation. We turn "smoothing" on to help the bitmap rotation not look jagged as the pixel locations are recalculated. This will be added to the init() method.

[cc lang="javascript" width="550"]bitmapToDisplay.smoothing = true;[/cc]

Here is the complete code for the "Part4" class. The new line of code is added to the end of the code in the Part3 runGame function.

[cc lang="javascript" width="550"]

package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;

/**
* ...
* @author Jeff Fulton
*/
public class Part4 extends Sprite {

private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
private var animationDelay:int = 5;
private var animationCount:int = 0;

public function Part4():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
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);

bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
bitmapToDisplay.x = 84;
bitmapToDisplay.y = 84;
addChild(bitmapToDisplay);

createBitmapData();
//makes it look better when rotates
bitmapToDisplay.smoothing = true;

bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;
addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}

//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, _
new Rectangle(0, 0, 32, 32), new Point(32, 0));

//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
}

}

private function runGame(e:Event):void {

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}

//normal rotation will not work as it rotates on the top left corner
bitmapToDisplay.rotation += 10;
}

}

}

[/cc]

Notice the long line under the //copy ship to next tile comment. This line has been broken with an "_" and should be reattached if you are going to cut and paste the code.

Here is the working swf file:

As you can see, if you want to simulate a space ship wildly spinning out of control this might be a good start but it isn't good for a rotation around the internal center point of the Bitmap. I am sure there are a few good applications where simulating the rotation from the top left point might work, but not for an Asteroids style ship rotation.

Part 5: Rotating the Bitmap on the Fly with a Matrix

In part 4 we rotated our Bitmap by updating it's standard rotation property. What we saw was a Bitmap that rotated around the top-left corner. Now, my normal method to get around this is to place the Bitmap inside a Sprite object and set its local coordinates to be -.5*width for x and -.5*height for y. When the Sprite is rotated, the Bitmap will rotate around it's center point. The one problem with this technique is that the actual top-left edge of the viewable Bitmaps now not at 0,0. This means you must offset some math calculations when using these objects (especially in tile-based games). Luckily, this is not the only way to accomplish this task. We can also use a combination of translation and rotation with Matrix object to simulate the same thing.

We will use the standard Matrix class, not the fl.motion.MatrixTransform class as the fl libraries are not part of the Flex SDK.

First let's look at the three new class level variables we will need for this operation:

[cc lang="javascript" width="550"]
private var bitmapRotation:int = 0;
private var bitmapRotationMatrix:Matrix;
private var startLocation:Point = new Point(84, 84);

[/cc]

The bitmapRotation variable will hold the current angle of rotation for the ship. It will range from 0-359 and will be used in the Matrix calculation to position the ship correctly on the screen. The bitmapRotationMatrix is a class level Matrix object instance that will be used to calculate and apply the center point rotation for the ship. We use a class level variable to avoid the overhead of creating a new Matrix object on each frame tick. The startLocation variable will be used as the upper left-hand corner location for the ship. We need to keep this in a separate variable from the Bitmap x and y coordinates as those will change based on the Matrix translation and rotation.

We will be instantiating this our Matrix in the init() function the Part5 class file.

[cc lang="javascript" width="550"]bitmapRotationMatrix= new Matrix();[/cc]

In our runGame function we will be commenting out the code that that does the animation with the scrollRect between the two dynamically created tile sheet sprite frames. We will add in the code that does the Matrix rotation of our ship. We will add the animation back in part 6, but for now we want to just demonstrate the rotation with out any other animation.

[cc lang="javascript" width="550"]
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapRotationMatrix.identity(); //resets the matrix

bitmapRotationMatrix.translate(-16,-16);
bitmapRotationMatrix.rotate(angleInRadians);
bitmapRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapRotationMatrix;

[/cc]

Let's step through this code:

1. First we update our bitmapRotation variable by 1 and check to see if it is greater than 359. If it is we set it back to 0. Now, this is not explicitly needed for this example as rotation will work without it. I use this because calculations with angles and especially look-up tables with vector calculations for angled movement are much easier to use with 0-359 as the angle of rotation.

2. We create a local Number variable called angleInRadians from the bitmapRotation value because the Matrix operation expects the angle to be represented in his manner.

3. We call the identity() method of the Matrix class. This resets the Matrix and allows us to feed new values into it on each frame tick. For some fun and weird effects, remove it and what what happens.

4. Now we get to the actual Matrix calculations. These function a little like a programming language all to itself.

First we call the translate() function and pass it -.5*with, -.5* height for the object. I just stuck 16 in here because it is a faster calculation. but you can calculate this on the fly with the width and height properties of the bitmapToDisplay object. This forces the rotation operation to occur at the center of the object rather than the upper left-hand corner.

Next we pass the newly calculated angleInRadians value into the rotate() method of the Matrix class. This will rotate the bitmapToDisplay object on the current x,y point, which is the center because of the translate() call above.

Finally, we must move the bitmapToDisplayObject back to its original position. We do this by using the startLocation Point instance's x and y coordinates (84,84) and adding the .5*width and .5*height to the respective startLocation Point values. Again, I applied the actual value (16) for speed of operation, but these very well could be calculated with the properties of the bitmapToDisplay object.

Here is the complete code for part 5:

[cc lang="javascript" width="550"]
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Transform

/**
* ...
* @author Jeff Fulton
*/
public class Part5 extends Sprite {

private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
private var animationDelay:int = 5;
private var animationCount:int = 0;

private var bitmapRotation:int = 0;
private var bitmapRotationMatrix:Matrix;
private var startLocation:Point = new Point(84, 84);

public function Part5():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
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);

bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
bitmapToDisplay.x = startLocation.x;
bitmapToDisplay.y = startLocation.y;
addChild(bitmapToDisplay);

createBitmapData();

bitmapRotationMatrix= new Matrix();
bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;

bitmapToDisplay.smoothing = true; //makes it look better when rotates

addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}

//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, _
new Rectangle(0, 0, 32, 32), new Point(32, 0));

//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
}

}

private function runGame(e:Event):void {

/*
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
*/

//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapRotationMatrix.identity(); //resets the matrix

bitmapRotationMatrix.translate(-16,-16);
bitmapRotationMatrix.rotate(angleInRadians);
bitmapRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapRotationMatrix;

}

}

}

[/cc]

Notice the long line under the //copy ship to next tile comment. This line has been broken with an "_" and should be reattached if you are going to cut and paste the code.

Here is the working swf file:

Part 6: Adding the dynamic tile sheet sprite animation back into the mix

For this section we will simply be adding the animation with the scrollRect of the bitmapToDisplay back into the code. It is currently commented out in the runGame() function from part 5:

[cc lang="javascript" width="550"]

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
}

[/cc]

For a refresher, here is the explanation from lesson 1 part 3.

When then animationCount == animationDelay we will change the x value of the bitmapScrollRect. The value is either 0 (the start x of the first tile) or 32 (the start x of the second tile). It just jumps back and forth between these two values every 5 frames. This isn't the most sophisticated animation technique, but it works fine for this example.

Notice that if there is a change to the bitmapScrollRect, we have to re-apply it to the bitmapToDisplay.scrollRect. The scrollRect property of a display object cannot be changed by reference, it must be re-applied as needed.

All of the above code will be inside a simple runGame function that we will create. This function is called on each frame by a simple EnterFrame event.

Simpily uncomment those lines from the Part5.as class file and you will have the animation back in to the code.

Here is the working swf file:

As you can see in the above example, we can simulate animation by changing the x value of the scrollRect property of a display object and at the same time we can rotate the Bitmap object using the Matrix operations.

In the next lesson we will cover scale transformations on our bitmapToDisplay object.

12Apr/100

Tutorial: Exploring the AS3 Bitmap Class Lesson 1 – Creating a basic dynamic tile sheet.

Tutorial: Exploring the AS3 Bitmap Class Lesson 1 - Creating a basic dynamic tile sheet.

We talk a lot about Blitting and BitmapData on this site, but we rarely take a step back and look at the container class for BitmapData, the Bitmap. In this series we will explore using the Bitmap class for many different applications and manipulate the BitmapData attribute to create animation and transformations. The end result of all the lessons will be a new class that we will add to the Essential Flash Games framework. With the AS3 Bitmap class we have a display object that we can manipulate to display BitmapData. This allows us to make use of the built-in features of a Flash Display object, but also the power of manipulating individual pixels of data for speedy rendering.

Lesson 1 will consist of parts 1-3 of the Bitmap class exploration. First we will take a look at dynamically drawing a simple shape on a Bitmap canvas. Next we will create a dynamic tile sheet using the BitmapData attribute of the Bitmap, and lastly we will create a simple animation using the dynamic tile sheet. Let's get started.

Part 1: Drawing pixels on a Bitmap canvas

The Bitmap class contains a BitmapData attribute. This provides the look of the Bitmap and allows for us to take control of the content of the Bitmap programmatically. The first thing we are going to do is draw a very simple "space ship" as a 32x32 image. The ship will look like this:

ship1

The "ship" we will create is a very simple upside down "T". This is a 32x32 png file with a transparent background. The ship does not touch the edges of the 32x32 size, but rather there is a 4 pixel buffer around the entire image. I created this one for reference only as it is a simple shape directly onto the bitmapData attribute inside our bitmap.

We will create a bitmap for our part1 code called: private var bitmapToDisplay:Bitmap;

We will also create a BitmapData object to hold the look of the ship: private var bitmapDataToDisplay:BitmapData;

We will draw the pixels of our ship by setting the color of the individual pixels of the bitmapDataToDisplay. Since we have some straight lines (2 pixels thick) that are symmetrical, we can create a very simple loop to draw our ship. We will use the setPixel32 function of the BitmapData:

[cc lang="javascript" width="550"]
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff)
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff)
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff)
bitmapDataToDisplay.setPixel32(ctr,27,0xff0066ff)
}

[/cc]

In this loop we create a vertical line from 4y to 27y at both pixel 15x and 16x (for a double thickness) and we create a horizontal line from 4x to 27x at pixels 26y and 27y. This mimics the png of the ship created above. Obviously this is probably the most simple space ship ever drawn, but it demonstrates how to manipulate pixels "on-the-fly" and will be the basis for much of the further discussion. Feel free to change up the look to meet your specific aesthetic desires (if it helps you make it through these tutorials, more power to ya!).

Here is the complete code for the "Part1" class.

[cc lang="javascript" width="550"]
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;

/**
* ...
* @author Jeff Fulton
*/
public class Part1 extends Sprite {
private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

public function Part1():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
bitmapDataToDisplay = new BitmapData(32, 32, true, 0x00000000);
bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
addChild(bitmapToDisplay);
createBitmapData();
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff)
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff)
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff)
bitmapDataToDisplay.setPixel32(ctr,27,0xff0066ff)
}
}
}

}

[/cc]

Here is the working swf file:

Part 2: Drawing a second frame on the same BitmapData to create a dynamic tile sheet

In part 1 we we created a single BitmapData instance called bitmapDataToDisplay and drew our simple space ship on it. In part 2 we will extend the size of this BitmapData from the original width of 32 pixels to a new width of 64 pixels. We will then draw the second frame of our simple space ship in what we will consider tile 2 of our tile sheet. Here is the code we will change in the init() function to make the width of the BitmapData 64 rather than 32:

bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);

Here is the ship image we will draw starting in pixel 32x and finishing in pixel 63x.

ship2

Again, the .png file is just for reference as we are going to be drawing into the BitmapData directly for this lesson. We can imbed the png in the IDE's library or at compile-time if we want to, but for this lesson we'll just use the dynamically drawn ship. The only difference between the ship1 and ship2 images is the small red "exhaust" at bottom of the "ship". This is the second frame of animation that would conceivably be used to simulate the ship thrusting through virtual space.

To draw this second frame into the BitmapData we will offset the original ship drawing by 32. The easy way to accomplish this is to copy the pixels from the first frame and drop them onto the second frame. We will use the BitmapData.copyPixels function to do this:

[cc lang="javascript" width="550"]
//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, new Rectangle(0, 0, 32, 32), new Point(33, 0));
[/cc]

We pass a reference to the source for the copy (itself), a Rectangle representing the area to copy and a Point representing the upper left-hand corner to start copying the pixels. This draws a copy of the exact original ship pixels to what we will consider "frame 2" of our dynamically created tile sheet.

Next we will draw the red exhaust:

[cc lang="javascript" width="550"]
//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(49, ctr, 0xffff0000);
}

[/cc]

Similar to how we created the

double blue lines for the original ship image, we create a 2 pixel width red line under the ship for the exhaust.

Here is the complete code for Part 2. It is very similar to Part 1 with some minor changes to accommodate the size of the BitmapData (extended to 64 pixel width) and the new drawing code.

[cc lang="javascript" width="550"]
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;

/**
* ...
* @author Jeff Fulton
*/

public class Part2 extends Sprite {

private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

public function Part2():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

bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);
bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
addChild(bitmapToDisplay);

createBitmapData();
}

private function createBitmapData():void {
//draw vertical line with setPixel32

for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}

//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, _
new Rectangle(0, 0, 32, 32), new Point(33, 0));

//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(49, ctr, 0xffff0000);
}
}
}

}

[/cc]

Notice the long line under the //copy ship to next tile comment. This line has been broken with an "_" and should be reattached if you are going to cut and paste the code.

Here is the working swf file:

As you can see, the entire BitmapData is displayed. While this is a good way to display an entire bitmap for an example, it doesn't help us animate the the ship. Let's take a look at how to do a classic "flip-book" animation of the ship next. We will do this by manipulating the Bitmap.scrollRect property.

Part 3: Animating the dynamic tile sheet with a scrollRect

The Bitmap.scrollRect property is used to define the viewable portion of the Bitmap object. It acts a little like a "mask" in that the rectangle will show through and everything else will be blocked out.

We will need to create a class level variable to hold the current Rectangle bounds that we want to display on our Bitmap. We will create a new variable to hold this called:

private var bitmapScrollRect:Rectangle;

In our init() function we will set the size of this to be 32x32:

bitmapScrollRect=new Rectangle(0, 0, 32, 32);

We will also apply it to the scrollRect property of the bitmapToDisplay.

bitmapToDisplay.scrollRect = bitmapScrollRect;

This is only good for the first frame as this will need to be re-applied each time we need to show a different part of the dynamic tile sheet. It does not work like most references where if we change the bitmapScrollRect's x value the bitmapToDisplay.scrollRect is also updated. We need to re-apply the assignment each time we change the x location of the bitmapScrollRect Rectangle.

We will update the bitmapScrollRect's x and y properties to change the location on the dynamic tile sheet we created. This will simulate animating the "thruster" at the bottom of our little space ship. We will create and EnterFrame event and use a counter for this animation. We will create two class level variables for the animation frame counting:

private var animationDelay:int = 5;

private var animationCount:int = 0;

The animationCount will be increased by one on each frame tick. When the count reaches the animationDelay the bitmapScrollRect will be updated. Here is the code that will accomplish this:

[cc lang="javascript" width="550"]
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}

[/cc]

When then animationCount == animationDelay we will change the x value of the bitmapScrollRect. The value is either 0 (the start x of the first tile) or 32 (the start x of the second tile). It just jumps back and forth between these two values every 5 frames. This isn't the most sophisticated animation technique, but it works fine for this example.

Notice that if there is a change to the bitmapScrollRect, we have to re-apply it to the bitmapToDisplay.scrollRect. The scrollRect property of a display object cannot be changed by reference, it must be re-applied as needed.

All of the above code will be inside a simple runGame function that we will create. This function is called on each frame by a simple EnterFrame event.

Here is the complete code for Part 3. It is very similar to Part 2 with addition of the new animation variables and the runGame function for controlling the animation.

[cc lang="javascript" width="550"]
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;

/**
* ...
* @author Jeff Fulton
*/
public class Part3 extends Sprite {

private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
private var animationDelay:int = 5;
private var animationCount:int = 0;

public function Part3():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
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);

bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
addChild(bitmapToDisplay);

createBitmapData();

bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;
addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}

//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, _
new Rectangle(0, 0, 32, 32), new Point(32, 0));

//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
}
}

private function runGame(e:Event):void {

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
}
}

}

[/cc]

Again, notice the long line under the //copy ship to next tile comment. This line has been broken with an "_" and should be reattached if you are going to cut and paste the code.

Here is the working swf file:

As you can see in the above example, we can simulate animation by changing the x value of the scrollRect property of a display object. This acts like a "mask" and leaves the rest of the dynamically created tile sheet hidden.

In the next lesson we will cover some rotation transformations of the Bitmap object using a Matrix.

3Mar/100

Tutorial: AS3: How to Blit an animation from a tile sheet embedded at compile time

Tutorial: AS3: How to Blit an animation from a tile sheet embedded at compile time

Today we are going to look at how to use a tile sheet embedded into an AS3 project at compile time.We will cover the embedding, instantiation, and actual usage of the tile sheet to do a simple blit canvas animation. I am currently working on a scrolling re-make of the classic Atari 2600 game, Sky Diver. I have started to create my own simple 8-bit styled tile sheet for the game. I only have the first three preliminary tiles created for the player sprite's free-fall animation. Here is the tile sheet so far:

skyKingGraphics.png

 The sheet is actually 10 tiles wide (320 pixels) by 2 tiles high (64 pixels). The tiles are 32x32 in size. We are only using three tiles so far, but there is room for many more if we need them. We will start to build a simple class that will demonstrate using this sheet for animating the character using a classic blit technique. This technique will use a background fill to wipe clean the entire screen between animation frames. Much of the theory behind this was covered in this basic blitting tutorial, so I will not repeat it here. What we will do is go through this new example line by line and give you a good understanding of this current implementation.

1. Let's start with the class import section of our class.

[cc lang="javascript" width="550"]
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;

/**
* ...
* By Jeff Fulton 2010
* ...
*/

[/cc]

 

In the above code we are importing the Bitmap class as a display object for our BitmapData blit canvas. The demo will be contained in a Sprite instance that will have the Bitmap added to its display list. We will be running an EnterFrame event that will use the generic Event class in its listener function. This EnterFrame Event will be used to simulate the frame by frame tile-based animation of our game character. The Point and Rectangle classes are needed for our blitting operations.

2. Now we will take a look at the variable definition section for our class. This will include the embed compiler directive for our tile sheet.

[cc lang="javascript" width="550"]
public class TileSheetEmbed extends Sprite{
[Embed(source = 'skyKingGraphics.png')]
private var TileSheet:Class;

private var backGroundBitmapData:BitmapData =
new BitmapData(100, 100, false, 0x000000);

private var canvasBitmapData:BitmapData =
new BitmapData(100, 100, false, 0xffffff);

private var canvasBitmap:Bitmap = new Bitmap(canvasBitmapData);
private var tileCounter:int = 0;
private var tileList:Array = [0,0,0 ,1,1,1, 2,2,2];;
private var tileSheet:Bitmap=new TileSheet();;
private var backBlitPoint:Point = new Point(0,0);
private var tileBlitPoint:Point = new Point(20,20);
private var blitRect32:Rectangle = new Rectangle(0, 0, 32, 32);
private var tileWidth:int = 32;
private var tileHeight:int = 32;
private var tilesPerRow:int = tileSheet.width / tileWidth;

[/cc]

 

(We had to break a couple lines for space reasons, so make sure you re-attach them. I have added extra blank lines around them for emphasis)

Notice that our class extends Sprite. It can extend MovieClip, but there is no need since we only have a single dynamically created frame for this example. We actually take care of a lot of business in this code section, so let's take a closer brief look at each of the variables.

First we embed the "skyKingGraphics.png" file as a class named TileSheet. Bitmap files are embedded into the swf as instances of the BitmapAsset class (a sub class of Bitmap) and not as BitmapData instances as in the Flash Library.

Next we create the backGroundBitmapData instance that will act as both the black background for our demo and as the "eraser" between blit operations. The canvasBitmapData will be the actual viewable blit canvas. It will be added to the display list inside the canvasBitmap instance. Remember, BitmapData instances cannot be attached directly to Sprite or MovieClip instances (as in AS2).

Our animation has three frames that will play in order repeatedly. We are copied them each into the array three times each (in a row) to create a rudimentary delay between tiles. This helps to slow down the animation and not let it run too fast. There are other more elegant methods to do the same procedure (such as creating a delay variable), but this gets the job done just fine for the demo. The tileList Array instance holds the tile numbers from the tile sheet that we want to play in order. Notice there that this is where we have three of each tile id number. This will allow us to easily change the tiles to display, and the order in which they display, etc. We would just need to change the tile id numbers in this array. The tileCounter represents the index on this array to display. Our current tile sheet has 20 tiles available. The top left tile is tile id 0 and the bottom right is tile 19. The tile numbers go from left to right and then down and back to the left again. So, the right most tile on the first row would be tile id 9 while the left most tile on row 2 would be tile 10 and so on. The only three tiles that have characters in them right now are tiles 0, 1, and 2.

The tileSheet variable is an instance of the TileSheet class that we embedded.

For the blitting operations we have set up a reusable set of Point instances at the class level. We do this so we don;t waste memory and processor time re-instantiating these two Point instances on each frame for the two blitting operations. The backBlitPoint (0,0) represents the top left x, y coordinates that out backGroundBitmapData will start copying to the canvasBitmapData. The tileBlitPoint (20,20) represents the top left corner of the where our 32x32 character will start copying to the canvasBitmapData.

The blitRect32 represents a 32x32 Rectangle on the tileSheet where we will find the pixels we want to copy to our canvas (starting at the tileBlitPoint). The x and y coordinates of this Rectangle will be changed on each frame we want to display a new tile from the tileSheet.. Note that the y coordinate will not change in this example because all three of our tiles are on the first row.

The tileWidth and tileHeight are both 32. These much match the actual tile width and height on our tile sheet. The tilesPerRow variable is used in our blitRect32 calculation to determine what row we will need to blit from our tile sheet. In this example we will always be on the first row, but our code can support any number of rows of tiles.

3. Next we will take a look at our constructor function:

[cc lang="javascript" width="550"]
public function TileSheetEmbed() {
addChild(canvasBitmap);
addEventListener(Event.ENTER_FRAME, runDemo, false, 0, true);
}

[/cc]

 

The constructor function is very simple. We add the canvasBitmap to the display list and then set up our animation loop with a ENTER_FRAME event.

4. The runDemo function

[cc lang="javascript" width="550"]
private function runDemo(e:Event):void {
canvasBitmapData.lock();

canvasBitmapData.copyPixels(backGroundBitmapData,
backGroundBitmapData.rect, backBlitPoint);

blitRect32.x=int(tileList[tileCounter]% tilesPerRow)*tileWidth;
blitRect32.y=int(tileList[tileCounter] / tilesPerRow)*tileHeight;

canvasBitmapData.copyPixels(tileSheet.bitmapData,
blitRect32, tileBlitPoint);

canvasBitmapData.unlock();
tileCounter++;
if (tileCounter > tileList.length-1) {
tileCounter = 0;
}
}
}
}

[/cc]

(We had to break a couple lines for space reasons, so make sure you re-attach them. I have added extra blank lines around them for emphasis)

The runDemo function starts by locking our canvasBitmapData instance. This keeps the canvasBitmap instance from drawing the canvasBitmapData to the output screen until the unlock method is called. We first blit the entire 100x100 black backGroundBitmapData to the canvasBitmapData using the backBlitPoint (0,0) as the location to start the copyPixels operation.

Next we calculate the top left-hand position on the tileSheet to start our copy of the game character. This is done using the current tile id number (0, 1 or 2) in the tileList array. The blitRect32 will represent the 32x32 rectangle that encompasses the picture on the tile sheet that we want to copy to the canvasBitmapData. It is modified each frame based on the tile id # in the tileList Array that we want to display. We use the tileCounter variable as the array index.

We now have the information to cut a 32x32 piece of our tileSheet and copy it to the canavsBitmapData. This is done with the copyPixels operation. We pass in the tileSheet as the source of the image data, as well as the blitRect32 that represents the rectangle of pixels to copy, and we also pass in the tileBlitPoint (which is 20,20) as the location to place the copied pixels on our canavsBitmapData. We unlock the canvasBitmapData and this allows the canvasBitmap instance to refresh itself with the new canvasBitmapData.

The final operation is to update the tileCounter so we will show so the next animation tile in the tileList array will display on the next frame tick.

That's all there is to it.

Here is a version running. It's not too pretty, but it gets the job done. He kind of looks like is a directing traffic on acid, but I'll work on improving it before I finish the game.

This site is protected by Comment SPAM Wiper.