email sol follow sol rss feed of the blog wishlist Sol::Tutorials

Sol's Graphics for Beginners

(ch13.cpp)

(prebuilt win32 exe)

13 - Loading Images

(last revised 9. August 2005)

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

First, download the tiles.bmp (right-click, save as), and save it to the project directory (same place as where the main.cpp is).

SDL has built-in BMP loader. If we wanted to, we could use some more advanced picture format (such as JPEG or PNG), but then we'd need to include code to decode said formats (SDL_Image is one library that can be used to decode various image formats).

First, add a new variable (near the top, under gScreen):


// Surface that contains the tiles
SDL_Surface *gTiles;

Next, in init(), paste the following (before the call to reset()):


  SDL_Surface *temp = SDL_LoadBMP("tiles.bmp");
  gTiles = SDL_ConvertSurface(temp, gScreen->format, SDL_SWSURFACE);
  SDL_FreeSurface(temp);

This code first loads the tile graphics to a temporary surface, then converts the surface to the same format as the screen, and then discards the temporary surface. This makes sure that the pixel data is in the same format as the display. The tiles.bmp is actually a 256 color image, while our pixels are 32 bit.

Since now our tile graphics is in a surface, we can access it in exactly the same way as we access the screen surface. The tiles themselves are arranged as a vertical array - the tiles are arranged on top of each other (see image on the right).

Let's add code that draws one tile to the screen. Paste the following function after the drawrect() function:


void drawtile(int x, int y, int tile)
{
  // Lock surface if needed
  if (SDL_MUSTLOCK(gTiles))
    if (SDL_LockSurface(gTiles) < 0) 
      return;

  int i, j;
  for (i = 0; i < TILESIZE; i++)
  {
    int screenofs = x + (y + i) * PITCH;
    int tileofs = (i + tile * TILESIZE) * (gTiles->pitch / 4);
    for (j = 0; j < TILESIZE; j++)
    {
      ((unsigned int*)gScreen->pixels)[screenofs] = 
        ((unsigned int*)gTiles->pixels)[tileofs];
      screenofs++;
      tileofs++;
    }
  }

  // Unlock if needed
    if (SDL_MUSTLOCK(gTiles)) 
        SDL_UnlockSurface(gTiles);
}

As I said, the the surface is accessed exactly the same way as the screen - you may need to lock the surface, and there's the pitch to consider. Note that we're not doing any clipping, so you must be careful not to draw the tiles outside the display - or, you could implement clipping yourself.

SDL has a surface to surface blit function, along with rectangle filling and lots of other useful and very likely optimized functions, which we could have used, but the point here is to understand what's going on, and - partially - to retain control over the drawing process.

Now, let's change the background drawing code to use the tiles. Replace the background-filling code in render() with the following:


  // fill background
  int i, j;
  for (i = 0; i < LEVELHEIGHT; i++)
  {
    for (j = 0; j < LEVELWIDTH; j++)
    {
      switch (gLevel[i * LEVELWIDTH + j])
      {
      case LEVEL_DROP:
        drawrect(j * TILESIZE, i * TILESIZE, TILESIZE, TILESIZE, FALLCOLOR);
        break;
      case LEVEL_GROUND:
      case LEVEL_COLLECTIBLE:
        drawtile(j * TILESIZE, i * TILESIZE, 0);
        break;
      case LEVEL_START:
        drawtile(j * TILESIZE, i * TILESIZE, 2);
        break;
      case LEVEL_END:
        drawtile(j * TILESIZE, i * TILESIZE, 1);
        break;
      }
    }
  }

Compile and run. Mm. Much better. Of course, it would be even better if an actual graphics artist had done the graphics, instead of a programmer like me.

Now, the ball and the collectibles have a very bad contrast compared to the background, so, to help things a bit, let's give them a shadow (of sorts). First, add the following right before the collectibles' drawcircle() call:


      drawcircle((int)gCollectible[i].mX + 2,
                 (int)gCollectible[i].mY + 2,
                 gCollectible[i].mRadius,
                 0);

and the following right before the drawcircle() of the player's object:


  drawcircle((int)gXPos + 2,
             (int)gYPos + 2,
             RADIUS,
             0);

There. It isn't good, but it's better. Now that we're starting to look a bit like a game, it would be nice to add scoring to the game. Wouldn't it be nice to actually see the score?

Let's add code for 14 - Printing Simple Text.

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

Any comments etc. can be emailed to me.

Site design & Copyright © 2017 Jari Komppa
Possibly modified around: June 01 2010