Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9
      Miscellaneous

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.

  1. 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.
  2. 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:

  1. 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.
  2. 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.

DirectX 4 VB 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster