Tricks of the Windows Game Programming Gurus:Vector Rasterization and 2D Transformations

All ITKnowledge Source Code Search Tips Advanced Search |

| Tricks of the Windows Game Programming Gurus by Andre LaMothe Sams, Macmillan Computer Publishing ISBN: 0672313618 Pub Date: 09/27/99

Search this book:

Previous | Table of Contents | Next

#### Homogeneous Tile Engines

The previous example of scrolling wasn’t really scrolling in the sense of a side-scrolling platform game. That kind of scrolling is a bit smoother—the entire screen image isn’t warped page to page, but is smoothly scrolled up, down, left, or right.

Using DirectX, there are a number of ways to achieve this effect. You could create a large surface and then display only a portion of it on the primary display surface, as shown in Figure 8.44.

Figure 8.44 Using a large DirectDraw surface to achieve smooth scrolling.

However, this only works on DirectX 6.0 (and later) and needs heavy acceleration. A better approach is to break up your worlds into tiles and then represent each screen by a square matrix of tiles or cells, where each cell represents a bitmap(s) to be displayed at that position. Figure 8.45 shows this setup.

For example, you might decide to make all your tiles 32×32 pixels and to run in a 640×480 mode, which means that the a single screen will require a tile map of 640/32×480/32 = 20×15. Or if you decide to go 64×64, you would need a tile map of 640/64×480/64 = 10×7.5 or, rounding down, 10×7 (7×64 = 448; the last 48 pixels at the bottom of the screen you’ll leave for a control panel).

Figure 8.45 Using a tile-based data structure to represent a scrolling world.

To make this work, you’ll need a data structure, like an array or matrix of integers, or maybe structures that each hold the bitmap information (just a pointer or an index) along with anything else you might need. Here’s an example of how you might create a tiled image:

    typedef struct TILE_TYP

    {

    int x,y;   // position of tile in matrix

    int index; // index of bitmap

    int flags; // general flags for the cell

    } TILE, *TILE_PTR;

Then, to hold one screen of information, you would do something like this:

> Note: You can design the data structure either way, but the single large array is easier to work with because you don’t have to deal with jumping screen maps as you scroll past each 10×7 tile map.

    typedef struct TILED_IMAGE_TYP

    {

    TILE image[7][10]; // 7 rows by 10 columns

    } TILED_IMAGE, *TILE_IMAGE_PTR;

And finally, here’s a world that’s 3×3 of these large tiled images:

    TILED_IMAGE world[3][3];

Or you might decide to just create a tile array large enough to hold 3×3 screens, 30×21, and forget the array, like this:

    typedef struct TILED_IMAGE_TYP

    {

    TILE image[21][30]; // 21 rows by 30 columns

    } TILED_IMAGE, *TILE_IMAGE_PTR;

    TILED_IMAGE world;

So how do you draw each screen? First, you need to load your bitmaps into a large array of 64×64 surfaces. You may have one or more tiles, and some may be repeatable, such as ships, edges, water, and so on. Figure 8.46 shows an example tile set.

Figure 8.46 Bitmap template of a typical tile set.

Then you write a tool, or just use an ASCII editor with some conversion software, so you can generate your tile maps. For example, you might decide to use ASCII data along with a bit of conversion software, so the numbers 0-9 may be used to indicate tiles 0 to 9 in the tile set. Given that, you would need to define a tile set composed of a 30×21 set of cells. Here’s what I would do:

    // use an array of string pointers, could have used an

    // array of chars or int, but harder to initialize

    // the characters '0' – ‘9' represent bitmaps 0-9 in some texture memory

    char *map1[21] =

    {

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    "000000000000000000000000000000",

    };

During runtime, you scan the map information into your main structure, and then you’re ready to render. To render, you must first have a viewport setup or a m×n window that the user is currently viewing.

In most cases, this window is the same size as the screen—640×480, 800×600, and so forth—but not necessarily. You may have a control panel to the right or something that doesn’t scroll. In any case, assuming that the whole screen scrolls and it’s 640×480, you must take a couple of things into consideration:

  • How the 640×480 viewport overlaps the main tile map of 10×7 cells.
  • The boundary conditions.

Let’s try and figure out what I’m talking about here. (I think it may help me too—I’m confusing myself!) All right, imagine that the viewport is at (0,0) in the uppermost tile map, as shown in Figure 8.47.

Figure 8.47 Boundary problems with scrolling tile maps.

In this case, you must draw only the tiles from map[0][0] to map[6][9]. But the second the map scrolls to the right or down, you’re going to have to draw some of the edge tiles from the tile map to the right and directly under the current viewport. Then, as you scroll one entire cell (64×64), you won’t draw an entire row and/or column of tile map[0][0].

So, you can see that any time you’re going to draw a rectangular collection of tiles that’s always 10×7, those tiles will come from one or more tile maps. Moreover, as you scroll +/- from positions that are multiples of 64, you’ll only see part of the edge tiles, so clipping is involved. Luckily, DirectDraw clips all bitmap surfaces for you, so if you draw a bitmap that’s partially off the screen, it will just be clipped. Hence, your final algorithm only needs to determine the tiles to render, look up the bitmap surfaces that each tile represents, and then send them to the blitter.

For a demo of this, check out `DEMO8_11.EXE|CPP` on the CD. It creates the exact world just discussed and allows you to move around in it.

Previous | Table of Contents | Next

Copyright © Macmillan Computer Publishing, Inc.

Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.