Tiling in DirectX: Part 2
by Martin Estevao

Introduction

Ok, folks, welcome back! In this second installment of "Tiling in DirectX," I plan to expand on the topics presented in the first article, which you can find here. Due to lack of time I'm only going to cover smooth scrolling in this article. This might disappoint some of you, but please realize that with school and my own projects I have increasingly little time. Sorry.

Smooth Scrolling

If you remember from reading Part I, we set up a simple tile engine that took a map from a 2-dimensional array and blitted the tiles to the screen using DirectDraw's BltFast(). We can't stop now! I think we're ready to take the big step into producing a scrolling world effect for your games. Don't feel that this step is daunting in any way. It's actually pretty easy once you understand the concepts.

For those of us who don't know what I mean by "smooth scrolling," I am referring to the effect made when a tile-based background behind the sprites in a game moves. This creates the illusion that the main character (or whatever you are controlling in your game) is travelling from one spot of the world to another, although he may be blitted to the same location on the screen at times. Think Super Mario Bros, which is an example of a scrolling world. Get it?

The Concepts

The key to scrolling is to keep track of where the heck you are in your large tile world, and therefore which tiles in your map to draw to the screen. For example, suppose that after the right arrow on the keyboard is pressed the sprite in your game has to "move" to the right in your map by two tiles. In your tile drawing function, you would have to recognize this change and blit a set of tiles to the screen that have been shifted by two tiles in the array, or, say 64 pixels in the world, to the right. As a result, your sprite appears to move as your tile world behind it changes. I'm sorry if this explanation is hard to understand, but I hope this concept will become clearer as we continue.

As you will see in a bit, we'll create two variables to store this information and the amount of change that has taken place in the map. These two variables will be used in our final version of the draw_tiles() function to decide which tiles in our world to draw to the screen.

As far as concepts go, that's about it. If you have these ideas down pat, you're ready to put this stuff into code.

Setup

Before we rewrite the draw_tiles() function, we have to provide some defines and globals.

#define TILE_SIZE 32 #define WORLD_SIZEX 20 #define WORLD_SIZEY 20 #define SCREEN_SIZEX 12 #define SCREEN_SIZEY 12

Here we defined the size of each tile as 32 pixels, the world size (in tiles) as 20 * 20, and the screen size (also in tiles) as 12 * 12. As a result, we'll have 12 * 12 (144) tiles viewable at a time on the screen, while there are 20 * 20 tiles total in our array. We had to make the map bigger than the screen viewable size so that we can witness the scrolling in action. Note: Depending on the resolution that you set for your DirectX app, you will probably not see the whole screen filled with tiles.

int world_camerax = 0; int world_cameray = 0;

world_camerax and world_cameray, as I mentioned in the Concepts section, are the two variables that will keep track of the position in our world. They will be used together to determine which tiles in the total map array will be blitted to the screen. Right now, we'll initialize them to 0 so our current position is at (0, 0), or the top-left corner of the world/array. world_camerax will store the position in x coordinates, or the distance from the starting point 0. world_cameray will store the position in y coordinates, or the distance from the starting point 0. Again, if you have any problems understanding this, go back and read the Concepts section a couple times. If you're still having trouble, then it's probably my fault. :)

The last thing that we have to do before re-writing the draw_tiles() routine from Part I is to define our new world (20 tiles * 20 tiles), which will be larger than the last array from tutorial I.

char map[WORLD_SIZEY][WORLD_SIZEX] = { {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2}, {2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2}, {2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2}, {2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2}, {2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2}, {2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2}, {2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, };

Pretty large world we've got here, along with a little message. :)

Drawing the Smooth Scrolling Tiles

Now to rewrite the draw_tiles() routine.

void draw_tiles(void) { int tile; int x, y; int scroll_x, scroll_y; // (NEW) int offset_x, offset_y; // (NEW) RECT tile_src;

tile is going to be used later in determining if the tile map[scroll_y][scroll_x], for example, has an ID# of 1 or 2 when we go through the loop of the map array. x and y are the two variables used in drawing the tiles at their appropriate locations. The RECT tile_src is going to specify which graphic is going to be used at each tile location, pointing at the offscreen surface. Whe

scroll_x and scroll_y will serve the function that the regular x and y variables served in the draw_tiles() function from Tutorial 1. You may be asking, "Why can't we just use x and y again?" Good question. The answer is that some calculations have to be done with the "world camera" that include the x and y variables, as you will see in the next steps, so the product of these calculations will be placed in scroll_x and scroll_y so that we can still use x and y, with their original values, for the blitting of the tiles.

offset_x and offset_y will be used in determining the very fine, or precise location to draw our tiles at. These two variables are the heart of the "smooth" in Smooth Scrolling.

for (y = 0; y < SCREEN_SIZEY; y++) { for (x = 0; x < SCREEN_SIZEX; x++) { // (NEW) scroll_x = (world_camerax / TILE_SIZE); scroll_y = (world_cameray / TILE_SIZE); // (NEW) offset_x = scroll_x & (TILE_SIZE - 1); offset_y = scroll_y & (TILE_SIZE - 1); tile = map[scroll_y][scroll_x];

Ok, some new stuff here. First, a double-nested loop is established in order to loop through our map and capture the ID# of each tile.

Next, scroll_x and scroll_x are defined as the values of x and y, respectively, added to the amount of scrolling (in tiles) that occurred in the world so far (the world_camera variables).

Then the offset variables are defined, which will be used later in blitting. What we will do is subtract the offset variables (the amount of "smooth scroll") from the final tile location.

tile_src.left = (tile - 1) * TILE_SIZE; tile_src.top = 0; tile_src.right = tile * TILE_SIZE; tile_src.bottom = TILE_SIZE;

The tile_src RECT is now set up, depending on the tile ID stored in tile for the tile at map[scroll_y][scroll_x];

// (MODIFIED) BltFast((x * TILE_SIZE) - offset_x, (y * TILE_SIZE) - offset_y, lpddsoffscreen, &tile_src, NULL); } } }

Now we blit the tile at the correct screen coordinates, which would be the x and y variable positions * 32 pixels - the "smooth scroll" stored in the offset variables. I used lpddsoffscreen as the name of the DirectX surface where my bitmap is loaded to. Yours might be loaded to a differently named surface.

Final Thoughts

  1. Now that we have a small scrolling tile engine set up, how do I implement it into a game?

    It's relatively simple. To get the world scrolling you'd have to do something like this:

    if (keypress_right) { world_camerax += 8; } if (keypress_up) { world_cameray += 8; }

    Now the player can press a key and have the tiled world scroll. Of course, a lot more goes into the workings of the world/player movement in a game, but I'm sure you can all figure it out : )

    Note: I just made up keypress_right, etc. You'd have to set up DirectInput or use the Win32 API input functions to get player input.

  2. What is that ugly stuff that's happening around the edges of the tiles when I scroll the world?

    That "ugly stuff" is BltFast() not being able to blit objects past the edges of the screen. What you are actually seeing is the drawing of a tile until the top/right/bottom/left of it hits the edge of the screen, and then nothing will be drawn in its place.

    You can fix it by:

    1. Converting all this stuff to use Blt() instead of BltFast().
    2. Doing some fancy calculations with the tile_src RECTs for the appropriate rows or columns.
    3. Cheating and clearing the area around the ugly part with a black section so that it all looks fine and dandy : )

  3. What about all that other stuff like collision detection, multi-layers, etc.?

    I'll really try to get to it. I promise.

  4. "I thought this article was pretty cool!" OR "I hated this @%*^%!$ article!"

    Email me. lpSoftware@gdnmail.net || lpSoftware@home.com

    Visit my website at www.cfxweb.net/lpsoftware

    Also, if you find any mistakes or bugs in this tutorial, please let me know.

This article is © 2000 Martin Estevao. This article may not be re-printed without the author's consent.

Discuss this article in the forums


Date this article was posted to GameDev.net: 11/27/2000
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
General

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!