[Game Programming Galaxy]
[Logo]
[Design]
Isometric Views
Explanation and Implemention
Tile and Sprite drawing in an Isometric View, Second Edition
By Jim Adams of Game Developers Network, Inc. (Jun 7,1996).
Copyright ⌐ 1996 by Jim Adams, All right reserved.
Graphics Illustrations by Lennart Steinke (Sep 1997)
The author, Jim Adams, gives full permission to duplicate this file only
for personal use. No part of this file may be published without prior
written permission by the author.
Notes:
Isometric can be defined as a multitude of view angles, but we are
discussing the one made popular from games like Ultima Pagan and XCOM (to
name a couple). All examples are not optimized for speed, but in a way to
easily understand the concept. All improvements are left up to the reader.
Please do not flood me with mail on how to improve the tile drawing
routines and such, as I already know how to.
This file has an acompanying .ZIP file (ISO_SRC.ZIP) that contains the
Isometric drawing engine with a sample program using it. This also contains
some great libraries that you can compile using either BORLAND or WATCOM.
(See 'library.txt' in LIBRARY.ZIP)
If you don't already know about tiled graphics, here it is in a nutshell.
Sections of pixels, usually a rectangle, compose a tile, much like a floor
tile. When you place these tiles together, they form a pattern. It is
possible to take a tile with a brick pattern and put them together to
create a bigger tile pattern.
So instead of storing raw bitmaps, you just use a map array to store the
number of the tiles to draw to form the bigger picture. A typical drawing
function would start at the top-left corner of the screen, moving right
until the right edge is reached, then moving down a row to start again.
No on to the Isometric view type. Instead of using rectangular tiles, they
are angled. When you draw them, instead of x going left to right and y
going top to bottom, x now goes down-right and y goes down-left. The map is
still left to right as x, top to bottom as y. Take a look:
[Image]
Now remember our display (video screen) is still rectangular, so a typical
scene would look something like:
We achieve the view by using angled tiles. These tiles have width,
height and depth. As the viewing angle depends on the width and
height of the tile (which give us depth), we need to draw them using a
certain ratio. So depth is not involved in the drawing, as we only need to
worry about width and height.
A good angle to view uses a 2:1 ratio. This means for every two horizontal
pixels drawn, there is one vertical pixel. We'll actually be using a 2.1:1.
Our tile width will be 32, so we quickly figure our height is 32/2.1=15.23.
So our final tile dimensions are 32x15.
This is the 'base' tile size with a height of 1. Remember, our tiles can
have different heights. So a wall may be 32x90. The height doesn't change
anything, but the width must stay as 32.
Let's take a look at the tile shape (in pixels):
1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
----------------------------------------------------------------
1| O O O O
2| O O O O O O O O
3| O O O O O O O O O O O O
4| O O O O O O O O O O O O O O O O
5| O O O O O O O O O O O O O O O O O O O O
6| O O O O O O O O O O O O O O O O O O O O O O O O
7| O O O O O O O O O O O O O O O O O O O O O O O O O O O O
8| O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O
9| O O O O O O O O O O O O O O O O O O O O O O O O O O O O
10| O O O O O O O O O O O O O O O O O O O O O O O O
11| O O O O O O O O O O O O O O O O O O O O
12| O O O O O O O O O O O O O O O O
13| O O O O O O O O O O O O
14| O O O O O O O O
15| O O O O
If you play with the shape a bit, you'll notice they piece together easily.
Just draw one, go right 16 pixels, down 8, and draw another. But how do you
know what block to draw where? Take a look at the image below, this time
the map x and y cordinates are added: (top number is x, bottom is y):
[Image]
Now it may seem like we want to draw down and right, but no. This is not
the best way to do this. In fact, we still want to drawn left to right, top
to bottom. What? I thought you said not to do it this way.
Well, it's a bit different. When we draw left to right, our tiles are
spaced 32 pixels apart. As we move top to bottom, we only move 8 pixels at
a time. Every other row, we pre-step 16 pixels left to make them piece
together correctly.
So we'll draw the screen like this: (the numbers are the order in which the
tiles are drawn)
[Image]
Because some of the tiles can be 'cut' be the edges of the screen, we clip
them. You'll notice every other row we are drawing one more tile. This is
because these are the tiles pre-stepped left and we need to compensate for
this.
Got it? While it's easy to draw like this, we certainly can't update the
map cordinates this way. So how do we do it? Well, take a quick look back
at the zoom in with the map cords. Watch the x and y cords as you move
right. You'll see that the x is increase by one and the y is decreased by
one for every tile. It's a bit different for top to bottom, since we are
pre-stepping every other vertical tile.
We will need to alter the addition of the x and y for every other vertical
tile. What this means is if the vertical tile counter is even, we increase
the map x when we move down. If the vertical tile counter is odd, we
increase the map y when we move down.
So now we know how to draw the screen and how to track the map cordinates
for each tile drawn. Now the hard part, putting this to work in a program.
Using your favorite map storage method (fixed array, variable array, link
list, etc) we'll create a simple drawing function. For ease of explanation,
I'll use a fixed array.
We'll use a 10x10 map array, with the ability to stack tiles on one another
each with a different height. This gives us the ability to combine graphics
tiles to create new ones.
For instance we want a wall and a wall with a candle. Instead of create two
wall graphics tiles, we create one wall and one with a candle. Now you just
draw the wall, then draw the candle on top of it. If you want the candle on
something else, just draw over it.
So we need to set aside an array that holds the number of different tiles
and heights. In C this would be:
struct MAP_STRUCTURE {
char num_tiles;
char tiles[10]; // assuming a max of 10 tiles per map cord
char height[10]; // also assuming a max of 10
};
and an array for our map:
MAP_STRUCTURE map[10][10];
We want three different objects in the map:
0) grass
1) wall
2) a tall wall
Look at our sample map:
0 1 2 3 4 5 6 7 8 9
0 O O O O O O O O O O
1 O . . . . . . . . O
2 O . . . . . . . . O . = grass (0)
3 O . . o o o o . . O o = wall (1)
4 O . . o . . o . . O O = tall wall (2)
5 O . . o . . o . . O
6 O . . o o o o . . O
7 O . . . . . . . . O
8 O . . . . . . . . O
9 O O O O O O O O O O
Now we have to define how the graphics tiles look. Well, the grass is
easiest, just a 16x15 tile drawn with a grass pattern. The wall is a tile
16x50. Since we're going to use a stacked method of drawing, the tall wall
will be two walls, one higher than the other.
So now we put the data in our map array as: (tile 0 = grass, tile 1 = wall)
map[0][0].num = 2;
map[0][0].tile[0] = 1;
map[0][0].height[0] = 0;
map[0][0].tile[1] = 1;
map[0][0].height[1] = 50;
...
map[1][1].num = 1;
map[1][1].tile[0] = 0;
map[1][1].height[0] = 0;
...
And you get the idea. Our drawing loop will now go through each array in
the map, using num to draw that many tiles there.
Also, since every tile can be a different size for both height and width,
we need to create a handle position that is the same for all tiles. The
bottom-right corner would do just fine. Just subtract the width and height
from the screen x and y position before drawing it.
In C, it would look something like:
(NOTE: This is not an Isometric drawing method. It's just to get
you to understand the stacked drawing method.)
for(i=0;i<10;i++) {
for(j=0;j<10;j++) {
for(k=0;k