DirectDraw:
Tile Engine Example
By: Jack Hoxley
Written: June 2000
Download:
DD_Tiles.Zip
(15kb)
Despite the fact that this is
extremely simple, I often get emails from people wanting to know how. Either
it isn't acually easy or there are some lazy people around (you choose). Because
you all seem interested in this aspect of graphics; this tutorial will show
you how to knock up a quick 2D tile engine.
Tiling is often used for generating
landscapes in 2D - each tile will represent a different type of land; ie, Sea,
Grass, Rock or Sand. Although 3D has taken over with dramatic landscaping techniques,
it is still possible to create a dynamic and interesting landscape in 2D. The
main advantages being that is is extremely easy, every computer can do it (and
get the same output), and compared with 3D it is quite fast.
The main consideration when using
a 2D tile engine is the tile size. If they are too big there will be a lack
of detail, if they are too small they'll slow the game down. Also, to keep things
simple, they should be a multiple of the screen size. ie, X many tiles will
fit into the width of the screen exactly Y many times. Use this simple reference
table:
Screen Resolution |
Tile Dimensions |
Number of
Blits required to fill screen. |
640x480 |
5x5 |
128 tiles wide,
96 tiles high = 12,288 tiles to be drawn |
|
10x10 |
64 tiles wide,
48 tiles high = 3062 tiles to be drawn |
|
16x16 |
40 tiles wide,
30 tiles high = 1200 tiles to be drawn |
|
32x32 |
20 tiles wide,
15 tiles high = 300 tiles to be drawn |
800x600 |
5x5 |
160 tiles wide,
120 tiles high = 19,200 tiles to be drawn |
|
10x10 |
80 tiles wide,
60 tiles high = 4800 tiles to be drawn |
|
16x16 |
50 tiles wide,
37.5 tiles high = 1850 tiles to be drawn |
|
32x32 |
25 tiles wide,
18.75 tiles high = 468 tiles to be drawn |
1024x768 |
5x5 |
204.8 tiles
wide, 153.6 tiles high = 31,212 tiles to be drawn |
|
10x10 |
102.4 tiles
wide, 76.8 tiles high = 7752 tiles to be drawn |
|
16x16 |
64 tiles wide,
48 tile high = 3062 tiles to be drawn |
|
32x32 |
32 tiles wide,
24 tiles high = 768 tiles to be drawn |
Note that in some resolutions
some tile size don't fit in exactly; this only means that you'll have a few
pixels of free space at the edges. I have marked on the table a red
dot for those sizes to avoid, and a green
dot for those that are good choices, yellow
dots are choices that should only be used if necessary.
This table is irrelevent if you
aren't using any of these resolutions, or if you are using windowed mode; but
the formulas are the same; and as a general rule: Above 1000 tiles and below
5000 tiles will give you good speed to looks ratio.
Tiling is extremely simple; and
there are two main ways it can be achieved.
- At runtime, render the map
onto a single surface - therefore only requiring one render. If done this
way, the table above is fairly irrelevent. However, it may well prove to be
difficult to do Z-Ordering and overlaps.
- At runtime render the map
on each loop. This can seriously cut down the frame rate, even on the faster
computers. With some clever optimisation it can still be done with a reasonable
frame rate. This allows you to animate, alpha blend, do transparencies all
with great ease.
We will be using a very simple
loop to render the map; this can be called either at runtime or before hand:
Sub RenderMap(NumTilesX
as integer,NumTilesY as integer, TileWidth as integer,TileHeight as integer
_
_ TileSourcesurf as DirectDrawSurface7, TileDestSurf as DirectDrawSurface7)
Dim X as integer, Y as
integer, r as RECT, retVal as long
For X = 0 to NumTilesX
For Y = 0 to NumTilesY
'Create our Rectangle.
r.Left = 'Left coordinate for Tile on source surface
r.Top = 'Top coordinate for Tile on Destination surface
r.Right = r.Left + TileWidth
r.Bottom = r.Top + TileHeight
'This is where we copy the tile from the source
to the destination
retVal = TileDestSurf.BltFast(int(X * TileWidth), int(Y * TileHeight),
TileSourceSurf, r, DDBLTFAST_WAIT)
Next Y
Next X
End Sub
|
|
|
|
That will now render a tiled
map. There are several things that could be changed to speed this up if it were
to be done on every loop:
- Remove the Multiplications.
There are 6 different maths functions in the above code. This could possibly
be cut down. Using Multiplication and/or division is quite costly on a loop
basis. The easiest way that this can be done is by using a lookup table. On
the first loop we calculate ALL the coordinates and store them in an array,
then on subsequent loops we just look at this array and the calculation will
be done; this is known as a lookup table.
- Only Draw if necessary. This
is the simplest way of speeding things up. The Fruitworld
game does this, all the tiles are represented by a number in an array, if
this array has changed since the last loop it will draw it, otherwise it moves
onto the next iteration. This can easily be combined with the lookup table
suggested above.
The other aspect of using tiles
is actually drawing them. The most important thing is that they join together
perfectly - and you cant see the joins between them, this is called a seamless
pattern, and you can use PaintShop Pro to generate one for you. The next aspect
is what they look like; although detail is important, there is a fine line.
Assuming that you are drawing generic landscape (Grass, Sand, Water) - you dont
want the user's eye to be focused on it; the grass, water and sand are the background,
not the foreground.
You can download a working example
fromt the top of this page, or you can get it from the downloads
page.
|