______ ____ _____ __ __ ____ _____ ____ ______ _____ /_ __/ / ___/ / __ / / /_/ / / __/ /_ _/ /__ / /_ __/ / ___/ / / /_/__ / / / / / / / / /_ / / //_// / / / / _/ /_ ___/ / / /_/ / / / / / / /_ / / / __ \ _/ /_ / /__ /____/ /____/ /_____/ /_/ /_/ /___/ /_/ /_/ /_/ /____/ /____/ __ __ ______ ___ _ _ ____ / / / / /_ __/ / __/ // // / __/ / /_/ / / / / /_ // // /_/__ \ / _/ /_ / /_ / // / __/ / \/ /____/ /___/ /_/\_/ /____/ Isometric Views Explanation and Implemention. Second Edition :) Tile and Sprite drawing in an Isometric View. By Jim Adams of Game Developers Network, Inc. (Jun 7,1996). Copyright (c) 1996 by Jim Adams, All right reserved. 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 means a multitude of view angles, but we are discussing the one made popular from games like Ultima 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. All text in this was typed with a mono-spaced editor (such as edit.com). Certain programs adjust the width of the font spacing so the 'graphics' I typed will not look correct. If you're using Windows, please select a proper font to view it. 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: (The x and y are map cords) Rectangular: Isometric: - X - 0 0 0123456789 / 1 * 1 \ 0 ********** Y 2 * * 2 X | 1 * ** * / 3 * * 3 \ Y 2 * **** * 4 * * 4 | 3 * ** * 5 * * 5 4 * * * * 6 5 ********** * * * * 7 * * * * * 8 * * * * 9 * * * * * * * * * * * Now remember our display (video screen) is still rectangular, so a typical scene would look something like: ------------------------ | \ Grass / | | \ / | | \ / | (slants show angle of tiles) | \/ Water | | Sand \ | | \ | ------------------------ 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 ( From now on, when we draw, I'll represent a tile like: /\ ) ( \/ ) 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. So, a screen drawn like this would look like: +-----------+ |/\/\ | |\/\/\ | |/\/\/\ | |\/\/\/\ | |/\/\/\/\ | |\/\/\/\/\ | |/\/\/\/\/\ | +-----------+ But how do you know what block to draw where? Take a look at the screen again, this time zoomed in with the map x and y cordinates added: (top number is x, bottom is y) +-------------------+ | /\ | | / 0 \ | |/\ 0 /\ | Note: | 0 \/ 1 \ | | 1 /\ 0 /\ | / \ |\/ 1 \/ 2 \ | Y /\ X | \ 1 /\ 0 /\ | / /\/\ \ | 1 \/ 2 \/ 3 \ | /\/\/\ | 2/ \ 1 /\ 0 / \ | \/\/\/ +-------------------+ So it looks like this in a map: -X- | 0 1 2 3 Y 1 X X X | 2 X X X 3 X X X 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) +-----------------+ |0 /\ 1 /\ 2 /\|3 | / \/ \/ | 4|/\ 5 /\ 6 /\ 7 | 8 | \/ \/ \/| |9 /\ 10 /\ 11 /\|12 |\/ \/ \/ | 13|/\14 /\15 /\16 |17 | \/ \/ \/| |18 /\19 /\20 / |21 +-----------------+ 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 like: \ 0,0 \ MAP CORDS: 1,0 <---- increase x / 1,1 <-- increase y \ 2,1 <---- increase x / 2,2 <-- increase y 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 max_layers) max_layers = map[i][j].layer; } } } current_layer++; if(current_layer >= max_layers) break; } Now all you have to do is link in the map cordinate and screen cordinate of the sprite you want to draw on a certain layer and draw it. You can do this by comparing the current tile cord being drawn with the sprites map x and map y: (spritex & y are map fine cords of sprite to be drawn) (mx and my are the current map cord that is being drawn) if(mx == sprite_x / 16 && my == sprite_y / 16) { Then you just offset the sprite before drawing it at that position: xo = sprite_x & 15; yo = sprite_y & 15; xx = xo - yo; yy = (xo/2) + (yo/2); block_draw(sprite_num,screenx-32+xx,screeny-16+yy); Well, I'll leave you now to your newfound knowledge of Isometric views. If you have any questions or comments, please EMAIL or snail mail me. Jim Adams Game Developers Network, Inc 1200 N Lamb Ste#124 Las Vegas, NV 89110 EMAIL: tcm@accessnv.com