This website uses cookies to improve user experience. By using our website you consent to all cookies in accordance with our Cookie Policy. X

Basics of tile-based engine part 2: path finding [advanced]

In the previous part I've talking a little about isometric view and how to achieve it a simplest way possible. This time around, just as promised, we will have a look at path finding in tile-based engine using one of the easiest methods available (but not the fastest). I am not really sure if the algorithm presented here has any specific name, but without a doubt it is the most direct way out of all possible solutions.
For all of you that are not interested in details I've prepared a class built around the whole algorithm, plus also a FLA file showing how to work with it: PathFind.zip
Running it will look like this:
(Pick a starting and an ending point but clicking any empty field. SPACE to create different map)

The idea behind the algorithm is quite simple and can be divided into two phases:
1. Target search. Starting for on of the chosen points we check, step by step, every tile in the neighborhood in search of the second specified point, while constantly saving current iteration step into the grid. These numbers are needed in the next phase.
Here is a code snippet from the example:

// ...
var vWrite:Vector.<Vector.<int>> = recreateTiles(tiles);
vWrite[startx][starty] = 1;
vWrite[endx][endy] = -1;
var vCheck:Vector.<Point>;
var vRead.<Point> = new Vector.<Point>();
			
var tile:Point;
var nStep:int = 2;
vRead.push( new Point(startx,starty) );
while(vRead.length != 0) {
				
	vCheck = vRead;
	vRead = new Vector.<Point>();
	for each(tile in vCheck) if(lookupTile(tile.x, tile.y, nStep, vRead,vWrite,vTile)) {
		return retracePath(tile.x,tile.y,nStep,vWrite);
	}
	nStep ++;
}
return null;
// ...
The lookupTile function looks for a next tile to be processed. If the end destination is anywhere in the 4 directions from the current tile, the loop ends and a found path is returned. In other cases vWrite is updated to hold new information, while any found tile is written into vRead to be processed in next iteration.
2. Path backtracking. Once we find our destination it is time to backtrack along the numbers saved in vWrite ,one by one to retrieve our path. Lets say the iteration stopped at 20 step - now we have start searching for the tile containing number 19, then 18, 17, and so on. Untill we reach number 1 which is the starting point (0 is used for tiles that were not checked).

Just a small note to finish this off: the algorithm can be speed up by executing it simultaneously on both starting and ending point, which means the path will be found when both of them meet each other half way through. This method was used in the attached example.
Next time we will have a look at the idea of "field of vision" in tile-based engines.

Basics of Stage3D part I: displaying multiple objects [advanced]

Two weeks ago I've wrote about support in Flash Professional for Flash Player 11 while mentioning about using the example of 3D rendering from the documentation ( http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display3D/Context3D.html#includeExamplesSummary). However using a pre-made example is one thing and understanding it is whole different matter - that is why this time around I will write about Stage3D (as Adobe like to call there 3D acceleration in Flash).
Just in case the whole code from that example (along with additional libraries needed to run it) is available here: Context3DExample.zip

In the first part of this article I will write a bit about creating and displaying multiple objects while still allowing for separate transformations on each of them. To make this simpler I suggest making a class for 3D cubes, using the already existing code (from the example in the documentation) responsible for creating geometry: IndexBuffer and VertexBuffer. Such class might look like this:

private var vbBuffer:VertexBuffer3D;
private var ibIndex:IndexBuffer3D;
private var m3D:Matrix3D;
private var nCount:int;

public function Box3D(context:Context3D) {
			
            var triangles:Vector.<uint> = Vector.<uint>([...]);
	ibIndex = context.createIndexBuffer(triangles.length);
	ibIndex.uploadFromVector(triangles, 0, triangles.length);
			
	const dataPerVertex:int = 6;
	var vertexData:Vector.<Number> = Vector.<Number>([...]);
	vbBuffer = context.createVertexBuffer(vertexData.length / dataPerVertex, dataPerVertex);
	vbBuffer.uploadFromVector(vertexData, 0, vertexData.length / dataPerVertex);
					
	m3D = new Matrix3D();
	nCount = 12;	
}
				
public function render(context:Context3D):void {
	context.setVertexBufferAt( 0, vbBuffer, 0, Context3DVertexBufferFormat.FLOAT_3 );
	context.setVertexBufferAt( 1, vbBuffer, 3, Context3DVertexBufferFormat.FLOAT_3 );
	context.drawTriangles(ibIndex, 0, nCount);
}
				
public function getMatrix3D():Matrix3D {return m3D;}
Nothing especially complicated here - just remember that m3D matrix will hold the transformation (position, scale) so it is best to build the cube around 0,0,0 coordinates and then later move it with appendTranslation function. Also, one more thing worth pointing out is that setVertexBufferAt is used to set which VertexBuffer will be used to draw triangles (drawTriangles function) and it is called twice, because first it points out the vertexes and next the colours.
So, creating a class for cubes is pretty simple, the problems start when we decide to display them. Assuming all those boxes are stored in vBox vector, the render function used in Context3Dexample needs to be changed to this:
private function render(event:Event):void {
	renderContext.clear(.3, .3, .3);
			
	for each(var box:Box3D in vBox) {
		finalTransform.identity();
		finalTransform.append(box.getMatrix3D());
		finalTransform.append(world);
		finalTransform.append(view);
		finalTransform.append(projection);
	renderContext.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, finalTransform, true);
				
		box.render(renderContext);
	}
			
	renderContext.present();
}
Here it gets a bit more interesting thanks to the two most important things: setProgramConstantsFromMatrix function and finalTransform matrix construction. With setProgramConstantsFromMatrix function we set the matrix (transformations) for the rendered object and it cannot be skipped (or at least it won't make any sense to do this, since you won't be able to see anything on the screen), while finalTransform will be used to create that matrix.
This stuff will probably be pretty confusing to people not familiar with 3D graphics, but the basic idea to remember is that every object (cubs in our case) is actually never modified during the runtime. In other words we create our cube, define its triangles and set some colours, but by it self the cube does not have specific position in the system - we use matrices for that. But the thing is matrices also don't do anything special by themselves, until we call the setProgramConstantsFromMatrix function to combine them. Note however this works somewhat like a filter - "Program" (external code used for rendering process) takes the geometry and passes it through the matrix, displaying results to the screen while not modifying any variables.
Now as for the finalTransform - we start each animation frame by resetting this matrix (identity function) to make sure it contains absolutely no transformations. This is very important for append operations, because existing transformations affect the results, so for example a translation by 10 units to the left followed by a 90 degrees turn will place an object in different spot than by starting with 90 degrees turn and then following it with a translation to the left by 10 units. Just like in the real world the direction we will take on "move to the left" command will be different depending on the side we are already facing. In any case it is best to experiment with this, while remembering that order of matrices addition is extremely important and that is why finalTransform is created by appending other matrices in this particular order: world, view (camera) and projection (defines how we see the world).

As usual the whole code is packed and ready to download from here: Tutorial3D.zip

3D engine for beginners III [advanced]

In previous parts of this guide dedicated to creating a very bare-bone 3D engine I've mentioned how to display the illusion of perspective and how to control camera movements. This time we will handle rotation. It will be the final chapter of this guide since any further immersion in the topic will require knowledge of more advanced issues, which are better to handle with a "proper" engine (like for example Alternativa3D or Away3D). Not to mention the newest Flash version with number 11 was extended with 3D acceleration, which basically means ActionScript can now be used to create a proper 3D game in likes of Crysis or Gears of War (especially since Adobe presented Unreal Engine 3 working in Flash).
But that is a different story, for now let's focus on that rotation.

1. However before we do anything create two new constants and one variable:

const SPEED:Number = 20;
const ROTATION:Number = Math.PI/16;
var a:Number = 0;
SPEED will be the speed of camera movement, while ROTATION will be the speed of rotation. The a variable will be used to store current rotation angle.

2. Do you remember which part of the code was responsible for transformation from 3D into 2D? We will modify it to include turning of the camera:

var nx:Number;
var nz:Number;
var rx:Number;
var rz:Number;
var bx2:Array = new Array(bx.length);
var by2:Array = new Array(by.length);
var i:Number = 0;
while(i<bz.length) {
	nx = cx-bx[i];
	nz = cz-bz[i];
	rx = cx+( nx*Math.cos(-a) )-( nz*Math.sin(-a) );
	rz = cz+( nx*Math.sin(-a) )+( nz*Math.cos(-a) );
	bx2[i] = vw+( ((cx-rx) / (cz-rz) )*vw);
	by2[i] = vh+( ((cy-by[i]) / (cz-rz) )*vh);
	++i;
}
Not much of a change, I've added additional variables for better clarity and the rotation formula (along the Y axis, which is exactly the same as in any other FPS game). You will probably find nothing shocking here if math is no stranger to you. Long story short for each point we calculate its translation (in case of x: nx = cx-bx[i];), which is rotated (( nx*Math.cos(-a) )-( nz*Math.sin(-a) );) and put back into position ( rx = cx+...). Newly created point is then transformed from 3D into 2D using the method we are already familiar with.

3. Now the only thing left to do is to modify the camera movement to include an option for turning around. We will accomplish this by changing how key presses are translated into actions by the switch(key) command. It is also a good opportunity to optimise the code - replace it with this:

switch(key&0x0000ff) {
case 0x00000f:
		cz += Math.cos(-a)*SPEED;
		cx += Math.sin(-a)*SPEED;
	break;
	case 0x0000f0:
		cz -= Math.cos(-a)*SPEED;
		cx -= Math.sin(-a)*SPEED;
		break;
}
switch(key&0x00ff00) {
	case 0x000f00:
	a += ROTATION;
		break;
	case 0x00f000:
		a -= ROTATION;
		break;
}
In the previous version switch was used to check every possible combination within the key variable, while now switch extracts only a part of key - first one gets up and down keys (0x000f0f&0x0000ff will result in 0x00000f which means the information about the left key, 0x000f00, is omitted), while the second one gets left and right.
Okay, so now we handle the key presses better, but what is actually happening here? Let's start from the end: left and right keys are used to turn (change the angle), while up and down are used for forward and backward movement. Of course since now we have to deal with rotation, the definition of "forward" constantly changes and that is why we need to use cos and sin functions which in this case calculate the translation vector based on the angle.

4. Time to test this out. Final result should look more or less like this:
(Movement with arrow keys)


And the whole class is available here: box3d_rot.zip

From AS2 to AS3: duplicateMovieClip() in ActionScript 3 [advanced]

Programmers that decide to switch to AS3 from AS2 will find sooner or later that the latest ActionScript iteration lacks duplicateMovieClip() function equivalent. It might seem odd, since it is hard to imagine Adobe having problems with implementation of such function. My guess is they are trying to kill wrong AS habits. In any case, sometimes you just have to duplicate a MovieClip and that is why I compiled a list of most common solutions:

1. Custom class.
I think around 90% of people looking for the duplicateMovieClip() alternative should find this solution sufficient. In brief: it is about creating a MovieClip's sub-class and then linking it graphics we want to duplicate. So for example, if we were to create an application with falling stars in the background we of course would start by creating a single MovieClip star and then copying it multiple times. To do this open the properties of that MovieClip star and in advanced settings check "Export for ActionScript".
Export for ActionScript
From now on "Star" class contains the star graphics and can be duplicated by writing:

var mc:MovieClip = new Star();
addChild(mc);
That is it.

2. Using the loadBytes() function within the Loader class.
Well sure, #1 method is pretty nice but what if we want to make copy of externally loaded file? In this case lets start by looking how to load the graphics from outside:

import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;

lLoad = new Loader();
lLoad.contentLoaderInfo.addEventListener(Event.COMPLETE,onLoaded);
lLoad.load(new URLRequest("your_file.swf"));

function onLoaded(e:Event):void {
	addChild(lLoad.content);
}
Okay, so at first glance Loader doesn’t seem to have that many functions we could use after loading an external SWF file - we could pretty much either unload it or load different file. The second options is out of the question (except if user has local caching enabled Flash will actually load the content from there rather than connecting to the server again) unless we use loadBytes(). The trick here is that loadBytes() can load any file-type supported by load() but does the reading from a ByteArray. The questions now is: where do we get that ByteArray of a loaded MovieClip from? The answer is actually very simple:
function onLoaded(e:Event):void {
	addChild(lLoad.content);
	lLoad.loadBytes(lLoad.content.loaderInfo.bytes);
}
The lLoad.content.loaderInfo.bytes property holds the loaded animation as ByteArray, so it is only a matter of putting it into the loadBytes() and then again catching the onLoaded event resulting in another MovieClip copy without actually establishing any outside connections.
However there is one problem, onLoaded event won't be called instantly... which means we will get our duplicated MovieClip only after few or more milliseconds (depends on FPS value).

3. Using the constructor property for objects loaded with Loader.
Alternative way to duplicate externally loaded SWF is by using the constructor property available for every object created in Flash. Lets just go ahead and see how it works:

function onLoaded(e:Event):void {
	var d:DisplayObject = new lLoad.content["constructor"]();
	addChild(d);
}
(Because constructor property is dynamic this is the only way to access it. Also, don't forget the keyword new!)
Unfortunately it is not a perfect solution. The code will work only when the loaded movie has a custom "Document Class" (it can be anything as long as "Document Class" is not empty), which basically means you have to be the author of the target file.

To save some work in future I've implemented all those solution into a single class - should have no problem with duplication of any MovieClips (and not only): foras_clone.zip
It is used in the following way:

import foras.utils.ForCloner;
import flash.display.DisplayObject;
if(stage==null) return;
var fas_Clone:ForCloner = new ForCloner();
var d:DisplayObject = fas_Clone.cloneMovie(my_mc,onClone);
if(d!=null) {
	d.x = stage.stageWidth*Math.random();
	d.y = stage.stageHeight*Math.random();
	addChild(d);
}

function onClone(d:DisplayObject):void {
	d.x = stage.stageWidth*Math.random();
	d.y = stage.stageHeight*Math.random();
	addChild(d);
}
Because ForCloner class uses the loadBytes() method (mentioned as second solution on the list) cloneMovie() requires a callback function to take over the duplicated MovieClip. Of course it is entirely possible it won't be need and cloneMovie will instantly return a copy. However in most cases it will result in the null value, which is where the callback function is used (onClone in the example above) - it will be given the copy of target MovieClip.
One last thing, if(stage==null) return; line stops the code for executing twice - if everything else fails whole application will be duplicated along with its source code (but at the end only target MovieClip will remain, everything else is deleted).

3D engine for beginners II [advanced]

In the previous part of this guide I've described how to create a basic 3D engine capable of creating a three-dimensional cube, however only in a static image which admittedly wasn't very impressive. That is why this time around we will expend it by adding camera movement.
Lets start by creating two keyboard events within the constructor of our box3d class:
stage.addEventListener(KeyboardEvent.KEY_DOWN,eventKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP,eventKeyUp);
(Don't forget to import flash.events.KeyboardEvent!)
Those listener were attached to the stage because any other object would require changes to the "focus" value which we don't need nor want to as stage is completely sufficient. Anyway, we need two functions to handle those events:
private function eventKeyDown(e:KeyboardEvent):void {

}
private function eventKeyUp(e:KeyboardEvent):void {

}
Before we continue I would suggest checking if Flash correctly handles key presses with trace(e.keyCode); put into one of the listeners.
In the next step we will work on the actual key presses capture and translation into camera movement, which even though can be done this way:
if(e.keyCode == Keyboard.LEFT) cx -= 20;
else if(e.keyCode == Keyboard.RIGHT) cx += 20;
We won't bother with it as it results in a jerky and limiting animation. Instead we will implement a bit more advanced solution based on bitwise operations - but before that, we need a new variable in our class: var key:Number = 0; which we will use in the following way:
private function eventKeyDown(e:KeyboardEvent):void {
	switch(e.keyCode) {
		case Keyboard.UP:
			key = key | 0x00000f;
			break;
		case Keyboard.DOWN:
			key = key | 0x0000f0;
			break;
		case Keyboard.LEFT:
			key = key | 0x000f00;
			break;
		case Keyboard.RIGHT:
			key = key | 0x00f000;
			break;
	}
}
(Don't forget to import flash.ui.Keyboard!)
OK, I admit, this might look a bit confusing at first (especially for people that are not familiar with bitwise operations or hexadecimal numbers), but in few moments everything will become clear. This code sets key variable to certain values depending on the pressed keys, so for example holding "up" and "left" keys will result with "0x000f0f" value being set to key. It is the outcome of bitwise operations done on hexadecimal numbers - by taking "0x000f00" and "0x00000f" and applying | (OR) to them we get "0x000f0f". That line symbol | represents a bitwise "or" and in some situations works a bit like addition - 100|1 will return 101 (more information can be found in ActionScript's documentation). But anyway, we don't really care about that, it is easier to think of key as a kind of light-board where 0 = light is off (a key is not pressed) and f = light is on (a key is pressed). 0x00ffff = all keys pressed, 0x00f00f0 = only up and right, 0x000000 = no keys are pressed etc.
However eventKeyDown only servers to switch the light on. For the full picture we need the opposite function:
private function eventKeyUp(e:KeyboardEvent):void {
	switch(e.keyCode) {
		case Keyboard.UP:
			key = key ^ 0x00000f;
			break;
		case Keyboard.DOWN:
			key = key ^ 0x0000f0;
			break;
		case Keyboard.LEFT:
			key = key ^ 0x000f00;
			break;
		case Keyboard.RIGHT:
			key = key ^ 0x00f000;
			break;
	}
}
In this case the bitwise ^ (XOR) acts a little like subtraction, where "0x000f0f ^ 0x00000f" will result in "0x000f00".
Well, I think the worst is behind us and now we only need to convert key value into the proper camera movement. To achieve this lets had back into the render function (which we created in the previous part of this guide) and before the graphics.clear(); line add the following code:
switch(key) {
	case 0x00000f: // ^
		cz += 20;
	break;
	case 0x0000f0: // v
		cz -= 20;
		break;
	case 0x000f00: // <
		cx -= 20;
		break;
	case 0x00f000: // >
		cx += 20;
		break;
}
Almost over. Compile the application and make sure everything is working as intended. If you had tried operating the camera straight from the eventKeyDown or eventKeyUp functions you will surely notice an improvement in animation (if not I suggest going back and comparing it to the current solution).
In any case, there is still one more little thing - moving diagonally. Using two keys at once will set key to a number that the switch(key) does not handle, so to finish things off we need to add four more clauses:
	case 0x000f0f:
		cz += 20;
		cx -= 20;
		break;
	case 0x00f00f:
		cz += 20;
		cx += 20;
		break;
	case 0x000ff0:
		cz -= 20;
		cx -= 20;
		break;
	case 0x00f0f0:
		cz -= 20;
		cx += 20;
		break;
That is it. Just in case you can download the whole class from here: box3d_2.zip
And the application itself is presented here: box3d.swf