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.
[size="5"]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 traveling 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?
[size="5"]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.
[size="5"]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
int world_camerax = 0;
int world_cameray = 0;
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}, };
[size="5"]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;
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];
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;
// (MODIFIED)
BltFast((x * TILE_SIZE) - offset_x, (y * TILE_SIZE) - offset_y, lpddsoffscreen, &tile_src, NULL);
}
}
}
[size="5"]Final Thoughts
- 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:
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 : )if (keypress_right)
{
world_camerax += 8;
}
if (keypress_up)
{
world_cameray += 8;
}
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. - 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:- Converting all this stuff to use Blt() instead of BltFast().
- Doing some fancy calculations with the tile_src RECTs for the appropriate rows or columns.
- Cheating and clearing the area around the ugly part with a black section so that it all looks fine and dandy : )
- What about all that other stuff like collision detection, multi-layers, etc.?
I'll really try to get to it. I promise. - "I thought this article was pretty cool!" OR "I hated this @%*^%!$ article!"
Email me. [email="lpSoftware@gdnmail.net"]lpSoftware@gdnmail.net[/email] || [email="lpSoftware@home.com"]lpSoftware@home.com[/email]
Visit my website at www.cfxweb.net/lpsoftware
Also, if you find any mistakes or bugs in this tutorial, please let me know.