# Designing Isometric Game Environments

SP 96: Designing Isometric Game Environments

Tile-based games have been around almost as long as two-dimensional (2-D) arrays. I can remember, for instance, sitting in front of a Commodore 64, carefully maneuvering a tiny "pixelesque" humanoid from square to square, thinking, "Hey, this is great!"

As computer games became more sophisticated, however, simple, top-down, tile-based games gave way to bigger and better things. Why? Because someone decided to look at things from a slightly different angle. The emergence of the isometric point of view took tile-based games somewhere they had never been before--into the third dimension. After playing through a prime example of this isometric ingenuity (Microprose's award-winning X-com), I decided to create my own multilevel, isometric environment. As Figure 1 shows, the Axoview Engine 1.0 is the result of my efforts. In this article, I'll describe how I designed the engine, and provide an overview of its inner workings.

### Putting Things in Perspective

You need to keep two things in mind when designing an isometric layout:

• Ease of visibility.
• Ease of putting things together.
Of all the isometric possibilities, the axonometric perspective provides excellent vision clarity and allows for each cube to be easily broken down into unique components; see
Figure 2(a). The multilevel, tile-based world is constructed entirely from axonometric cubes called "cell blocks." As Figure 2(b) illustrates, each cell block is composed of separate tiles:

• The base tile, which serves as a floor or ceiling.
• X- and y-wall tiles, for walls, fences, and the like.
• The prop tile, for stationary objects such as trees and chairs.

### Getting Coordinated

Cell blocks are arranged side-by-side on a 2-D grid, bearing a strong resemblance to a top-down tile layout. The cell-block grid, however, is rotated at 45 degrees. Now the origin of the rotated grid lies at the top-most (farthest) corner, and the x- and y-map grid axes increase downward and to the right or left, respectively, as in Figure 3. Therefore, the x-wall of each cell block is the wall players come in contact with as they traverse the grid in the x-direction; likewise for the y-wall. This Cartesian gridmap of cell blocks is referred to as "map coordinates."

To locate various positions within each cell block, the map grid is further broken down into a uniform number of virtual partitions per cell block. For practical reasons, the number of partitions must be an integral power of 2. These virtual partitions are referenced using world coordinates. Consequently, each cell block has a local coordinate system (with the origin in the top corner of the cell block) created by the world-coordinate partitions. This "grid within a grid" is referenced using "cell coordinates" (see Figure 3).

Finally, a familiar coordinate system must be defined. The grid of pixels that makes up the screen display will be referred to as "screen coordinates."

### Base Tile Design

The base tile is most important because it is directly related to the layout of the cell-block grid. Its design hinges on two crucial choices: edge slope and edge length. For standard 320x200 VGA displays, a slope of 0.5 pixels produces the best results. This particular slope creates a series of two-pixel steps which go down and right in the x-direction, and down and left in the y-direction. The "length" of each edge is simply the number of two-pixel steps per edge. This length must correspond to the number of virtual cell-block partitions chosen earlier.

By connecting the ends of four edges to form a diamond (the base tile), a graphic representation of the local cell-coordinate grid is formed; see Figure 4(a). Subsequently, by aligning adjacent base tiles so that the edges transfer smoothly in both the x- and y-directions (no disturbance in edge slope), a graphical representation of the rotated cell-block (map) grid is created. This also reveals an extra edge of "padding" along the bottom half of each base tile; see Figure 4(c). This padding completes the design of the base tile.

The alignment of base tiles also provides several important cell-block dimension constants defined in Listings One and Two. Figure 4(b) illustrates these dimensions.

### Wall Tile Design

It should be noted that the base tile's edges of padding form slots between adjacent base tiles. This is a convenient place for some walls. The width of each wall tile is a function of the base tile width: wall width = (base tile width/2)+2. The height of the wall must be an even number; choose the height with user visibility in mind.

### Prop Tile Design

Once the base and wall tiles are designed, the prop-tile dimensions are straightforward. The width of the prop tile is simply the same width as the base tile. The prop-tile height is equivalent to the height of the connected base and wall tiles decremented by 1.

### Leveling Things Out

Multiple levels will be dealt with by stacking several identical cell-block grids directly on top of one another. The base tiles of one level should rest comfortably on the wall tiles of the level below. It should be noted that the base tile of a certain cell block will serve as the ceiling tile for the cell block directly below it.

### Basic Engine Structures: The Tile Maps

With the abstract design of the engine out of the way, how do you represent the axonometric layout in memory?

### The Tile Class

Not surprisingly, the Axoview Engine declares four instances of the tile class: base_tiles, xwall_tiles, ywall_tiles, and prop_tiles (see Listing One). To declare an instance of the Tile class, the class constructor requires three arguments: the number of tiles, the width of each tile, and the height of each tile. The constructor calculates the size of each bitmap (widthxheight), and, more importantly, allocates memory for each of the tile bitmaps (see Listing Four).

But how do the bitmaps make it to the big screen? The Tile class' member function blit() takes care of everything. You just need to be sure to pass it three things: the tile identification number, screen coordinates (where you would like the bitmap displayed), and a pointer to the off-screen buffer on which it should do the blitting. How exactly does the blit() function blit? The typical way of rendering a tile-based display would be to use the painter's algorithm. The painter's algorithm draws the tiles back to front, so that the tiles in the foreground cover the tiles beneath them (much like a painter would paint a picture). Standard bitmap transparency also would be applied, where each byte of the bitmap would be checked for transparency (usually a value of 0). If the byte were "transparent," it would not be drawn to the off-screen buffer. While this method works, it can be highly inefficient, drawing several levels of cell-block tiles on top of each other, even though many of the tiles would not be seen at all. It turns out that there is a way to draw only what can be seen. I refer to it as the "reverse painter's algorithm."

### The Reverse Painter's Algorithm

The reverse painter's algorithm (RPA) eliminates the blitting of "covered over" portions of tile bitmaps--only the pixels that will be seen in the end are drawn. The algorithm can be broken into several steps. First, fill the entire off-screen buffer (destination viewport) with a string of transparent (zero) values as quickly as possible. Second, calculate the order in which the tiles would be drawn using the normal painter's algorithm and reverse it (draw tiles in the foreground first). The cell-block tile-display order becomes: prop, x-wall, y-wall, base. To draw from front to back according to the RPA, the top-most level of cell blocks should be drawn first. Therefore, draw each level from left to right across the screen, starting with the bottom-most row, and ending with the top-most row. A significant problem still remains to be addressed, however--can the blit() function keep the later-drawn background tiles from copying over the foreground tiles?

### RPA-Style Drawing with Tile::blit()

To preserve speed, the blit() function performs all clipping outside of the main blitting loop. It first clips the edges of the tile bitmap to the viewport, adjusting the starting and ending coordinates appropriately, and calculates the necessary blitting offsets. It then loops through the unclipped portion of the tile bitmap and copies the "permissible" bytes to the off-screen buffer.

It is here that the "magic" takes place. Instead of checking each byte of the bitmap (the source byte) for transparency, the blit() function checks the corresponding byte in the off-screen buffer (the target byte) for transparency (recall that the off-screen buffer was initially filled with transparent values). If the target byte is transparent (nothing has been drawn there yet), the blit is deemed "permissible" and the function copies the source byte to the target byte, as in Figure 5. The only drawback to this method is that transparent source bytes may be needlessly copied over transparent target bytes. This drawback is negligible, however, when compared to the inefficiency of copying hundreds of tiles on top of one another. Listing Four presents the full definition of Tile::blit().

### It Was a Time to be Rendered

The next step involves rendering the multilevel cell-block environment. The engine function draw_tilesRPA() assumes full responsibility for this task. The function draw_tilesRPA() requires five arguments:

• The pair of world coordinates around which the display should be centered.
• The level that will serve as the top-most level.
• The level around which the display should be centered.
• A pointer to the off-screen buffer.
However, before draw_tilesRPA() can begin the process of rendering the layout to the screen, we need a method for converting world coordinates to screen coordinates (and vice versa).

### Going from World to Screen, and Back Again

To convert from world coordinates to screen coordinates, you need to compare the local cell coordinates of a base tile with its local screen coordinates; see Figure 6. This comparison shows that a change of +1 along the cell-coordinate x-axis is equivalent to the following screen-coordinate changes: +2 in the x-direction, and +1 in the y-direction. Table 1 lists similar comparisons.

These comparisons suggest the pair of equations in Example 1(a) for converting from a change in world (or cell) coordinates to a change in screen coordinates. Solving this pair of equations for the change in world (or cell) coordinates yields the pair of equations in Example 1(b), which convert from a change in screen coordinates to a change world coordinates. With these conversions at the ready, the rendering process may begin.

### Rendering with Function draw_tilesRPA()

Because the rendering process starts on the top-most level (not necessarily the current level), the world intersection point must be adjusted to compensate for the difference in screen height between levels. This change along the screen y-axis is equivalent to the distance (in pixels) from the top of a wall tile to the top of its connected base tile (there is no change along the x-axis). Using the conversion formulas again, the equivalent level change in world coordinates is added to the world intersection point for each level that needs to be drawn above the current level. This centers the display around the current level.

### The Main Rendering Loop

Next, the local cell coordinates of the world intersection point are calculated using the same binary-masking technique. However, in this case, a different power of 2 needs to be clipped. Therefore, the cell-coordinate mask is defined as Cell Coordinate Mask = ( # of cell partitions - 1 );. The world-to-screen conversion formulas are then used to find the screen offsets of the calculated cell coordinates. These screen offsets, once adjusted to the viewport, yield the screen coordinates of the intersection block.

The function next finds the map and screen coordinates for the starting block (the place where the rendering process will finally begin). The starting block is found by dropping down a certain number of cell blocks from the intersection block to account for the fact that walls and props of off-screen base tiles may still be visible. Hence, the higher you make the walls, the farther down you have to move the starting block; see Figure 7.

The time to render has finally come. Using the starting block as a starting point, the tiles for each cell block are drawn from left to right (according to the RPA). Each tile is drawn through a call to the appropriate Tile::blit(), after getting the tile-identification number from the appropriate tile map. After each row is drawn, the starting block is moved alternatively up and to the left and up and to the right, as in Figure 7. Once all the rows that fit into the viewport have been drawn, the process is done for the current level. The world intersection point is then adjusted for the next level down, and the main rendering process continues until all the levels have been drawn. That's all there is to it! The Axoview Engine is ready to display your worlds!

### Conclusion

You can fill the tile maps with level layouts, fill the Tile class bitmaps with artwork, and create axonometric, multilevel environments (with applications beyond just game worlds). Moveable characters (and objects) could be implemented by incorporating an object map (with the same dimensions as the tile maps) that contains a linked list to all the objects currently in a cell block. These characters could then be moved around the world using standard, cell-oriented collision-detection techniques.

Although the Axoview Engine was written specifically for Mode 13h in DOS (assembly-language files are available electronically; see "Availability," page 3), it can easily be ported to any platform--in some cases with just a few changes to the Tile class' blitting function. Portability aside, however, a multitude of possible projects stem from the Axoview Engine as it now stands. Animated tile code could be thrown into the blitting algorithm, map rotation could be incorporated to allow the user to change the perspective, and so on. The possibilities are limited only by your imagination.

#### Example 1: (a) Equations for converting from a change in world (or cell) coordinates to a change in screen coordinates; (b) equations which convert a change in screen coordinates to a change in world coordinates.

```(a)
dX(screen) = 2*dX(world) - 2*dY(world)
dY(screen) = dX(world) + dY(world)

(b)
dX(world) = [ dX(screen) + 2*dY(screen) ] / 4
dY(world) = [ 2*dY(screen) - dX(screen ) ] / 4```

#### Table 1: Cell-coordinates-to-screen-coordinates conversion.

```Change in Cell          Change in Screen
Coordinates          Coordinate

dX     dY     dX     dY

+1     0     +2     +1

0     +1     -2     +1

+1     +1     0     +2

+1     -1     +4     0

-1     +1     -4     0

-1     -1     0     -2

-1     0     -2     -1

0     -1     +2     -1

```

#### Listing One

```/********************************************************************
AXOVIEW.H
Header file for the Axoview Engine v1.0
Contains engine constants, global data structures, and function prototypes.
Written by Nate Goudie  1996
********************************************************************/
// Defines for Tile Totality Constants
#define  BASE_COUNT        7        // Number of base tiles
#define  WALL_COUNT        5        // Number of wall tiles
#define  PROP_COUNT        3        // Number of prop tiles
// Defines for Tile Bitmap Dimension Constants
#define  BASE_WIDTH        30       // Width of base tile
#define  BASE_HEIGHT       16       // Height of base tile
#define  WALL_WIDTH        17       // Width of wall tile
#define  WALL_HEIGHT       28       // Height of wall tile
#define  PROP_WIDTH        30       // Width of prop tile
#define  PROP_HEIGHT       33       // Height of prop tile
// Defines for Viewport Dimension Constants
#define  FULL_SCREEN_SIZE  64000    // Size of a full screen buffer
#define  VIEWPORT_X_START  0        // Starting X coord of viewport
#define  VIEWPORT_X_END    319      // Ending X coord of viewport
#define  VIEWPORT_Y_START  0        // Starting Y coord of viewport
#define  VIEWPORT_Y_END    199      // Ending Y coord of viewport
#define  VIEWPORT_WIDTH    320      // Horizontal width of viewport
#define  VIEWPORT_HEIGHT   200      // Vertical height of viewport
// Defines for Map Dimension Constants
#define  LEVEL_MAX         4        // Maximum number of levels
#define  XMAP_MAX          50       // Maximum X dimension of map
#define  YMAP_MAX          50       // Maximum Y dimension of map
#define  SCROLL_STEP       2        // World coordinate scroll step
#define  MAX_CELL_UNITS    8        // No. of cell units per map unit
#define  X_VPTM_OFFSET     10       // Viewport-to-map X offset
#define  Y_VPTM_OFFSET     90       // Viewport-to-map Y offset
// Defines for Cell Block Dimension Constants
#define  CELL_START_X      14       // Init X for cell -> screen op
#define  CELL_START_Y      0        // Init Y for cell -> screen op
#define  CELL_FULL_WIDTH   32       // X of cellA - X of adj cellB
#define  CELL_HALF_WIDTH   16       // Half the full width
#define  CELL_FULL_HEIGHT  16       // Height of base tile
#define  CELL_HALF_HEIGHT  8        // Y of cellA - Y of next cellB
#define  XWALL_XOFFSET     -1       // X offset for the cell's X-Wall
#define  YWALL_XOFFSET     +14      // X offset for the cell's Y-Wall
#define  WALL_YOFFSET      -20      // Y offset for the cell's walls
#define  PROP_YOFFSET      -18      // Y offset for the prop tile
#define  LEVEL_ADJUST      10       // Offset to move up/down a level
// Defines for view rectification of walls
#define  START_YS_OFFSET   32       // Initial Y screen offset
#define  START_XS_OFFSET    0       // Initial X screen offset
#define  START_YM_OFFSET    2       // Initial Y map offset
#define  START_XM_OFFSET    2       // Initial X map offset
// Declare global data structures
unsigned char palette[256*3];   // array for the color palette
// Declare arrays for the tile maps
unsigned char base_map[LEVEL_MAX][XMAP_MAX*YMAP_MAX];
unsigned char xwall_map[LEVEL_MAX][XMAP_MAX*YMAP_MAX];
unsigned char ywall_map[LEVEL_MAX][XMAP_MAX*YMAP_MAX];
unsigned char prop_map[LEVEL_MAX][XMAP_MAX*YMAP_MAX];
// Declare instances of the Tile class
Tile  base_tiles(BASE_COUNT, BASE_WIDTH, BASE_HEIGHT),
xwall_tiles(WALL_COUNT, WALL_WIDTH, WALL_HEIGHT),
ywall_tiles(WALL_COUNT, WALL_WIDTH, WALL_HEIGHT),
prop_tiles(PROP_COUNT, PROP_WIDTH, PROP_HEIGHT);
// Function prototypes
void draw_tilesRPA(int x, int y, int top_level, int current_level,
unsigned char far *screenbuf);
```

#### Listing Two

```/********************************************************************
AXOVIEW.CPP
The Axoview Engine v1.0. An engine which renders free scrolling tile-based
environments in axonometric perspective. Main program and engine functions.
Written by Nate Goudie  1996
********************************************************************/
#include    <stdio.h>
#include    <dos.h>
#include        <conio.h>
#include    <alloc.h>
#include    <mem.h>
#include        <iostream.h>
#include        <process.h>
#include    "tile.h"
#include    "screen.h"
#include        "axoview.h"
void main()
{
clrscr();
// Allocate memory for the offscreen buffer
unsigned char far *screenbuf;
screenbuf=new unsigned char[FULL_SCREEN_SIZE];
// Get old video mode number
int oldmode=*(int *)MK_FP(0x40,0x49);
// Create pointer to video memory
char far *screen=(char far *)MK_FP(0xa000,0);
// Clear video memory ( set each byte to 0 )
memset(screen, 0, FULL_SCREEN_SIZE);
// Call function to load files into memory, exit on error
// Call assembly routines to set graphics mode and palette
setgmode(0x13);          // Set mode to 13h
setpalette(palette);     // Set VGA palette
// Initialize starting variables
int bye=0;                   // Exit program flag
int x=0,y=0;                 // Initial world coord position
int tlevel=(LEVEL_MAX-1);    // Top level to display
int clevel=0;                // Current level position
// Main rendering loop
do
{
// Clear the offscreen buffer: 1st Step in RPA
memset(screenbuf, 0, FULL_SCREEN_SIZE);
// Render the axonometric tile display
draw_tilesRPA(x, y, tlevel, clevel, screenbuf);
// Copy offscreen buffer to the screen
memmove(screen,screenbuf,FULL_SCREEN_SIZE);
// Check to see if user hit a key, and process if so
if ( kbhit() )
{
int xmap, ymap;
char ch;
ch=getch();
switch (ch)
{
case 0:
ch=getch();
switch (ch)
{
// Left Arrow Key - shift map to the right
case 'M':
x+=SCROLL_STEP;
if (xmap>(XMAP_MAX-1)) x-=SCROLL_STEP;
break;
// Right Arrow Key - shift map to the left
case 'K':
x-=SCROLL_STEP;
if (xmap<0) x+=SCROLL_STEP;
break;
// Up Arrow Key - shift map downward
case 'P':
y+=SCROLL_STEP;
if (ymap>(YMAP_MAX-1)) y-=SCROLL_STEP;
break;
// Down Arrow Key - shift map upward
case 'H':
y-=SCROLL_STEP;
if (ymap<0) y+=SCROLL_STEP;
break;
}
break;
case  27:
bye=1;
break;
}
// Clear the keyboard buffer (eliminates backup)
while(kbhit()) ch=getch();
}
}
while(!bye);
// Restore the old video mode and clear the screen
setgmode(oldmode);
clrscr();
return;
}
/*******************************************************************
Function:  draw_tilesRPA();
Purpose:   Renders the axonometric tile display using the
"Reverse Painter's Algorithm"
Arguments: x, y          - pair of world coordinates on which
display should be centered
top_level     - topmost level to display
current_level - level on which display should be
centered
screenbuf     - pointer to offscreen buffer
Comments:  The destination viewport (screenbuf) must be cleared
(set to 0 values) before this function is called
*******************************************************************/
void draw_tilesRPA(int x, int y, int top_level, int current_level,
unsigned char far *screenbuf)
{
// Add viewport-to-map offsets corresponding to the distances
// from the center to the lower left corner of the viewport
x+=X_VPTM_OFFSET;
y+=Y_VPTM_OFFSET;
// Adjust world position to compensate for levels which must
// be drawn above the current_level position
for ( int k=current_level; k<top_level; k++ )
// Begin rendering process, starting with topmost level
for ( int level=top_level; level>=0; level-- )
{
// Calculate map location (map coordinates)
// Calculate location within cell (cell coordinates)
// Set up initial values for conversion from cell
// coordinates to screen coordinates
int xpos=CELL_START_X;
int ypos=CELL_START_Y;
// Calulate x and y screen coordinates using the
// cell-to-screen conversion formulas
xpos+=(2*cellx-2*celly);
ypos+=cellx+celly;
// Adjust screen coordinates to the viewport
xpos=(VIEWPORT_X_START-xpos);
ypos=(VIEWPORT_Y_END-ypos);
// Make adustments to the map and screen coordinates
// (go below the viewport) to ensure that the walls/objects
// of offscreen base tiles may still be visible (drawn)
mapy+=START_YM_OFFSET;
mapx+=START_XM_OFFSET;
xpos+=START_XS_OFFSET;
ypos+=START_YS_OFFSET;
// Set initial x increment value
// Hold on to starting values of map coordinates
int mxhold=mapx, myhold=mapy;
// Create temporary holding variables for the screen position
int tempx=xpos, tempy=ypos;
// Loop through and draw tiles: left->right, bottom->top
do
{
mapx=mxhold; mapy=myhold;     // Set to starting map values
tempx=xpos;                   // Get starting x value for run
do
{
// Check if current map position is in bounds, if not, skip
if ((mapx>=0)&&(mapx<XMAP_MAX)&&(mapy>=0)&&(mapy<YMAP_MAX))
{
// Calculate current map coordinate location
int loc=(XMAP_MAX*mapy)+mapx;
// Get tile index values from the tile maps
int prop_index=prop_map[level][loc];
int xwall_index=xwall_map[level][loc];
int ywall_index=ywall_map[level][loc];
int base_index=base_map[level][loc];
// Blit cell block tiles to screen in "reverse" order
// if not of index 0 ( indicates nothing there )
// 1st=Prop, 2nd=Xwall, 3rd=Ywall, 4th=Base
if(prop_index) prop_tiles.blit(prop_index-1,
tempx, tempy+PROP_YOFFSET, screenbuf);
if(xwall_index) xwall_tiles.blit(xwall_index,
tempx+XWALL_XOFFSET, tempy+WALL_YOFFSET,
screenbuf);
if(ywall_index) ywall_tiles.blit(ywall_index,
tempx+YWALL_XOFFSET, tempy+WALL_YOFFSET,
screenbuf);
if((base_index)||(level==0)) base_tiles.blit(base_index,
tempx, tempy, screenbuf);
}
tempx+=CELL_FULL_WIDTH;  // Incr x position for next x move
mapx+=1; mapy-=1;        // Incr map values for next x move
}
while ( tempx <= (VIEWPORT_X_END+1) );
tempy-=CELL_HALF_HEIGHT;   // Move y position up for next row
myhold-=1;             //   decrease starting y map pos
else                       // If xadd is negative,
mxhold-=1;             //   increase starting y map pos
}
while ( tempy >= (VIEWPORT_Y_START-CELL_FULL_HEIGHT) );
// Adjust world coordinates in order to draw the next level
}
return;
}
/*******************************************************************
Arguments: none
returns 0 if error is encountered
*******************************************************************/
{
int j;
FILE *in;
if ((in = fopen("pics.wad", "rb")) == NULL)
{
fprintf(stderr, "Cannot find file: PICS.WAD \n");
return(0);
}
for (j=0; j<BASE_COUNT; j++)
for (j=0; j<WALL_COUNT; j++)
for (j=0; j<WALL_COUNT; j++)
for (j=0; j<PROP_COUNT; j++)
fclose(in);
// Load in data for tile map arrays
if ((in = fopen("map.wad", "rb")) == NULL)
{
fprintf(stderr, "Cannot find file: MAP.WAD \n");
return(0);
}
fclose(in);
return(1);
}
```

#### Listing Three

```/********************************************************************
TILE.H
Header file for the Tile class of the Axoview Engine v1.0
Contains the Tile class declaration.
Written by Nate Goudie  1996
********************************************************************/
class Tile
{
// Make everything public
public:
char far **image;       // Pointer to array of tile bitmaps
int width,height;       // Width and height of tile bitmap
int size;               // size of tile bitmap (in bytes)
//Constructor for tile class:
Tile(int num_tiles,int w,int h);
// Function to draw tile into offscreen buffer
void blit(int tile_num,int x,int y,
unsigned char far *screen);
};
```

#### Listing Four

```/********************************************************************
TILE.CPP
The Tile class for the Axoview Engine v1.0
- a class to handle the containment and drawing of tiles
Contains the Tile class functions.
Written by Nate Goudie  1996
********************************************************************/
#include    <stdio.h>
#include    <alloc.h>
#include    <mem.h>
#include    "tile.h"
// Declare constants for the viewport clipping
#define XMIN 0
#define XMAX 319
#define YMIN 0
#define YMAX 199
#define SCREENWIDTH 320
#define SCREENHEIGHT 200
/*******************************************************************
Function:  Tile::Tile();
Purpose:   The Tile class constructor
Arguments: num_tiles     - number of tiles (bitmaps)
w             - width of the tile in pixels (bytes)
h             - height of the tile in pixels (bytes)
Comments:  Will compute size of each tile in bytes, and will
allocate memory for the tile bitmaps
*******************************************************************/
Tile::Tile(int num_tiles,int w,int h)
{
width=w;
height=h;
// Calculate size of tile bitmap
size=w*h;
// Allocate memory for tile bitmaps
image=new char far *[num_tiles];
for (int j=0; j<num_tiles; j++)
image[j]=new char[size];
}
/*******************************************************************
Function:  Tile::blit();
Purpose:   Tile class function which blits the indicated tile
bitmap to an offscreen buffer using the
"Reverse Painter's Algorithm".
Handles clipping and tile transparency.
Arguments: tile_num      - index of tile bitmap to display
x, y          - offscreen coordinates where the tile
should be drawn
screen        - pointer to offscreen buffer
Comments:  The destination buffer (screen) must be 320x200.
*******************************************************************/
void Tile::blit(int tile_num, int x, int y, unsigned char far *screen)
{
int txstart=0, txend=width, tystart=0, tyend=height;
// Perform clipping before main loop
// Clip tile to viewport and set boundaries to blit through
if (x<XMIN)
txstart=XMIN-x;
if (y<YMIN)
tystart=YMIN-y;
if ((x+width-1)>XMAX)
txend=XMAX-x+1;
if ((y+height-1)>YMAX)
tyend=YMAX-y+1;
// Calculate tile and buffer starting offsets
int toffset = (tystart*width)+txstart;
int poffset = (y+tystart)*SCREENWIDTH+(x+txstart);
// Calculate next row increments
int toffinc = ((width-txend)+txstart);
int poffinc = (SCREENWIDTH-(txend)+txstart);
// Dereference one of the pointers to the tile bitmap for speed
char far *tileimage = image[tile_num];
// Now loop through and copy the tile bitmap to the screen buffer
for(int row=0; row<(tyend-tystart); row++)
{
for(int column=0; column<(txend-txstart); column++)
{
// Get pixel from the offscreen buffer
int dest_pixel=screen[poffset];
// Check if it is transparent (0), if so, copy
// the tile bitmap's pixel over it
if (!dest_pixel)
screen[poffset] = tileimage[toffset];
poffset++; toffset++;
}
toffset+=toffinc;
poffset+=poffinc;
}
return;
}
End Listings
```

### More Insights

 To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.