Sol's Graphics for Beginners

(ch11.cpp)

(prebuilt win32 exe)

11 - Adding In the Level

(Last revised 9. August 2005)

Feel free to make a copy of the project, or continue from the last one. (Making backups is always a good idea!)

In this chapter we'll be dividing the display into a grid of tiles. The player can move on some tiles, and will die if other tiles are hit.

We'll start again with a couple new constants. Add them after the other #define lines.

// Width of level data
#define LEVELWIDTH 15
// Height of level data
#define LEVELHEIGHT 10
// Fall color
#define FALLCOLOR 0x000000
// Tile size (width and height)
#define TILESIZE 32

The LEVELWIDTH and LEVELHEIGHT constants define the size of the level; TILESIZE defines the size of one tile.

The level size is chosen so that one tile is 32x32. (32*15 = 480 and 32*10 = 320).

The FALLCOLOR will be used to fill out the tiles which cause the ball to fall off the playing field.

Paste the level data itself after the gKey variables:

// Level data
unsigned char gLevel[LEVELWIDTH * LEVELHEIGHT] =
{
  0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,
  0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,
  0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,
  0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  0,1,0,0,0,0,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,
  1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,
  1,1,1,1,1,0,1,0,1,1,1,0,1,1,1
};

The level data at this point is pretty simple. The platform on which the ball can move is marked with 1, and the holes through which the ball can fall (and thus end the game) are marked with 0. We'll be redefining the level data in later chapters.

Next, add the following lines in the render() function near the end of the physics loop, before the gLastTick += 1000 / PHYSICSFPS line.

if (gLevel[(((int)gYPos) / TILESIZE) * LEVELWIDTH + ((int)gXPos) / TILESIZE] == 0)
{
  // player fell off - reset position
  gXPos = WIDTH / 2;
  gYPos = HEIGHT / 2;
}

In each physics iteration, we calculate on which tile the ball is on (or to be exact, where the center of the circle is). If this tile's value is 0, we reset the player's position to the center of the screen.

For example, if the player's coordinates are 179,83, the tile coordinates would be 179/32 = 5, 83/32 = 2 (remembering that this is integer division, rounded down).

Next, still in render(), replace the background filling code with the following:

// fill background
int i, j;
for (i = 0; i < LEVELHEIGHT; i++)
{
  for (j = 0; j < LEVELWIDTH; j++)
  {
    drawrect(j * TILESIZE, i * TILESIZE, TILESIZE, TILESIZE, 
              gLevel[i * LEVELWIDTH + j] ? BGCOLOR : FALLCOLOR);
  }
}

This code loops through all of the tiles, and fills the whole screen in 32x32 blocks. The color of the tile depends on the value of gLevel[i * LEVELWIDTH + j]. If the level data for the tile is non-zero, BGCOLOR is used, otherwise FALLCOLOR is used. (If this is confusing, don't worry; we'll be replacing this code with something more sensible in later chapters).

Now if you run the application, the level will show, and if you move around, whenever the ball hits a black region, it jumps back to the center.

If you move outside the screen, though, anything can happen. If you're lucky, the ball resets to the center. If you're not, the application crashes. This is because we're reading outside the level data. We'll fix this by making it so that the ball will collide with the borders of the display.

First, add a new constant (after the SLOWDOWN macro definition)

// Slowdown due to collision
#define COLLISIONSLOWDOWN 0.9f

Next, add the following block near the end of the physics loop, right before the line that starts with gLastTick +=...

// Collision with the screen borders
if (gXPos > WIDTH - RADIUS)
{
  gXPos -= gXMov;
  gXMov = -gXMov * COLLISIONSLOWDOWN;
}

if (gXPos < RADIUS)
{
  gXPos -= gXMov;
  gXMov = -gXMov * COLLISIONSLOWDOWN;
}

if (gYPos > HEIGHT - RADIUS)
{
  gYPos -= gYMov;
  gYMov = -gYMov * COLLISIONSLOWDOWN;
}

if (gYPos < RADIUS)
{
  gYPos -= gYMov;
  gYMov = -gYMov * COLLISIONSLOWDOWN;
}

The above code checks for each screen border separately, and, if the ball crosses a screen border, it is moved back to its earlier position, and its motion vector is mirrored in that direction.

(We could have ignored the ball's radius and just checked directly whether the player's position is inside the screen, but doing it this way is somewhat more intuitive to the player).

After this, the ball will bounce off the display's borders. To clean things up a bit, remove the 'draw borders' bit from render(), and you can also remove the WALLCOLOR constant, as we won't be using it anymore.

Moving steadily towards a functional game, next we'll be adding 12 - Start, Goal and Collectibles.

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

Any comments etc. can be emailed to me.