Sol's Graphics for Beginners

(ch25src.zip)

(prebuilt win32 exe)

25 - Bigger Levels

Feel free to make a copy of the project, or continue from the last one.

Now that we have the camera bit done, we're down to two more tasks regarding the bigger levels. First, we're currently hardcoded to one level size; we need to replace the LEVELWIDTH and LEVELHEIGHT macros with variables that depend on the actual level size. The second task is to load different sized levels.

Open up gp.h. Find the LEVELWIDTH and LEVELHEIGHT macros, and remove them. Try to recompile. (If you don't get any errors, you will need to remove the object files before compiling).

Luckily, we don't access those two macros in too many places. Add the following variables in main.cpp and also in gp.h, with the extern-prefixes:

// Dynamic level width and height, in tiles
int gLevelWidth;
int gLevelHeight;

Since we'll be allocating the level map dynamically, the variable's definition changes. Change the variable in both main.cpp and gp.h to:

// Level data
unsigned char *gLevel;

Remember to include the 'extern'-prefix when adding the variables in the header file.

Next up, go to game.cpp. The LEVELWIDTH and LEVELHEIGHT macros are in use in several places in this source file. The easiest way to replace them with gLevelWidth and gLevelHeight is to simply use the search-and-replace functionality in the editor.

After the change the code compiles again, but will crash, as we're not allocating the level table.

First, go to main.cpp and its init() function, and set the gLevel variable to NULL in the beginning.

gLevel = NULL;

Next, back in game.cpp, add the following at the beginning of reset():

gLevelWidth = 15;
gLevelHeight = 10;
delete[] gLevel;
gLevel = new unsigned char[gLevelWidth * gLevelHeight];

With these changes, the game should compile and run again.

This may have felt like an useless step, but after drastic changes like this, it's important to see that the game is still functional, and we haven't forgotten anything important.

Next we'll need to change the level-loading code to support larger levels. We'll change the level-loading code in reset() to perform two passes through the level file. On the first pass, we'll detect the level width and height, and the second pass loads the level as before.

First, remove the lines we just added from the beginning of reset(); they won't be needed after the new level loading code is finished.

Currently the reset() function first opens the level file and then loops through the level array, filling it with data from the level file.

Paste the following block between the file opening and level loading code:

gLevelWidth = -1;
int i = 0;
while (i != '\n' && i != '\r')
{
  i = fgetc(f);
  gLevelWidth++;
}
gLevelHeight = 1;
while (!feof(f))
{
  if (fgetc(f) == i)
    gLevelHeight++;
}
fseek(f, 0, SEEK_SET);

delete[] gLevel;
gLevel = new unsigned char[gLevelWidth * gLevelHeight];

Here we first scan the first line in the level file until we hit either the carrier return or the newline character. (Macs use 'carrier return' as the 'new line' code; unices use 'line feed'. Microsoft OSes use both. And yes, new os X macs are also unix machines. This does not make things simpler, however).

Next we scan through the rest of the file, counting the number of the newline characters we detected earlier. This count will become our level height.

Finally we seek back to the beginning of the file so that we're ready for the second pass. We also allocate space for the level array, first deleting any previous array we may have had.

If you try to compile, the compiler will give an error from the redefinition of the integer i; remove the latter definition (it's located before the loop in which we're looking for the collectibles and start tiles)

After that little change the code should compile and run. Now the game can load levels of any size, as long as each line has the same amount of tiles. There's still a couple of things to change to make them work though. First, make larger levels (or download a bunch of them here (right-click, save as) and try to play.

When you move to the right far enough, you will suddenly die. This is because our collision code with the level's borders is not actually using the level size, but still expects the level to be as big as the screen.

In game.cpp, find the comment that says 'Collision with the level borders', and change the border collision check under it to:

if (gXPos > gLevelWidth * TILESIZE || 
    gXPos < 0 || 
    gYPos > gLevelHeight * TILESIZE || 
    gYPos < 0)

One bug fixed, but there's a worse one left. Try to play; you'll notice that when you move to the right, the background repeats. Moving downwards is worse, and will crash the application.

Find the call to the drawbackground() function, and change it to:

drawbackground(tick, 
                (int)(WIDTH * ((WIDTH / 2) - gCameraX) / (gLevelWidth * TILESIZE)), 
                (int)(HEIGHT * ((HEIGHT / 2) - gCameraY) / (gLevelHeight * TILESIZE)));

This will cause the background scrolling to be scaled based on the size of the level. It will look a bit weird in levels which are not square, as the background moves faster on one axis than on the other, but I don't consider this a real problem right now though.

The application may still crash, though, due to how we're moving the camera. Instead of capping the camera positions, we'll make sure that the background rendering code never crashes. In background.cpp, add the following at the beginning of the drawbackground() function:

if (posx < 0) posx = 0;
if (posx > WIDTH) posx = WIDTH;
if (posy < 0) posy = 0;
if (posy > HEIGHT) posy = HEIGHT;

Before tuning the gameplay, we'll experience some 26 - Wall Madness.

Having problems? Improvement ideas? Just want to discuss this tutorial? Try the forums!

Any comments etc. can be emailed to me.