Isometric - Too ambitious?

smacks sen

You have way too much dman time on your hands… But then again… So will I until I find another job… :trout: :slight_smile:

yawwwn… When ya gonna get to the exciting stuff… Like multi-layered collision detection… Bridges and stuff like that… lol

I’ve dabbled in this as well… And have been working with multilayered stuff ever since… And stuff like… Good Job on the coding though.

if you’re a ‘complete beginner’, then it might be a little ambitious. I have a simple key controlled iso example here
http://www.umbc.edu/interactive/fla/isometric.swf
http://www.umbc.edu/interactive/fla/isometric.fla
but thats not including path finding…
ahmed’s example does here (between 2 points in an open grid):
http://www.kirupaforum.com/showthread.php?s=&threadid=11356&perpage=15&pagenumber=15#post82974
Still… if you are getting it done, chances are you arent writing it yourself, and youd be using other pre-written code. Im sure theres more complete examples around which you can assimilate into your own work. You’ll just have to search for them.

but thats what Im saying, it IS that complex… unless you want to pre-animate it all by hand

to get a block out of the _x/_ymouse use


gridPosition = function(x, y){
	var xm = x/spacing;
	var ym = y/spacing;
	return {x:Math.round((xm + 2*ym)/2), y:Math.round((xm - 2*ym)/2)};
}

where the 0,0 block centered on 0,0 of the movieclip and spacing is the height of a single block (where its width is spacing * 2)

… theres also a more intuitive way of handling things with a rotated and scaled clip… Ill do a writeup on isometrics and cover that (when I have time - maybe tonight since its friday and I have no life :))

…though common grid detection like that is done through buttons - each grid space is an attached button with its press revealing its position in the iso grid

[edit] code typo (missing })[/edit]

ok I know I said Id try to write something up on isometric broo-haha but something came up and I got distracted in all my self-absorbtion :!:

So what Im going to do is go home now, get some grub, loaf a little and then sit at my desk and do what I can until I pass out, then finish up tomorrow and post it up.

What I’ll cover is (planning so at least)

  • what is isometry
  • applications of isometry
  • the grid system
  • isometric drawing vs isometric coding
  • approaches to working with isometric grids
  • arrays & loops and their use in the creation of grids
  • objects within isometric grids

and hopefully get to cover more about:

  • screen object interaction
  • movement - progressing from one location to another (not specifically isometric-related)
  • simple pathfinding along an obstacle ridden path in a grid (not specifically isometric-related)
  • vertical positioning within a flat iso plane
  • methods of depth handling and ways to cheat depth (hehe - well I thought it was funny)

… and other things which I may not be able to remember at this time.

Come to think of it, thats a lot to cover, and much of that can be very in depth. Well, Ill do what I can and slap up what comes out of it hopefully tomorrow.

ok the first installment. Not at all juicy for those who already know the basics. I didnt get much done last night because I was busy heling Aislin learn Flash (I think hes doing good ;)) But heres some quick foundation:

Isometric Perspective
First off, before messing with isometrics, you should know what isometry is. Isometry is equality within measurement. What this means, is that in any isometric representation, all measurements are to scale, no matter how far close or how far in the distance they are in view. In an isometric perspective, you have a 3D view where, no matter where you are in that space, the object scaling retains its value and doesn’t change. Conversely, in a true perspective, objects reduce in size as they recede into the background.

Because of this lack of perspective scaling in isometric perspectives, they provide a way to represent a 3D object in 3D space while maintaining proportions and hence making it reliably measurable (for the most part). The following is an isometric cube that has all of its edges equal in length - represented in a 3D manner without true perspective distortion.

This can be invaluable in technical drawings for engineers and architects where an object can be represented, and therefore more easily comprehended, in a visually understandable manner but still maintains correct line measurements for referencing. For instance, in 4-panel technical object drawings (for engineering), much like the 4 views you see in today’s 3D modeling software, you often have a front, top, side and isometric view (though in 3D apps your isometric is a scaled perspective). Id whip out some of my blueprints to show this but they’re buried deep in my closet and I don’t know where my digital camera is :wink: The isometric view is “to scale” (for the most part) and easily drawn because it relies on known measurements. This prevents the need for the draftsperson to have to calculate and determine perspective values for the 3D view, making it much easier and faster to produce. The ease of creation is the true advantage to isometric perspectives in that instance.

So far I’ve said “(for the most part)” twice already. What I mean by this is that not all dimensions in isometric views are actually to scale. What are to scale are the lines which run parallel to your x, y or z axes. Because these lines are skewed and at unusual angles, there will be distortions in diagonals and other lines of the such. This really isn’t all that important though, and nothing to concern yourself about :slight_smile:

Drawing Isometric views
Drawing isometric views is based around one important tool. Your 30/60/90 triangle. What this is, is a straight-edge tool that helps draw lines on your drawing paper. Its called a triangle because its in the shape of a triangle. A 30/60/90 triangle is a triangle that has angles of 30 degrees, 60 degrees and 90 degrees. This lets you draw lines straight up (90 degrees), lines at 60 degrees, and lines at 30 degrees. The 30 degree lines are the important ones. Below is an illustration of how the previous cube would be drawn on the drawing table with a 30/60/90 triangle.

You can see that the lines that make up the cube are all either up and down or 30 degree lines (going in either directions), easily made with the triangle. This is the basis for virtually all isometric drawings. When you hear “isometric view” you should immediately think “30 degrees!” This isn’talways the case, and we’ll see that a little later on, but in terms of understanding what isometry is, the 30 degree angle concept is the one to associate it most with.

For drawing in Flash, however, you don’t have triangles such as the 30/60/90 triangle. What you do have is the transform panel. This panel allows you to not only scale clips and shapes within Flash, but also rotate them. So in drawing isometric images (in this manner) in Flash, start off with a single vertical (or horizontal) line and duplicate/rotate it fitting it in to place where ever it needs to be to create your isometric view. Because the measurements in the isometric view are consistent, duplicating the same vertical line and reusing it for your 30 degree angled lines is perfectly acceptable. In fact, its preferred since it assures that your lines are of correct length and therefore will fit together as they should in the end. Here’s a short walk through of drawing the iso-cube http://www.kirupaforum.com/showthread.php?s=&threadid=13088

Grids
Before getting more into isometrics in Flash, we need to first get a brief foundation in grids and grid systems. Chances are you know what a grid is already. A grid is a cross-referential table of positions, sometimes in themselves containing values or just posing as a possible a position within that grid. Easy enough, right? Graph paper is a grid. Flash with View > Gird > Show Grid selected… is a grid. All x, y positions are based on a grid. The grid you see in Flash is a grid based on x and y locations within the Flash work area with the upper left being 0,0 and the lower right being document width, document height passed in pixels. Each position in a grid is determined by the x value, or the distance left and right, followed by the y value or the distance up and down.

In terms of Flash, as y increases, the position on the screen becomes lower in lower. In math, on the cartesian plane, the opposite is true. On the cartesian plane, the typical representation of the x and y coordinate space (grid) as y increases, y gets higher. Here’s where you have to make a choice Neo. Either you chose to follow suit with Flash’s flipped y or, in your constructed mechanisms of movement (scripted), you opt for the more traditional y is up understanding.

I personally prefer the cartesian method, where an increasing y means movement up, and that will be how I utilize it in the bulk of the isometry covered here, though in one technique I show you, it just may be easier to use Flash’s perception of y movement. That, again, will be covered a little later on.

For dynamically populating a grid in Flash, loops come in good use. Having two loops, one embedded in the other, allow you to loop through and access each position in the grid by looping through its width and height. In looping through both, you cover the entire area of the grid. For example, if you wanted to make a 10 x 10 grid, you would have something like this:


for (x = 0; x < 10; x++){
	for (y = 0; y < 10; y++){
		this.createTextField("_txt"+depth, depth, x*20, y*20, 20, 20);
		this_txt = this["_txt"+depth++];
		this_txt.border = true;
		this_txt.text = x+":"+y;
	}
}


Just throw it in and test. This loops through a 10 x 10 grid (of spaces 20 tall by 20 wide) and creates a text field in each position. That text field text is set to the x and y value so you can see how the loops executed as they run through the grid. For any kind of cycling through grid spaces, this is the way to go about it. I don’t want to spend too much time on grids since this is about isometric perspectives. However, those isometric perspectives are based around grids, so a base understanding of what a grid is and how a position is defined is needed. Now lets get back to the drawing of isometric views.

The Isometric Alternative to 30 Degrees
So we saw how isometric perspectives are based on a 30 degree angle, but also learned that, that’s not always the case. The two most common instances where its not are 1) pixel art and 2) programming (a la Flash). Here, instead of using 30 degrees to base the altering angle of the perspective, a 2:1 ratio with the x to y coordinate spacing is used. What this means, is that to create your perspective isometric angle, you’d move one space up or down (y) for every 2 spaces left or right (x). This is easily seen in a close up of the precise miniature imagery in pixel art.

Though this is very close to a 30 degree angle, it is not. Its closer to 26.6 degrees. If you were to convey 30 degrees as a ratio, it would be more like 1.73:1 or for every 1 space along y, 1.73 move spaces along x. As you can probably imagine this would not be very suitable for pixel art. Using 26.6 degrees, and the 2:1 ratio allows a steady pixel slope such as the one depicted in the image above.

The difference; is this a problem? No of course not. Each are basically just a different views with one method being slightly more slanted than the other. The difference is so subtle that you’d probably never notice it unless you were told or were directly comparing one to another and the changes became apparent. Each style is based on what is more appropriate for the situation.

To follow, actually getting to the programming of isometric grids in Flash :slight_smile:

still only dipping the toes in, more to come…

Programming Isometry in Flash
There are two main reasons why 2:1 is preferable for your isometric work in Flash. One is that it gives us nice even, simple numbers to work with. There’s no reason in messing around with complicated decimal and fraction variations of screen placement if you don’t have to. The other reason is that it makes drawing isometric views in Flash easier. Granted, the method described previously with rotating lines isn’t brain surgery, but what’s easier is using the line tool with snap to grid on and just drawing point to point lines along the grid at 26.6 degrees (for every 2 spaces left or right moved, move 1 up and down). But, the advantage is not only for drawing from within Flash, but also for imported isometric pixel art, which itself is also based on the 2:1 x to y ratio. Keeping your programming on that basis will allow those images to mesh well with the movement about your isometric grid.

Yes, isometric movement is grid based. What isn’t, right? :wink: In an isometric perspective, you are moving along two directions along a flat plane. For all events and purposes in our isometric world, we’ll say along x and y. Here you have to make another choice. Which way is x and which is y? You cant really base it on the current x or y because in the isometric grid, going in either direction would involve going the both distances of either x or y. Consider the following.

As you can see, either direction along the grid causes the same span of movement along each axis along Flash, neither is definitively along the y axis or along the x. What I say is that direction A is along the y and direction B is along the y. I’m also going to say that A is pointing in the negative direction, and B is pointing in the positive. With that asa basis for our grid setup, we get the following.

With the far left grid space being point 0,0. So with that now straightened out, we can start programming our isometric code.

Where to now? This can be the hard part. Depending on how functional you need your isometric world to be determines how deep you need to get into it in programming. The choice of including or not including one option can change the whole programming style used in your approach. First, we’ll start off with the absolute basics.

Basic Key Movement Along an Isometric Plane.
First things first, draw a plane… but NOT on the main timeline. Make one in a new movie clip. This will keep the plane contained and easy to manipulate once complete. In drawing, use the grid and grid snapping to help you create your lines. I have my grid snapping at 10 px. Its good to know what yours are set at if you don’t already. They determine the size of your grid as you’re drawing it. If you want, you can use some checkerboard coloring to help distinguish the squares - like those I’ve been using in the examples above. Once complete, exit the movie clip. and make a new movie clip. for the moving object. This is the object which will travel about on the grid. For testing purposes, it can just as well be a circle. You can always go back and change the imagery though Ill refer to the object in question as “ball” from here on out. When done, exit that clip, and with both clips on the stage, arrange them so that the ball is above the grid and centered on a square.

At this point nothing happens if you test the movie - obviously. To change that we’re going to add some script. What script? The script that causes “Basic Key Movement Along an Isometric Plane” of course! The script will be triggered by a keypress and can be put directly on the ball as a clipEvent - the keyPress clipEvent to be exact. In doing so we’re going to have to know the size of each grid on the screen, or at least some numerical reference from which to base our calculations on. If you know, or figured out, your grid spacing and to what dimensions you drew your grid, then this shouldn’t be a problem at all, though a lot of measurements can come out of that grid…

With the way this was drawn (using 2:1), all of these measurements fit together nicely. Well all except the span, which is the ‘actual’ distance traveled and can be attained through trig, but that’s actually not important at all at this point so it can be completely ignored. What IS important are the x and y spans. These are the spans we need to move in order to make it to that other square the red arrow is pointing to. Assuming each one of my grid spaces were drawn with a width of 40 and a height of 20, my x and y values would be 20 and 10 respectively; 20 being 2 times the value of 10. So to move to the new square, a movie clip. would have to move 20 spaces to the right (_x += 20) and 10 spaces up (_y -= 10). In doing this we will have moved, in isometric grid terms, plus one y space. With that in mind, we can figure out what it takes to move in the other directions and assign them to the proper keys in our keyPress clipEvent, like so:


onClipEvent(keyDown){
	if (Key.isDown(Key.UP)){
		_x += 20;
		_y -= 10;
	}else if (Key.isDown(Key.DOWN)){
		_x -= 20;
		_y += 10;
	}else if (Key.isDown(Key.LEFT)){
		_x -= 20;
		_y -= 10;
	}else if (Key.isDown(Key.RIGHT)){
		_x += 20;
		_y += 10;
	}
}

When this is put on the ball clip, the ball properly traverses the grid in the appropriate manner when using the arrow keys! Be careful though. Hitting up too many times will take the ball right off the screen! There are no protections to prevent this, but we will implement some later.

A Quick Calculation
This past example was a simple example, and the rest will go faster and with less step by step instruction in the assumption you know what’s going on, but there is an important concept to be understood here. That is the relation between _x and _y placement on the screen and x and y placement within the isometric grid. This example begins to embrace the very concept which all other isometric placement is based and from that two equations are developed for each _x and _y placement:

_x = spacing * (x + y);
_y = spacing/2 * (x - y);

where _x and _y are the objects on-screen coordinates, x and y are the isometric grid coordinates and spacing is the height or x distance of your grid measurement (20 in the key movement example).

So how was this derived? To come to this equation, lets think back to the previous example and start our ball at the isometric point (0,0) though, at the same time, this point should ALSO be be our _x and _y (0,0). Here’s where putting the grid in the movie clip. comes into play. With the grid in the movie clip., you can easily set your grid to be at (0,0) and arrange the grid movie clip properly on the main timeline not to be thrown in the upper left hand corner of the screen. Anyway, now, at isometric (0,0) our screen position is also (0,0) which is good because it starts us off with a conformity. This is very important though, because it will be consistent for all future examples here. In moving up to isometric (0,1) - up one grid space along the y, we move the ball to screen position (spacing, -spacing/2). So without moving along the x at all, both the _x and _y values changed. If we moved from (0,0) to grid position (1,0) - right one along the x then the new screen position would be (spacing, spacing/2). Again, both the _x and _y changed with only the x of the isometric spacing changing. so for every isometric x movement, _x increases by spacing and _y increases by spacing/2 while for every y movement, _x increases by spacing and _y increases by -spacing/2. Putting that into a formula, we get:

_x = x * spacing + y * spacing;
_y = x *-spacing/2 + y * spacing/2;

which reduces to:

_x = spacing * (x + y);
_y = spacing/2 * (x - y);

which are the original functions mentioned above - the functions used to calculate any given _x or _y given an isometric x and y in a grid with the given spacing. This will be used as a foundation for positioning in a future example, but first, lets look at a different way to handle isometric positioning with Flash.

A Different Approach
Flash, being as special as it is, allows us to take a completely different approach to handling isometric positioning without the need of any kind of complicated calculation as previously mentioned. This is achieved through movie clip. transformations. Because movie clips are self-contained and operate through their own timeline and own coordinate space, you have the ability to change and alter the appearance of a movie clip. on the screen and not effect any of the movie clips internal proportions. Visually, on the screen they appear altered, but this is only because, as a viewer, you are seeing them as through a transformed instance. Lets say of example, if you were to take a movie clip. and reduce its height by 50%, the contents of that movie clip. would visually shorten from the main timeline, though, from within that movie clip., would not display any signs of change. This is because the contents themselves were not altered, but rather the clip they are in.

If you haven’t already caught on, the point I’m getting at is that, instead of using mathematic calculations to position objects isometrically, we can let Flash do it for us using movie clips to automatically add the needed transformations, then globalize the new point to the main timeline and position objects there to the newly acquired point.

There’s two calculations needed here, one being the rotation, as we are going from a square grid to a rotated diamond like grid, and then a scaling transformation to give us that perspective look and bring us closer to the 30/26.6 degrees of isometry. This is done with two movie clips - one for each transformation. See illustration:

(The gray cross hairs represent (0,0)) For any movie clip. within mc1, given a normal path along say, the _x axis, that movie clip. would, on the screen, appear to traveling along the isometric angle from within those transformations. The only problem is, it too would be rotated and scaled and distorted beyond normal recognition. That is where the globalization comes into play using Flash’s localToGlobal() function where you’d use either an a simple variable point or even an empty movie clip. to maintain your grid position and relay that to the main timeline as a coordinate there. Example; similar set up as before with the ball (_root.ball) and the grid, only the grid is transformed like described and the following code is placed on an empty clip in _root.mc1.mc2 representing the grid movement:


onClipEvent(keyDown){
	if (Key.isDown(Key.UP)){
		_y -= 20;
	}else if (Key.isDown(Key.DOWN)){
		_y += 20;
	}else if (Key.isDown(Key.LEFT)){
		_x -= 20;
	}else if (Key.isDown(Key.RIGHT)){
		_x += 20;
	}
	point = {x: _x, y: _y};
	_parent.localToGlobal(point);
	_root.ball._x = point.x;
	_root.ball._y = point.y;
}

Transforming the point with localToGlobal() allows the ball in _root placed on that position to follow this clip precisely without any complicated calculations. All positioning within _root.mc1.mc2 (the grid) relates accordingly to the apparent isometric position when viewed from the main timeline.

Now this technique can pretty much replace that which uses the functions outlined earlier, however, the _y if you’ll notice is flipped here as it is in Flash (which isnt a big deal) and this method as a whole is a little unorthodox and possibly even confusing due to its transformations. Those transformations also require going through the scope of 2 movieclips to obtain any of your desired points which in itself may be obnoxious to deal with. Using the mathmatical approach over this one is more practical and contained. It may be a little more difficult to understand but it presents itself to be the easiest to handle as everything is operated through one scope, plus it allows the methods used to be more easily ported to another language. The rest of isometric handling discused here will be through that method.

*Originally posted by senocular *
**


gridPosition = function(x, y){
	var xm = x/spacing;
	var ym = y/spacing;
	return {x:Math.round((xm + 2*ym)/2), y:Math.round((xm - 2*ym)/2)};
}

**

back on page 1 :slight_smile:

this takes a screen x and screen y (either _x/_y or _xmouse/_ymouse etc) and converts it to an isometric x and y returned in a sinlge object {x: x, y: y}. Here the values are rounded within the function so it points to a spefic single isometric grid block.

spacing represents the height of an iso block or the x distance needed to go from one block to another (same value)

Im writing this part of the “big iso explanation of 2003” as we speak. Actually, I can post that part now. Its just the beginning of it all but it wont hurt to throw it in early :slight_smile:

Isometric Coded
Mentioned earlier were the base formulas for finding a screen _x and _y for any given isometric x and y. (Remember, this is all based on a scenario where isometric (0,0) equals screen (0,0).)

_x = spacing * (x + y);
_y = spacing/2 * (x - y);

Where spacing is a predefined constant representing the height of an isometric grid block (or the x movement in a single block movement as they are the same).

From these two equations we can solve for x and y to get the isometric grid position from any screen _x and _y.

x = (_x/spacing + 2*_y/spacing)/2;
y = (_x/spacing - 2*_y/spacing)/2;

Each of these functions, though seemingly simple, are the basis for all isometric behavoir. What they do is convert normal grid spacing to isometric spacing and vise versa. Outside of that, all coded actions are simple, normal grid behaviors, just as you would write for normal flat grid layouts. If you havent done that before, well I’ll probably cover some of that as well. :wink: One thing to remember here though, is that, according to my preference and how this code is set up, I am using cartesian plane y direction which in terme of Flash means -y = y with the flipped y axis. So in converting Flash-ready grid operations to this isomtric setup, that reversing will occur.

*Originally posted by timhlu *
**I may be wrong, but shouldn’t the formula for finding the _x and _y of the onscreen object given the spacing and isometric x and y be… **

Just FYI, everything Im putting here is only a description of a certain approach. It isnt nec. THE approach, and other techniques are just as well acceptable as long as it works. In the end, thats what really matters anyway, the fact that it works. What Im posting works and will work for you if used. Even if you dont use it, hopefully you’ll at least learn something in the process.

… the rest of the next section is about 1/3 done. Might be slow going though because I have yet to write up a lesson plan for a class Im teaching tuesday, so I think tomorrow will be spent on that. Ill see what I cant do though - maybe posting what Ive gotten so far and just finishing it up later… Ill see about that tomorrow :slight_smile:

I got up nice and early to finish this first half or so of it which is a next step up, but more is coming. :slight_smile:

At this point, another choice is at hand. Depending on the extent of your isometric endevour, you will need to decide on an approach to handling the programming and the objects involved. Should basic functions run isometric handling? should everything be controlled through extending the MovieClip object with prototypes? Should an isometric object class be created for all isometric handling? The level of depth can be quite intimidating. I’ll try to cover both a simple and a more comple example of isometric handling in these manners, though first lets go off on another tangent and look back at some grid handling.

Isometric Grid Development
Before you start moving around and interacting in your isometric world, you will first need to define it. This is, of course, handled through grids. First off, lets consider what exactly we need to define. Again, the depth of your isometric project will be reflected in what goes here, but some things may include:.

[list][]the extent of the grid and the space it occupies (not extending to infinity)
[
] the visual objects that exist on the grid
[] the suitable placement for objects on the grid, either allowable over other visual objects in the same grid position or not
[
] important points of action in the grid. For example a stairs tile. If an user controlled character lands on this grid space, the action of changing the scene from floor 1 to floor 2 would be executed.[/list]

Each determination of these coorespond to a grid location or coordinate or a group thereof. For example, think of a treasure chest sitting on the floor. Firstly, you have to know where on the floor its sitting - the grid space at which it exists so you know where to put it when it needs to be put. This you dont necessarily have to do through code, but rather, it can be done directly in Flash by placing it on the screen. And this is fine, but know you have the option of doing it through scripting as well. Once that chest is on the screen, you have to make sure user controlled (or ai controlled for that matter) characters dont walk through it. You could use something like hitTest here, but managing it through programming within our grid space is easier and yeilds better results which conform more to the “isometric way” :). Also, since this is a chest we’re dealing with, we may want to open it. In that case, we would need to define where a character may need to be (what grid space) to open it and what action will occur in the event that they try. Will the chest open or will a message pop up informing that the chest is locked an unable to be opened?

To handle these, you would need to define values that correspond to each position in the grid representing the state which exists there. So if that chest is on grid space (2,3), you’d need to set a value to that space which more or less says, “Player cant move here if he/she tries.” How this is done is through using 2D arrays.

Arrays are adequate because they can easily hold a a lot of information and are number based, just like a grid is. Using 2D arrays (arrays containing arrays) give us a way to contain information about the whole grid in one variable, being able to reference any value for each grid position with the coordinates of that position. This functions much in the same way the loops did earlier in making a grid, only this isnt generating a grid, but rather holding information about it. In fact, the loops will be what are used to extract this information. Heres an example of a 2D array for collisions in a 5x5 grid system:


collisions = [	[0,0,0,0,0],
		 [0,0,1,0,0],
		  [0,0,1,0,0],
		   [1,1,1,0,0],
		    [0,0,0,0,0]];

The odd formatting helps to visualize how the spaces will correspond on the screen making it look more like an isometric grid. Afterall, here, the array going across left to right actually represents the y and up and down is the x, which itself seems counter-intuitive, though this makes it work more intuitively when we actually work through arrays like this with loops, especially when using the cartesian plane method of y navigation . When this array is translated to the screen with the 1’s representing points of barriers or impassible objects, you get something like this, where the red represents the impassible tiles.

For a collision array such as this one, you dont need to loop through it value by value when you need to check to see if a space is a valide grid sapce for movement or not. All you need to do is check the array position that relates to the grid sqare at the x,y coordinate you need to check using

collisions[x][y];

If that value is true, or 1, then there would be a collision in that grid space making a move there invalid. If the value there is 0 (false), then there is no collision and moviing to that grid location is acceptable. When it comes down to it, you’ll actually be checking for the 0 value, not necessarily the 1. This being the case, you might want to actually replace the 1’s with 0’s and vise versa, though as I see it, 1’s function better visually as being impassible and 0’s seem more like floor tiles. Of course you could just as well use 4’s and 7’s, and at times, may need to if dealing with flat walls, but I dont think Ill get around to covering that; not here, not today.

Dynamic Grid Creation
Often grid spaces are created dynamically. This is done through loops (as previously seen with the textfields in a normal grid), looping through attaching a squares or ‘tiles’ as it progresses through the grid layout of the scene. An advantage of this is that it allows an easy way to associate a unique grid position with each tile. Once a tile is attached, it can be told where it was attached so if you’d want to use that grid as a button, for instance, it would know exactly where it is when you clicked on it. For example:


spacing = 20;
for (x = 0; x < 5; x++){
	for (y = 0; y < 5; y++){
		tile = this.attachMovie("tile", "tile" + depth, depth++);
		tile._x = spacing * (x + y);
		tile._y = spacing/2 * (x - y);
		tile.x = x;
		tile.y = y;
		tile.onRelease = function(){
			trace("x: "+this.x+", y: "+this.y);
		} 
	}
}

What this does is attaches a movieclip linked from the library with the linkage ID “tile” (a single isometric square) to the current movieclip and positions it using the forumlas described earlier so its… isometric. On top of positioning it, the clip is given x and y variables which equal the grid x and y position which it occupies. Then an onRelease method is set so that when the clip is pressed, it traces its grid location. Instead of a trace, this could be used in other ways such as a command to tell a movie clip to move to the postion clicked.

Simple example
With the knowledge put forth so far we can begin working on a functional isometric system. A heads up for the more advanced users: this will be a (fairly) simple example based on simple principles and is intended for the intermediate programmer. A more advanced example with more suitable class definition etc. will be addressed afterwards.

This example wont be dynamically created, so if you want to try this out for yourself, you’re going to have to draw your isometric plane (grid) from scratch. Be sure to know what kind of spacing is being used. Even if you dont show it visually on the screen, there needs to be some reference so that the code can correctly function in the space. Remember to draw in a movie clip and with the (0,0) point at the (0,0) point ;).

Once everything is set up there, make the clip which is to move about the screen. Give it an instance name and place it where it needs to be on the screen. For this example, Im going to going to use the term “ball” again to represent that movieclip.

What this example will cover is clip movement, clip placement and collision detection. The ball will be moved along the grid with its _x and _y properties adjusted accordingly not being allowed to move on any grid space which is restricted. The ball’s current position in the grid will be kept in that movieclip as two variables, x and y. So on to some code. (This is all placed within the clip in which the grid was drawn and the “ball” clip is placed and is based on a 5x5 grid):


// first set up spacing and collisions
spacing = 20;
collisions = [	[0,0,0,0,0],
		 [0,0,1,0,0],
		  [0,0,1,0,0],
		   [1,1,1,0,0],
		    [0,0,0,0,0]];

// set up a function to move the ball to a new grid x,y
// then set the new x, y to the ball's x, y variables
MoveToGridSpace = function(x,y){
	ball._x = spacing * (x + y);
	ball._y = spacing/2 * (x - y);
	ball.x = x;
	ball.y = y;
}

// set up a function to get the ball's grid x,y based on its position
// then assign the new values to the ball's x, y variables
SetGridSpace = function(){
	ball.x = Math.round((ball._x/spacing + 2*ball._y/spacing)/2);
	ball.y = Math.round((ball._x/spacing - 2*ball._y/spacing)/2);
}

// set a function to determine if the grid space x, y is a valid
// space for movement returning true if valid, false if not
IsValidTile = function(x,y){
	if (collisions[x][y] === 0) return true;
	else return false;
}

// set the ball to recognize key presses and move based on them
Key.addListener(ball);
ball.onKeyDown = function(){
	var xmove, ymove;
	if (Key.isDown(Key.UP)){
		ymove = 1;
	}else if (Key.isDown(Key.DOWN)){
		ymove = -1;
	}else if (Key.isDown(Key.LEFT)){
		xmove = -1;
	}else if (Key.isDown(Key.RIGHT)){
		xmove = 1;
	}
	// update new position based on ball's current
	xmove += this.x;
	ymove += this.y;
	if (IsValidTile(xmove, ymove)){
		MoveToGridSpace(xmove, ymove);
	}
}

// additionally we can set the ball to be put where the mouse
// was pressed on the screen.
ball.onMouseDown = function(){
	var mouse_x = Math.round((_xmouse/spacing + 2*_ymouse/spacing)/2);
	var mouse_y = Math.round((_xmouse/spacing - 2*_ymouse/spacing)/2);
	if (IsValidTile(mouse_x, mouse_y)){
		MoveToGridSpace(mouse_x, mouse_y);
	}
}

// update the ball x, y position based on where it was placed
// initially on the screen
SetGridSpace();

// then move the ball to be exact on that grid space
MoveToGridSpace(ball.x, ball.y);

When this all comes together, you get this:
[swf=“http://www.umbc.edu/interactive/flash/tutorials/isometric/flash/iso_example1.swf height=300 width=300”][/swf]
[ Download ]

So whats going on there? First the spacing variable and the grid collisions grid (array) is defined. These are core variables in determining the balls placement here. Next some functions are defined. These are simple functions which relate directly to the ball clip and move it when called. There’s MoveToGridSpace, SetGridSpace, and IsValidTile. Each use the equations earlier mentioned and are apart from that, pretty self explanitory. Following that comes the user interaction setting the ball’s on onKeyDown and onMouseDown actions. When a key is pressed, if the key is an arrow key, a variable, either xmove or ymove is set to represent the direction of movement in the iso grid. Then that value is compounded with the ball’s current position to get the new postion the ball would be if this movement was applied. Following that, using one of the functions defined above, the new grid space is checked with the collisions array to see if it is a valid space (making sure it equals 0). If thats true, then the ball is moved there using MoveToGridSpace. In the onMouseDown, the location of the mouse in the grid is gotten (again, from the equations mentioned earlier) and that position is checked and the ball is moved if valid (this could also be done using dynamically attached tiles with an onRelease action as previously discussed). Following that is just setting the ball up to be exactly on a grid space based on where it was placed on the screen.

Note: the .fla posted earlier is similar though a small step up in difficulty incorporating some MovieClip prototyopes - and actually handling the collision array slightly different, but none the less effective. I personally prefer what I showed above, though that example lays the array visually in code to work with the x and y axis. Unlike this example, that one does not include mouse-click placement though.

Advanced
Ok, time to really get our fingers dirty and build a complex fully featured isometric environment. Ok, not really. It probably won’t be any more complex than what’s above, at least not at first, though it will be set up in a way to be more flexible and easier to throw in more methods once discussed. Before, I said, “…the rest will go faster and with less step by step instruction…” though I dont think I really cohered to that :wink: This time, that statement will be true.

So first off, since we are dealing with a gridded system based on 2 value coordinate locations, we should set up a Point class to allow us to work with those coordinates as a single, solid object. In this Point class will be methods making use of those important isometric equations. Code:


/*¯¯¯¯¯ Point Class ¯¯¯¯¯*/
Point = function(x,y){
	this.x = x;
	this.y = y;
}
Point.prototype.toString = function(label){
	return label + "{x: "+this.x+", y: "+this.y+"}";
}
Point.prototype.toIso = function(trans){
	var xs = this.x/spacing;
	var ys = this.y/spacing;
	var x = (xs + 2*ys)/2;
	var y = (xs - 2*ys)/2;
	if (trans){
		this.x = x;
		this.y = y;
		return this;
	}
	return new Point(x,y);
}
Point.prototype.toScreen = function(trans){
	var x = spacing * (this.x + this.y);
	var y = spacing/2 * (this.x - this.y);
	if (trans){
		this.x = y;
		this.y = x;
		return this;
	}
	return new Point(x,y);
}
Point.prototype.round = function(trans){
	if (trans){
		this.x = Math.round(this.x);
		this.y = Math.round(this.y);
		return this;
	}
	return new Point(Math.round(this.x),Math.round(this.y));
}
/*_____  End Point Class _____*/

toIso takes a screen-based point, such as _x, _y coordinates or a mouse position, and returns the point as it relates to the iso grid. toScreen takes an isometric point and returns that point as it exists on the screen. The trans argument decides whether or not the point being used is itself transformed into the new point or if its just being used to return a new seperate point with the transformations applied. round obviously rounds the point and too includes the trans argument. This would be used to get an absolute grid location from something like a mouse point location, like that which was done in the previous Flash example of placing the ball where the mouse was pressed. Of course, also included is the toString method so that if we ever try to trace a point directly, we’ll get a formatted string displaying the contents of our point object and not “[object Object]”.

As this class is set up, it accesses spacing directly as a free, single variable from that scope. By doing so, there lacks the immediate ability to change that easily based on different grid systems. If you needed to do that, then you might consider developing an isometric grid class to let you create distinctive grid systems and incorporate these functions in that. Again, it all depends on the level of complexity that you plan for yourself and what you intend to achieve. Here, Im assuming a single consistent spacing-based grid which wont change or need to be so complex to have a seperate class to define it with.

Since we’ll need to move movie clips around on the screen based on these points, we should set up our movie clips to be able to handle them a little more easily. Notably, we should be able to tell a movieclip to set its location to a point and have it just happen. With that, we can include a property to allow us to get the mouse location as a point from the scope of that movieclip:


/*¯¯¯¯¯ Movieclip Extending ¯¯¯¯¯*/
getMCLoc = function(){
	return new Point(this._x, this._y);
}
setMCLoc = function(pt){
	this._x = pt.x;
	this._y = pt.y;
}
getMCMouseLoc = function(){
	return new Point(this._xmouse, this._ymouse);
}
MovieClip.prototype.addProperty("loc", getMCLoc, setMCLoc);
MovieClip.prototype.addProperty("mouseLoc", getMCMouseLoc, null);
/*_____ End Movieclip Extending _____*/

Now, to set the position of the movieclip on the screen with a point object, we just use myMc.loc = myPoint; using myMc.loc alone to retrieve the point. myMC.mouseLoc will give the mouse location within the scope of that movieclip as a point. As a quick example of setting a movie clip to grid position (2,3), you can use:

myMc.loc = new Point(2,3).toScreen();

This makes up the basic foundation for movieclip to grid relations through their point locations. Now we can start to encompass more functionality, namely, at this point, collision grids. This really doesnt take much since all we have to do is check within a certain point in an array and see if its a valid value or not:


Array.prototype.valueAt = function(pt, check){
	if (check === undefined) return this[pt.x][pt.y];
	if (check === this[pt.x][pt.y]) return true;
	return false;
}
MovieClip.prototype.moveToTile = function(pt){
	if (collisions.valueAt(pt, 0)) this.loc = pt.toScreen();
}

Next lets implement the key controls, but also include a method to turn the character to face in the direction of movement. This will be based on the key pressed, but instead of stringing everything out in if statements, we can consolidate a little using some math. At this point we also have the option of allowing a character to be moved diagonally on the screen, in both an x and y movement at the same time. That would mean drawing more character positions though, and Im not up for that :wink: Included is not only key detection, but also methods of moving absolutely or relatively. Absolutely we already have, absolutely has yet to be included:


Point.prototype.toFrame = function(){
	return 3 + this.x*2 + this.y;
}
Point.prototype.addTo = function(pt, trans){
	if (trans){
		this.x += pt.x;
		this.y += pt.y;
		return this;
	}
	return new Point(this.x+pt.x, this.y+pt.y);
}
Key.direction = function(){
	var m;
	if (m = Key.isDown(Key.UP)-Key.isDown(Key.DOWN)) return new Point(0,m);
	else if (m = Key.isDown(Key.RIGHT)-Key.isDown(Key.LEFT)) return new Point(m,0)
	else return false;
}
KeyMovement = function(){
	var dir = Key.direction();
	if (dir){
		this.gotoAndStop(dir.toFrame());
		this.moveToTile(dir.addTo(this.loc.toIso().round()));
	}
}

KeyMovement isn’t prototyped because that will just be assigned directly to an onKeyDown. Other than that you have a function attached to the key object to return a point indicating the direction of the arrow keys using 1 and -1 to distinguish between positive and negative movement, and two new Point prototypes, one which adds two points and one which converts the point to a frame number. This is the math I was talking about. What the point.toFrame does is takes that point from the Key function (direction) and converts it to one of 4 frames which the movieclip is then told to gotoAndStop at. Because we have only 4 possible directions, only 4 possible frames can come out of the 4 possible points - and those frames are 1 (left), 2 (down), 4 (up) and 5 (right). 3 is an empty frame though will never get hit unless toFrame is called and both x and y values are 0. This wont happen because of the if (dir) check in Key.movement, however the possibility still exists to use that method to cause a movieclip to go to frame 3 if you need. Though its not the most efficient way to handle the frames (straight if/elses would be faster though like thousandths of a second) its crafty and takes up little space. On top of this we can include another Movieclip prototype to let us know what direction it’s in by its frame number. Ill just use a case statement here though ;):


Point.RIGHT	= function(){ return new Point(1,0); }
Point.LEFT	= function(){ return new Point(-1,0); }
Point.UP	= function(){ return new Point(0,1); }
Point.DOWN	= function(){ return new Point(0,-1); }
MovieClip.prototype.direction = function(){
	switch(this._currentframe){
		case 1: return Point.LEFT();
		case 2: return Point.DOWN();
		case 4: return Point.UP();
		case 5: return Point.RIGHT();
		default: return false;
	}
}

I also included some point functions to return direction points associated with those directions, much like Key.LEFT and Key.RIGHT but since they are objects and not basic values, a function is needed to return a new object of that type and not a reference to the actual Point.LEFT or Point.RIGHT etc.

One last thing before going to bed. There’s still more to cover, most notably fluid movement (not jumping directly from grid space to grid space) and possibly some path finding, but first lets finish up this Point driven example. The last thing is an action grid - setting a space for an action and then initiating that action when the user tries (whatever it is that allows them to do so). So here we go:


Point.prototype.equals = function(pt){
	return (this.x == pt.x && this.y == pt.y);
}
Array.prototype.actionAt = function(pt, args){
	return this.valueAt(pt)(arguments.slice(1));

}

Yeah, thats pretty much it. Basically all you do from this is great a new grid and assign a certain position to be the action for that spot. Then you simply run the action in that grid associated with the players loc when its tried. For example:

actionsGrid.actionAt(myMC.loc);

If no action is set in that grid space in the actionsGrid, nothing happens. What the new Point.equals prototype allows you to do is within your defined function check for the direction of the character to be correct for calling that action. This can be used easily checking to see if the character’s direction equals a preset Point.LEFT or Point.RIGHT etc.

In terms of interaction, things are still on the simple scale. Though this use of the Point class is getting us closer to a well oiled, developed, working isometric machine. Whats above may be altered when other features are brought to the table, though it gives an idea of how things can be set up and organized in terms of a simple class definition and extending the movieclip/array objects.

more to come.

everything would be done in ‘normal’ perspective and translated ‘toIso’ when put on the screen.

I added more but it was cut off with the forum move. I dont remember what I put :frowning: so Ill have to re-do it… I also have another section I was working on pretty much done, I just havent found the inspiration to sit down and write it all out :slight_smile:

ok heres the one I lost. What is it?

Basic Movement in an Isometric Plane
So really, with the foundation Ive created so far, theres no problem at all in translating basic movement in Flash to an isometric plane. Just code normally, then, when positioning on the screen, use the toScreen methods to translate an ‘isometric’ position correctly on the screen.

Along with this, toIso can be used to find out the position of the Clip on the screen and utimately the grid spce it occupies acting as a rudimentary hitTest.

see example:
[swf=“http://www.umbc.edu/interactive/flash/tutorials/isometric/flash/iso_example2.swf height=300 width=300”][/swf]
[ download ]

Since this one has been lingering around since I made it, Im not sure if it uses anything new from what was previously discussed. If so, it’d be pretty basic and comprehenable or else I think I would have taken note to write more about it then (which I dont think I did :wink: )

Oh, pressing and holding the mouse will allow you to control the direction of the ball a la diablo

Depth Sorting
So what do you get when you have a space that goes not only side to side but back and forth as well… having depth? What you get is the nuisance of making sure that you have your movieclips overlap properly as to correctly represent their position in that space. With an isometric plane, that issue arises, especially when you are moving things about crazily as in the previous example. Consider the 3 following characters as movieclips:

As you can see, though each image shows the characters in the exact same locations, the one on the left represents the characters in an arrangement which does not conform to their proper places on the isometric plane, while the rightmost does.

In basic terms, depth sorting is quite easy. All you have to do is realizes that the top most objects in an isometric plane are those which are closer to you, which, visually, translates to those objects which are further down the screen.

This means those movieclips with the highest _y value get the highest depth and are told to be above those with the lower depths. The easiest way to do this is simply using swapDepths on a movieclip with that movieclip’s _y as the input value.

this.swapDepths(this._y);

The lower the clip is on the screen, the higher its _y value hense the higher its depth therefore making it above all the clips which are technically behind it.

This technique is not without its flaws. There are some things to know about depths in Flash that will help complicate how you handle depth positioning and may make you wish it was always as simple as this.swapDepths(this._y);

[list]
[] You can only swapDepths between -16384 and 2130690045
[
] swapping outside the range of 0 to 1048575 will make the movieclip unable to be removed (unless swapped back)
[] swapping depths at a currently occupied depth will switch the depths of the two clips - clips cannot share the same depth
[
] creating or attaching a new clip within the depth currently occupied by a movieclip will replace that old clip with the newly created or attached clip
[*] swapDepths cannot swap between different parent clips. If a clip is within one movieclip, swapDepths can not swap it to be within another
[/list]

These arent so bad right? Not really. The worst ones are the 3rd and 4th where you have to worry about clips swapping each other out to the other’s last depth and newly placed clips completely killing your existing clips.

When dealing with an isometric plane, you’re dealing with many different positions which are set at a rotated and scaled perspective. This rotation is 45 degrees making diagonal spaces lined up with each other horizontally on the screen

What this means is that the center point of each one of those squares - the point at which a movieclip would be placed if occupying that space, all have the same _y position as the diagonal (seen horizontally) that they’re in.

So what happens when you have certain objects within that same horizontal? They are all going to replace each other’s depths and only one clip (the last swapped clip) will actually be occupying that arrangement. This is a problem. Why? Well, what if you are attaching 5 clips to be placed on those locations dynamically? Well, first off, you would have to attach them at some errant depth as to prevent attaching them over and therefore replacing any currently existing clips. Then position them into those grid spaces and then use swapDepths to arrange. However, in swapping depths, after the first is set, the second one replaces it, causing the first one to go into the second one’s original attached depth who knows where… then the third does the same to the second, then the 4th comes in followed by the 5th which is the only one which gets swapped properly leaving the other 4 back to the original attaching depths!

What to do? One solution is taking into account the horizontal positioning and figure that into the depth swapping. Here, instead of each depth being swapped through by _y positioning, it would be based on both the _y and _x which, in the aforementioned example, would leave each of those clips with seperate depths. So in doing that, you have to take into account the maximum width of your isometric plane and make each _y postion contain that many depths between them (for eacy _x position). Then, assuming the leftmost _x is 0, you get

this.swapDepths(this._x + this._y*isoPlane_width);

Since at its max, the _x will only reach the isoPlane_width and never interfere with the next step up in the _y positioning.

Now, when it comes to 3D swapping in isometry, you then have to worry about that extra z plane of possible depths - where then, another factor comes into play and your possible depth positions start to sky-rocket :hair:

some basic 3D movement will be covered next.