DelphiX Game Tutorial One:
Creating an isometric tiling engine
By Michael Dundee
When I started programming the only thing I had in mind was game programming. After a while I gave up. I used
Delphi but I couldn't find any good game APIs for it. I even started thinking of converting to C/C++, mainly
because there was better coverage of such
APIs as DirectX and OpenGL. But the hassle it required to use these APIs in C/C++ scared me.
Then I stumbled across DelphiX. This API wrapper is easy to implement, fairly fast,
and quite powerful. I like it so much I've used it ever since. And now I'd like
to share it with you.
I will not explain the installation of DelphiX here. I will not explain DirectX either, since
that would take up too much of this article. For a closer look at DirectX and how to install
DelphiX I recomend that you take a look at Dominique Louis's articles
and other Borland Developer Community articles on the subject
(1,
2,
3).
The goal of this series of tutorials is to create a basic game engine. We'll
cover subjects like these: creating an isometric tiling engine, animating units and
tiles, pathfinding, basic game AI, and collision detection.
In this chapter we will create a basic tiling engine which can read a map from
a disk file. An editor for these files can be
downloaded here.
GETTING STARTED
First let's go over some special words which will be used throughout this tutorial:
TILE. A small image of regular shape. Alone it doesn't represent much but if you put
a lot of them together it looks like something. Usually used for generating landscapes in games. Tiles can be of different
shapes. The most common are diamond, square, and hexagonal. Since this is an
isometric tiling engine, we will be using diamond-shaped tiles.
TILING ENGINE. A sequence of code (usually a procedure or part of a procedure) which draws the tiles
in their respective positions. It also
determines which render frame to show.
TILING. The procedure of placing all the tiles in their proper positions. This is what the
tiling engine does.
MAKING AN ISOMETRIC LANDSCAPE
The trick to making an isometric environment is to draw all the tiles so that their edges meet.
In this case, the tile is shaped like a diamond. If we simply place all the tiles in even rows
side by side it would look like this:

As you can see we have created what some would call a checkerboard. The huge holes in the ground
are the transparent parts of the images. The edges
don't meet very well, do they? This is what we are trying to achieve:

The way to achieve this is to draw the first row side by side as usual. The next row will
not be started not 32 pixels down (the full height of the tile) but 16 pixels. It will also be started 32 pixels to the left. So
for every new row the start position will be moved down 16 pixels and left or
right (odd rows left, even rows right) 32 pixels.
But that's not all. If you have played Age of empires or AoE2 then you must have noticed that the edge
of the map is not zigzag shaped. The entire map is shaped like a diamond. The way to achieve this is to
draw just one tile on the first row, two on the next, three, four, five and so on to a certain point. At
that point you turn over and start to decrease until you've reached zero. This time you
don't need to switch offset direction between right and left. Just offset it left while increasing
and right while decreasing. If we make a 3x3
tile map it will look like this:

Do you really want a map with just grass? Just water? If the answer is yes then you could stop
here. But to get a bit of variation in the maps (which is
needed in a strategy-based game) we have to be able to load the maps from somewhere. A
two-dimensional array consisting of integer values would solve this. The routine that draws the current
tile checks in this array to determine image to draw. To be able to use multiple maps without having
to redefine them at runtime, we have to save them
as files. This is a simple task as you will see in the code. A map editor is be supplied with
this article. This is a very simple editor that saves
landscape arrays to disk. You only get the source to the editor so you will have to compile
it yourself. You can get it here.
That's enough theory. Let's do the practical part!
BUILDING THE TILING ENGINE
First, make a folder somewhere on your hard drive. All the graphics and .map
files for this project can get lost if they're not organized in one place.
The next step is to download the graphics from here.
If you choose to make your own graphics then remember to keep the size of the images to
64 pixels wide and 32 pixels high.
Now make a subfolder for the editor. Download and unzip those files from
here. Make sure DelphiX is
installed and ready to use. Compile the editor. Get something to drink and a really comfortable chair.
Now you are set to go!
Start a new application in Delphi. Save it in the folder where you put the graphics. I called my files uGame.pas and Game.dpr.
First let's prepare the form. In the type declaration of the form, change TForm to TDXForm. In the object inspector
set these properties:
BorderStyle: | bsNone |
Caption: | Whatever you want |
Name: | MainForm |
Now we need a surface to draw on. For that we use a TDXDraw component. Just drop a TDXDraw
component on your form and give it these properties:
Align | alClient |
Display | 640x480x8 |
Name | DXDraw |
Options |
doFullScreen | True |
doAllowReboot | True |
doFullScreen | True |
Next we will need a place to store the images used to draw the map. Drop a TDXImageList on your form and give it these properties:
DXDraw | DXDraw |
Name | TileMaps |
Add images to the Items property. (You will figure out how, it's self-explanatory.) There are
four images to add. They will be numbered 0 to 3.
Next we will need a Timer to tell the computer when to draw. Don't use the Timer that ships with
Delphi, as it's too slow for game purposes. The DXTimer is much faster. I once clocked the two timers against each
others -- the standard timer went 19 times per second while the DXTimer went about
1,200 times a second. Drop a DXTimer on your form and give it these properties:
Enabled | False |
Interval | 0 |
Name | DXTimer |
Next we'll need a way to scroll our screen. For this we'll use the DXInput component. Drop a DXInput
component on your form and give it these properties:
Name | DXInput |
Joystick |
Enabled | False |
Mouse |
BindInputState | True |
Enabled | True |
Now place a panel on the form. You only have to clear the caption and call it
"Panel." Set the Align property to "alBottom." On this panel place a
button and call it whatever you want. It will be used to shut down the application.
Now all the cut-and-paste programming is done!
BEYOND CUT AND PASTE
Now switch over to the code window and put this declaration in the private section of your form:
procedure DrawTiles(StartX, StartY : Integer);
This is the procedure in which we draw the tiles, the tiling engine.
As you can see, it requires two values to know where to start drawing the tiles. Make these type declarations above the form declaration:
Type TTile = Record
TileImage : Integer;
end;
Type TTileMap = Record
Map : array[1..50,1..50] of TTile;
end;
Also make these global declarations:
Map : TTileMap;
StartX, StartY : Integer;
This creates the information for a map about 50x50 tiles of size. Couldn't we just make
an array of integers instead of a record? I hear you ask.
Maybe. I made it a record so we can expand it later without changing a lot of code. This
way we get a flexible system to build on for later articles.
In the onClick event of the button, write:
Close;
This is for exiting the application. Since we're in full-screen mode we won't have access to the system menu or system buttons.
Now
implement this procedure the DrawTiles procedure we declared earlier:
procedure TMainForm.DrawTiles(StartX, StartY : Integer);
var PosX, PosY, LoopX, LoopY, XTile, YTile, NumOfTilesY,
TileImage : Integer;
begin
PosY := 0;
PosX := 1;
NumOfTilesY := 1;
for LoopY := 1 to 50 do
begin
XTile := 1;
YTile := LoopY;
for LoopX := 1 to NumOfTilesY do
begin
TileImage := Map.Map[XTile, YTile].TileImage;
TileMaps.Items[TileImage].Draw(DXDraw. Surface,
StartX + PosX + LoopX * 64, StartY + LoopY * 16, 0);
XTile := XTile + 1;
YTile := YTile - 1;
end;
NumOfTilesY := NumOfTilesY + 1;
PosX := PosX - 32;
PosY := PosY + 16;
end;
NumOfTilesY := NumOfTilesY - 2;
PosX := PosX + 64;
for LoopY := 1 to 49 do
begin
XTile := LoopY;
YTile := 3;
for LoopX := 1 to NumOfTilesY do
begin
TileImage := Map.Map[XTile, YTile].TileImage;
TileMaps.Items[TileImage].Draw(DXDraw. Surface, StartX +
PosX + LoopX * 64, StartY + PosY + LoopY * 16, 0);
XTile := XTile + 1;
YTile := YTile - 1;
end;
NumOfTilesY := NumOfTilesY - 1;
PosX := PosX + 32;
end;
end;
This procedure draws the tiles starting at the StartX, StartY coordinates. TileImage holds
the current tile type to draw. It's recalculated for each tile, as you can see. To access
this procedure we need to put this code in the onTimer event for DXTimer:
if not DXDraw.CanDraw then Exit;
DXDraw.Surface.Fill(0);
Panel.Caption := 'FPS:' + InttoStr(DXTimer.FrameRate);
DrawTiles(StartX, StartY);
DXDraw.Flip;
DXInput.Update;
If isLeft in DXInput.States then StartX := StartX + 16;
If isRight in DXInput.States then StartX := StartX - 16;
If isUp in DXInput.States then StartY := StartY + 8;
If isDown in DXInput.States then StartY := StartY - 8;
First the procedure checks to see if it can draw on the surface. Then it fills the surface
with black paint. This erases the previous frame content. The
frame rate is then passed to the Caption of the panel. Then it calls the DrawTiles procedure,
passing StartX and StartY as the
starting position. It flips the new image to the screen. After that it
updates the input states. It also tests a number of buttons to figure out how to move the map.
Be sure to add this to the onCreate event for the form:
StartX := 1;
StartY := 1;
DXTimer.Enabled := True;
SUPPORT FOR CUSTOM MAPS
This is all we need to display the map on our screen. But what about custom maps? Start by adding this global declaration:
MapFile : file of TTileMap;
Put a button on the panel and set its caption to "Open." Also place an OpenDialog component on your form. Give it the name
OpenDlg and the filter: MapFile(*.map) | *.map
. Add this code to the onClick event of the Open button:
if OpenDlg.Execute then
begin
AssignFile(MapFile, OpenDlg.FileName);
Reset(MapFile);
Read(MapFile,Map);
end;
This reads the map from file and draws it.
That's it! Now you've got a basic isometric tiling engine up and running. In
the next article (in about a month) we will take a look at animated tiles and world positioning of the
cursor.
The source for this tutorial can be downloaded
here.
E-mail me
if you have questions about this article. That's all for now! See you next month!
Connect with Us