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 Stage3D part IV: working with shaders [expert]

Nowadays shaders have become an inharent part of the 3D graphics, it is basically impossible to display a picture without creating a single shading program. Which is why, this time around we will take a quick look on how to work with shaders by adding a simple lighting to our virtual world. In Flash shaders are created using a series of instructions called AGAL, which is a very different language from ActionSctipt that requires a complete walkthrough on its own for full understanding - those however are easy to find on the Internet so for now we will only focus on the most basic stuff needed for lighting to work.
Returning to the code from previous article first thing we should do is add new Fragment Shader:
private const FRAGMENT_SHADER_LIGHT:String = "dp3 ft1, fc2, v1 n"+
"neg ft1, ft1 n"+
"max ft1, ft1, fc0 n"+
"mul ft2, fc4, ft1 n"+
"mul ft2, ft2, fc3 n"+
"add oc, ft2, fc1";
and Vertex Shader:
private const VERTEX_SHADER_LIGHT:String = "mov vt0, va0n"+
"m44 op, vt0, vc0n"+
"nrm vt1.xyz, va0.xyzn"+
"mov vt1.w, va0.wn"+	
"mov v1, vt1n" +
"mov v2, va1";
(The code has been borrowed from http://blog.norbz.net/2012/04/stage3d-agal-from-scratch-part-vii-let-there-be-light)
Above shaders will render triangles by taking each pixel and applying a light or dark color depending on their relative position to the camera. Before we do anything with that code we still need to compile it into a special "program" using the AGALMiniAssembler:
vertexAssembly.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER_LIGHT, false);
fragmentAssembly.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER_LIGHT, false);

programPairLight = renderContext.createProgram();
programPairLight.upload(vertexAssembly.agalcode, fragmentAssembly.agalcode);
(Don't forget to prepare the programPairLight variable)
So, right now we have two shaders: the standardprogramPair and light generating programPairLight. Only thing left to do is to decide which one we will be using by calling the setProgram function in Context3D. To better showcase the difference lets create another Box3D (lets call it "box3DLight") and then add the following code before present() but after the "box3D" rendering:
boxMat = box3DLight.getMatrix3D();
boxMat.identity();
boxMat.append(rotMat);
boxMat.appendTranslation(posVec.x + 1, posVec.y + 1, posVec.z+1);

finalTransform.identity();
finalTransform.append(boxMat);
finalTransform.append(world);
finalTransform.append(camera);
finalTransform.append(projection);
	
renderContext.setProgram(programPairLight);
renderContext.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, finalTransform, true);

var p:Vector3D = camera.position.clone();
p.normalize();
var m3d:Matrix3D = finalTransform.clone();
m3d.invert();
p = m3d.transformVector(p);
p.normalize();
p.negate();

renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([0,0,0,0]));
renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([0,0,0,0]));
renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, Vector.<Number>([p.x,p.y,p.z,1]));
renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 3, Vector.<Number>([1, 1, 1, 1]));
renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 4, Vector.<Number>([0.6, 0.6, 0.6, 1]) );

box3DLight.render(renderContext);
That is a lot to take in, but what really matters to use happens after setProgram ("boxMat" and "finalTransform" operations are previously discussed transformations from 3D to 2D). The term "program" is not used here without a reason as just like any other program on our computer shaders do a mathematical operations on variables and constants, only by using a very low-leveled language which is also quite limited as it only support up to 8 variables at a time and they only in program's memory. That is not the case for constants, although they are still limited to a maximum of 8, they can be supplied from the outside using either setProgramConstantsFromVector or setProgramConstantsFromMatrix functions - precisely what the code above is doing, it supplies the camera position ("p" variable) and light's color.
When done, we should see something like this:
This application only uses two shaders but there is nothing stopping us from create much, much and then just switch them around using the setProgram function just as we are doing with the lineStyle function when working with Graphics.
Whole code is available here: Tutorial3D_light.zip.

Basics of Stage3D part III: working with rotation [expert]

Continuing from where we left off this time around I want to write a bit about rotations. Anyone who started working with Flash's Stage3D without previous experience in 3D will sooner or later run into unforeseen results with appendRotation() function. To make things even less inviting for newcomers, looking for support on this topic on the Internet most routes lead to one answer "you will need complex numbers for that (quaternions to be exact). Hmm? Okay, quaternions might be really useful, but if you don't need 3D calculations to solve 2D rotations, it would be logical to assume that you also don't need 4D numbers to solve 3D problems, especially when being a newcomer to this topic.
Well, it doesn't matter right now so lets just move along, starting with the code from the previous post: Tutorial3D-2.zip, lets remove all those cubes and only leave one box, as that is all we need. By the way you could also create two additional variables: one for a Vector3D and one for Matrix3D - it will come in handy.
Generally rotation can be added in two ways: locally and globally, or in other words by either taking into the account the existing transformation or not. Which ever way we choose it is worth noting that existing transformation also includes any translation that might be applied to the matrix, which is why it is a good idea to make our lives easier and the rotation in separate Matrix3D object, only appending it to the existing object when needed. So lets start with the global transformation and creating a separate matrix (if you haven't done so already):
private var rotMat:Matrix3D = new Matrix3D();
Next, we will modify the handling of keyboard arrow keys by including the rotation for rotMat:
switch(key&0x0000ff) {
	case 0x00000f: // forward
		rotMat.appendRotation(SPEED, Vector3D.X_AXIS);
		break;
	case 0x0000f0: // backward
		rotMat.appendRotation(-SPEED, Vector3D.X_AXIS);
		break;
}
switch(key&0x00ff00) {
	case 0x000f00: // left
		rotMat.appendRotation(SPEED, Vector3D.Y_AXIS);
		break;
	case 0x00f000: // right
		rotMat.appendRotation(-SPEED, Vector3D.Y_AXIS);
		break;
}
Now only thing left to do is add that rotation to the box3D object, before it has its final transformation created in finalTransform:
var boxMat:Matrix3D = box3D.getMatrix3D();
boxMat.identity();
boxMat.append(rotMat);
Which should result in this:
To make this example a bit more interesting and also check if the rotation is correctly applied, that is without being affected by the translation, lets use the Vector3D I've mentioned earlier. Like this:
private var posVec:Vector3D = new Vector3D();
Working with vectors should not bring any troubles at this stage, so I will only mention we will have to append it in proper place - right before the boxMat.append(rotMat); insert this line:
boxMat.appendTranslation(posVec.x, posVec.y, posVec.z);
In turn getting this:
So the global transformation isn't really that complicated, but there is still the matter of local rotation. Fortunately it is quite trivial and comes down to only finding the right axis:
switch(key&0x0000ff) {
	case 0x00000f: // forward
		rotMat.appendRotation(SPEED, rotMat.transformVector(Vector3D.X_AXIS));
		posVec.y += SPEED/50;
		break;
	case 0x0000f0: // backward
		rotMat.appendRotation(-SPEED, rotMat.transformVector(Vector3D.X_AXIS));
		posVec.y -= SPEED/50;
		break;
}
switch(key&0x00ff00) {
	case 0x000f00: // left
		rotMat.appendRotation(SPEED, rotMat.transformVector(Vector3D.Y_AXIS));
		posVec.x -= SPEED/50;
		break;
	case 0x00f000: // right
		rotMat.appendRotation(-SPEED, rotMat.transformVector(Vector3D.Y_AXIS));
		posVec.x += SPEED/50;
		break;
}
The X_AXIS vector will be transformed the object's local system, which will give us the exact location of the X axis we will use for the rotation. If done right at least one edge (depending on the direction of the rotation) will stay in same place (minus the translation):
Source code: Tutorial3D_rot.zip

Understanding the "Graphics" symbol

Anyone who already spent some quality time in ActionScript is no doubt fully aware what MovieClips do or how they work, however Flash also allows for creation of something very similar, the symbols called "Graphics". At first they might seem like a simplified versions of MovieClips, which is of course true but their existence relies on much simpler rule... during the exporting process all "Graphics" symbols are split into pieces, similar to how the "break apart" command (CLTR+B) does it, with only difference being that all properties and tweens are applied to objects within. From the ActionScript's perspective this gives us an interesting ways of optimization by removing excessively nested MovieClips.
For example lets take a scenario where we want two objects to be circling around each other and also at the same time both of them smoothly relocated to a new position:

So the Anim symbol contains the animation of two circling objects, while on the stage both of them are being moved to a new location. Nothing interesting here yet, but if we decide we want access to both of those objects from ActionScript instinctively the first step will be to switch Anim from "Graphics" into "MovieClip" and than followed by giving it an instance name so we can navigate through it to access the two objects we want. Which is exactly the nesting we can avoid by using the Graphics - of course if we know how they work.
I suggest experimenting with this by yourself, starting by creating two MovieClips on the stage with two different key-frames within them, then name them "mc1" and "mc2" (instance names, so you can access them from ActionScript) and finally enclose the whole thing inside a Graphics symbol. What will happen if we call the following script from the Stage's timeline?
mc1.gotoAndStop(1);
mc2.gotoAndStop(2);
Because the Graphics symbol will be broken apart with MovieClips "mc1" and "mc2" being thrown outside, the script will execute correctly.
A ready example for download: GraphExample.zip

Site locking with ActionScript

There is no sugarcoating over it, whatever is put on the Internet can be copied and redistributed without author's permission and Flash is not an exception. Luckily, as opposed to music or pictures, with a bit of ActionScript we can restrict access to our Flash file to only specific sites.
The task is simple, we will take the address our Flash application is started at and compare it to the one we want it to be:
function checkDomain(...domains):Boolean {
	if(domains == null || domains.length==0) return false;
	
	var sList:String = ".*"+domains.join("|.*");
	var sURLswf:String = stage.loaderInfo.url;
	var sRegPattern:String = "^http(|s)://("+sList+")/";
	var regPattern:RegExp = new RegExp(sRegPattern,"i");
	
	return regPattern.test(sURLswf);
}
The checkDomain function takes a list of comma-separated domains and checks them against the address from which the SWF file is being loaded - if any of the supplied domains matches the URL we get true in return. Comparison is done with regular expressions which are not part of this article, so I will just quickly explain that "sURLswf" variable is matched against the pattern in "sRegPattern" variable created from the list of supplied domains.
This actually could do, but there is still a chance our SWF will be loaded using on external Loader, which in turn means "loaderInfo.url" will still return a proper address despite the Loader being on different site. Solution? Simple, we add this line to the checkDomain function:
var sURLLoader:String = stage.loaderInfo.loaderURL;
...
return regPattern.test(sURLswf) && regPattern.test(sURLLoader);
And done, now it will be impossible to run our application from unauthorized servers.
However one thing needs to be pointed out, this function won't prevent the so called hot-linking, or in other words the loading of files from our server onto a completely unrelated website. That being said, we can't really do anything about it from the Flash side as it should be prevented from the server itself - I recommend reading about hot-linking and "htaccess" at this point.
But what if our host simply doesn't not support such countermeasures?
There is one trick that could possibly help, but unfortunately it is not always effective - quite frankly I am even not sure if it works at all, since I never had a chance to test it. Before I say anything more take a moment to check the code you use for embedding the SWF file and make sure that the allowScriptAccess line is set to "sameDomain". This simple change will allow Flash to execute a JavaScript code, which in turn will give us access to the contents of the browser's address bar. To do this we will have to call this code in ActionScript:
var  sURLTop:String;
if(ExternalInterface.available) {
	try {
		sURLTop = ExternalInterface.call("function(){return window.location.href}") as String;
	}catch(e:Error) {
		 sURLTop = "";
	}
} else {
	sURLTop = "";
}
If everything goes as planned, no outsider will be able to do anything at this point - allowing JavaScript execution will provide us with current URL that we will instantly check and block, but on the other hand restricting the code will result in an Error which we will treat as "no access" as well (since our own site does allow JavaScript). This solution would be perfect, but there is nothing stopping the users themselves from disabling the JavaScript in their browsers...

Strict comparison to a class type without the use of 'is' or 'instanceof'

In ActionScript2 and 3 you can always check if an object belongs to a certain class - in AS2 the keyword for this is instanceof while in AS3 its is and both of them do a pretty fine job, however they also return 'true' when checking a base class against a subclasses. In other words MovieClip check against Sprite will get us 'true':
var mc:MovieClip = new MovieClip();
trace(mc is Sprite); //true
This raises a question: what if we want to check against a specific class and only that one class? Solutions are two:
1. The getQualifiedClassName function.
Which is the most straightforward answer - we extract the class name as a String and compare it against the name we are searching for.
var mc:MovieClip = new MovieClip();
trace(getQualifiedClassName(mc) == "MovieClip"); //true
trace(getQualifiedClassName(mc) == "Sprite"); //false
2. Comparing the constructors.
This is a bit more advanced topic that only works in AS3. It is not always clearly stated but each object in ActionScript3 has a 'constructor' field which always holds the reference to the constructor used in creation process of an object and because in ActionScript there can be only one constructor, it just a matter of comparing them:
var mc:MovieClip = new MovieClip();
var con:Object = MovieClip.prototype.constructor;
trace(mc.constructor == con); //true
However there are few things worth pointing out here:
- because 'prototype' and 'constructor' are created dynamically, their access time is slower in comparison to the getQualifiedClassName function.
- however with a reasonably long loop and local variable holding the constructor reference, execution time can be even as twice as fast.
- mc.constructor in FlashDevelop (but not Flash Professional) will most likely throw an error, which needs to worked around by casting mc to the Object class:
var con:Object = MovieClip.prototype.constructor;
trace((mc as Object).constructor == con); //true
Unfortunately this will decrease the execution time by around 10%.

Interfaces in Flash [basic]

In programming, by ‘Interface’ we understand an abstract definition of methods that by themselves don’t do anything, except for forcing a certain syntax in a class that implements them. In other words we define a function and its arguments in one place and then implement its mechanics in another (target class). Of course by no means its the best way to create a function in Flash, but interfaces have a special trait that makes them irreplaceable in certain scenarios. This trait is ability to cast an object to the interface they implement, which in turn allows us to communicate with it, without actually know what the object is.
I think it is best to demonstrate it on a example, starting from the very beginning: let’s say we want to create a game that displays some information through pop-ups in such way, that each of them contains an “OK” button:
public class MyPopup extends MovieClip {
	private var myButton:OKButton;
	public function MyPopup() {
		myButton = new OKButton(this);
		addChild(myButton);
	}

	public function close():void {
		this.visible = false;
	}
}
public class OKButton extends MovieClip {
	private var myPopup:MyPopup;
	public function OKButton(popup:MyPopup) {
		myPopup = popup;
		this.addEventListener(MouseEvent.CLICK,onMouse);
	}
	private function onMouse(e:MouseEvent):void {
		myPopup.close();
	}
}
At first everything might like fine, but as soon as we start adding more pop-ups the limitation of the above code become apparent - what if we want to reuse the same button for MyPopup2 ? Is adding another argument ( OKButton(popup:MyPopup, popup2:MyPopup2) {} ) enough? Should we repeat this for each new pop-up we want to add? One solution for sure is to create a base class for every pop-up, so we cast it down to use shared methods. However such root classes might bring some troubles in Flash, that I won’t be discussing here. At this point it is probably quite obvious what I am trying to say - the best solution is to use Interfaces. So, let’s create a new AS file:
public interface IPopup {
	function close():void
}
And then modify existing classes:
public class MyPopup extends MovieClip implements IPopup{
	private var myButton:OKButton;
	public function MyPopup() {
		myButton = new OKButton(this);
		addChild(myButton);
	}

	public function close():void {
		this.visible = false;
	}
}
public class MyButton extends MovieClip {
	private var myPopup:IPopup;
	public function MyButton(popup:IPopup) {
		myPopup = popup;
		this.addEventListener(MouseEvent.CLICK,onMouse);
	}
	private function onMouse(e:MouseEvent):void {
		myPopup.close();
	}
}
The difference is quite small, but possibilities definitely much broader. From now on MyButton can communicate with every pop-up without actually knowing what class it is, as long as they implement the proper interface.

Debugging in local enviroment [basic]

In previous post I've described some very basic debugging methods for already released products - while we are on topic of debugging, lets have a look at some popular and easy methods for fixing errors in local environment. Of course everything comes down to what we want to do with our application, but there will be always room for those universal tricks:

1. Execution speed time.
Skipping past the fact you should build your application also considering slower computers, speed measuring is a great way to test algorithms, especially if we know how much time they need beforehand. For more prominent results it's usually a good idea to add a loop:

public static function measureProcessTime(fun:Function, repeat:int = 10):Number {
	var time:Number = getTimer();
	for(var i:int = 0; i < repeat; i++) {
		fun();
	}
	return getTimer() - time;
}
Note that local functions are especially useful here as Flash allows their creation pretty much everywhere, allowing us to test any part of the code by simply enclosing it in the following way:
var someVar:Number = 0;
someVar = 1;
function debug() {
	var otherVar:Number = 10;
	someVar += otherVar;
}
trace(measureProcessTime(debug));
}

2. Loop traversing.
This is another neat neat way to test algorithms. Basically the idea here is to display one single point in a loop, but with ability to move that point around. Example implementation:

private static var DEBUG_COUNT:int = 0;
private static var DEBUG_STEP:int = 0;
public static function nextStep():void {
	DEBUG_STEP++;
}
public static function prevStep():void {
	DEBUG_STEP--;
	if(DEBUG_STEP<0) DEBUG_STEP=0;
}
public static function resetDebug():void {
	DEBUG_COUNT = 0;
}
public static function countDebug():Boolean {
	DEBUG_COUNT++;
	return  DEBUG_COUNT == DEBUG_STEP;
}
Functions nextStep and prevStep are meant to be connect to the arrow keys, allowing us to traverse the loop. The resetDebug function should be called before the loop we want to test. Finally the countDebug function which will return true when our loop reaches the selected step. Although it doesn't seem like much, loop traversing can be incomparable debugging tool. Good example of this is a line drawing algorithm:
(Use the up and down arrow keys to move around the loop)

3. Graphics overlay.
I think no one will disagree it's easier to see data in graphic representation rather than list of variables, which is why every application should always have some kind of way to draw (using Graphics class) information above every other object. This can be easily done by holding the main application in a "container" MovieClip, while having a single Sprite one step above it, which will be empty but accessible from anywhere in the application:
public static debug:Sprite;
public function DocumentClass() {
	var main:MyApplication = new MyApplication();
	addChild(main);

	var debug:Sprite = new Sprite();
	addChild(debug);
}
Now we just need to write DocumentClass.debug.graphics to start drawing over everything else. Personally I like to use it as a way to check hit-boxes of objects on the screen:

Debugging on user side [basic]

Debugging is a popular term used to describe a process of removing errors (or any kind of unwanted behavior) from a source code. Developing in Flash Professional or FlashDevelop we already have an access to pretty good debugging tool, which no doubt helped all of us fix at least few problems. Unfortunately debugger can only used by the developers themselves and they can not always foreseeing every possible scenario users might find themselves in. Which is why it is always a good idea to implement a mechanism for catching any errors from the user side - lets have a look at the most popular ones.

1. Main Loop.
This, what might seem to be a trivial thing, should be a part of every self-respected applications and not only because it makes debugging a much easier process, but also helps avoid memory leaks and makes whole code easier to read. But what exactly is a "main loop"? In Flash it basically comes down to having only ONE "ENTER_FRAME" event in whole application, which means every other function will executed directly (or indirectly) from the main loop. Such approach will make catching errors a trivial task, as it only requires a simple try and catch around loop's code, ensuring we always stay in control. Here is an example of a main loop:

public function render(e:Event):void {
	try {
		processPlayer();
		processEnemy();
		processMap();
	} catch(e:Error) {
		trace("Error!");
	}
}

2. Log.
Even though catching errors is very important, they might useless without information about circumstances that surround them, which is why a log that details all key functions used by the user is second most important element in every application. Writing down user status (Flash version, user's operating system etc.), button they press or windows they opened, or in case of games, what map they are on, what is their progress and events they already completed will help us recreate the scenario in which user encountered the problem.
Log class is always the easiest one to implement:

public class Log {
	private static var log:String = "";
	public function Log() {}
	public static function add(s:String):void {
		log += s+"n";
	}
	public static function toClipboard():void {
		System.setClipboard(log);
	}
}

3. Console.
Most likely there is no PC gamer didn't ever use a console, even if it only was to enable the "god mode". By console I mean a (usually hidden) applications commend line, which allows every user to fire a predefined functions at any given moment. Implementation of a good console requires a good deal of work, but luckily there is already a nice selection of ready solutions on the Internet, like for example: Flash Console. Everyone should try out creating a console for their application - one time is enough to fall in love for them.

Finally, when our application is ready, it is a good idea to replace the trace function in the main loop with something that will help us receive any possible error's information, by for example sending an e-mail.

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 tile-based engine part 1: isometric view [basic]

In this three-part article I will try to explain some of basic problems one might stumble upon when working with tile-based engines. Since they are quite old one might think that the topic was already exhausted, however many people still have problems with it, often complicating trivial issues.
One of those issues is isometric view (sometimes might be called "diagonal view") which a specific type of 3D transformation without the perspective and was often used in older strategy games. Although the definition mentions 3D transformation it is actually not required at all and can be replaced with something much simpler, which unfortunately not many people try to do, resulting in many articles like this one focusing on basics of 3D - unnecessarily.
Basically you could sum everything to this simple function:
function getTile(nTargetX:Number,nTargetY:Number,nWidth:Number,nHeight:Number):Point {
	var nScale:Number = nWidth/nHeight;
	var nTransY:Number = (nScale*nTargetY-nTargetX)/2;
	var nTransX:Number = nTargetX+nTransY;
	var nTmpY:Number = Math.round( nTransY/(nHeight*nScale) );
	var nTmpX:Number = Math.round( nTransX/nWidth );	
	var nTileX:Number = (nTmpX-nTmpY)*nWidth;
	var nTileY:Number = (nTmpX+nTmpY)*nHeight;
	return new Point(nTileX,nTileY);
}
Which results in this:
(Green circle is the Point returned by the getTile function)
Try it yourself: tile_example.zip

That is pretty much it, now the only thing left to do is explain how getTile works step by step:
1.
var nScale:Number = nWidth/nHeight;
Scale will be used during the transformation to realign the difference between height and width (as the algorithm works only in 1:1 scale).
2.
var nTransY:Number = (nScale*nTargetY-nTargetX)/2;
var nTransX:Number = nTargetX+nTransY;

Here is where a point on the screen gets transformed into a point in the isometric view.
Usually when we are talking about an isometric view, we are thinking about a space that is rotated by 45 degrees in such way that movement to the right is not a simple +X, but rather some value of X plus partly also some Y. The example will probably make more sense:

As we can see the movement of the mouse cursor to the right is transformed into a point that is only partially moved to the right, because partially it is also moved upward. Armed with this knowledge we can try and write a general equation - since movement to the right (+X) gives a bit of upward movement (-Y) we can say that Y gets smaller along with X getting bigger, or in other words: Y = -nTargetX. However we have to remember it is not a straightforward transformation x=>y (otherwise it would result in 90 degrees turn), which is why have to divide the equation by 2 (as 90 degrees divided by 2 gives us 45... in a great simplification). Now that we know Y, getting X is quite trivial as we only need to subtract the Y value from target X (so the distance remains the same as the one before the transformation) and since Y is already negative we need to use the addition Y = nTargetX+nTransY;. By the way, you can switch plus and minus signs between each other to achieve a -45 degrees rotation.
3.
var nTmpY:Number = Math.round( nTransY/(nHeight*nScale) );
var nTmpX:Number = Math.round( nTransX/nWidth );
var nTileX:Number = (nTmpX-nTmpY)*nWidth;
var nTileY:Number = (nTmpX+nTmpY)*nHeight;

Here we are translating given point into a tile, by "shrinking" the whole space using width and height values, then discarding any numbers after the comma and finally bringing everything back to "normal size". Because I love examples here is another one: if tile #0 is 20 pixels width, then every pixel between 0 and 20 will belong to it. So now the question is: how to check which tile owns the seventh pixel? Lets take that 7 and divide it by 20, resulting in 0.35. Now we discard everything after the comma and get our tile with id #0. Lets re-try that for pixel 27; 27/20 = 1.35; drop the comma stuff and we get #1, which is correct because as we already know 27 doesn't belong to #0.

Phew, that is it for today. In two weeks I will write about a very simple path finding algorithm you could implement in a tile-based engine.


Older posts>>>