Chinaunix首页 | 论坛 | 博客

  • 博客访问: 290277
  • 博文数量: 57
  • 博客积分: 2014
  • 博客等级: 大尉
  • 技术积分: 605
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-18 14:30
文章存档

2015年(3)

2009年(5)

2008年(32)

2007年(17)

我的朋友

分类: C/C++

2008-05-18 13:37:54

by Marius Andra



Hi all! In this tutorial we will build a simple game in SDL called Clickomania. Clickomania is actually a really simple game to build. Our version of the game will consist of a 10x14 grid of balls. When you click (with the mouse) on a ball, that's connected to 2 or more balls, all the connected balls of the same color as the ball you clicked dissapear. And the balls that were above the dissapeared balls will simply fall down. If you manage to clear a row of balls, then the other rows move in to the left from the right. These 4 images illustrate the basic aspects of the game:


Before
 
After

After clicking one of the purple balls in the yellow region, all of them disappear and the balls that were above them fall down.


Before
 
After

When we get rid of the balls in the yellow region the rest move from right to left. Note that the rows of balls move from right to left, not the individual balls themselves. Now let's get to coding the game.

We first have some #includes and some #defines.
#include 
#include 
#include 
#include 
#include 

#include 
Since we'll also use the SDL Font routines (tutorial 4), we'll have to include font.h
#include "font.h"
Now come some variables. The first two contain the dimensions of the playfield. They will be set to some values later.
int rows;
int cols;
screen should be obvious. balls contain the images of the 10 possible ball types. font1 and font2 shouldn't be too much to grasp as well.
SDL_Surface *screen;   // The screen surface
SDL_Surface *balls[10];// The ball images
SDLFont *font1;        // 2 fonts
SDLFont *font2;
playf contains the grid of all the balls. We will initalize it later.
char *playf;           // The playfield itself
scrwidth and scrheight contain the default width and height of the screen. Later we'll "parse" the command line arguments checking, if the user wants to run this program at some other screen resolution. Depending on the screen resolution, the grid of balls might and might not fully cover the screen when drawn from the screen coordinates o,o. centx and centy tell us from where to start drawing the array of balls. They will be calculated later.
// The default width and height of the screen
int scrwidth=640, scrheight=480;
// Used to center the grid nicely onto the screen
int centx=0,centy=0;   
The next variable, bls, tells us how many differently colored balls are there on the screen. bla is used with the mouse. More specifically it's used to see whether a mouse button has been clicked. score should be obvious.
int bls=4;             // Number of differently colored balls

int bla=0;             // Used with the mouse...

int score=0;           // The current score
The function DrawIMG should be clear to all of you by now.
// This function draws a surface onto the screen
void DrawIMG(SDL_Surface *img, int x, int y)
{
  SDL_Rect dest;
  dest.x = x;
  dest.y = y;
  SDL_BlitSurface(img, NULL, screen, &dest);
}
Now the function swap takes references to 2 char variables (the playfield is an array of type char, btw). It then simply swaps them.
// This swaps two type char variables
void swap(char &r1, char &r2)
{
  char temp=r1;
  r1=r2;
  r2=temp;
}
The function grid takes the coordinates of the playfield as parameters and returns the value (color of the ball) that's on that specific coordinate. It's used almost everywhere in the rest of the program. The function returns a reference to the char (instead of an instance of the char like we usually do) at the coordinate so that we could manipulate it (swap it with some other, assign a value to it, etc) outside the function.
// Returns the color of a ball in the playfield.
inline char &grid(int a, int b)
{
  return playf[a*cols+b];
}
The next function, collapse, makes the level collapse. It's called right after we remove a bunch of balls from the grid.
// This function makes balls fall down and rows move left.
void collapse()
{
First we check if we can make any balls fall down. For that we loop through all the columns in the grid. With each column we loop through all the rows. If we find some ball with the value -1, then we remember it's row and move the others onto it. After we move one ball onto the previously marked spot, we decrease the location of the marked spot so that the next ball we move goes onto the top of the spot where we moved our previous ball. After we do this all the balls will be fallen down.
  // Make balls fall down
  int to=-1;
  for(int j=0;j=0;i--)
    {
      if(to==-1 && grid(i,j)==-1) {to=i;}
      else if(to!=-1 && grid(i,j)!=-1) 
        {swap(grid(i,j),grid(to,j)); to--;}
    }
  }
Now we want to move the balls from right to left. For that we loop through all the columns. If one of those columns should have the bottom cell in the row empty (-1), then we know that the entire column is empty. And if it is empty, we mark the location and move all the other columns onto it. After we move one column, we increase the marked location counter and move our columns there.
  // Move rows to the left
  to=-1;
  {
    for(int j=0;j
After we have done all that we blank out all the leftover columns.
    {
      if(to!=-1)
      {
        for(int j=to;jNow the next function recursively clears out a bunch of balls. We use the flood fill algorithm in it. How it works is really simple. We first make the current location (passed as the parameters to this function) in the grid equal -1. We then check all the sides of the current location (top, bottom, left, right). If they equal the color the current location was before, then we call this same function on them. The function runs until all of the connected balls get changed to the value -1. This function also keeps track on the number of balls cleared and returns the cleared number with the return statement at the end. The number of balls cleared is important with the scoring system. You should also know that after we run this function on a specific ball, we call the collapse function (look above for it).
// This is a recursive function that clears a bunch of balls.
// It uses the flood fill algorithm
int pick(int i, int j, int a)
{
  int sum=1;
  grid(i,j)=-1;
  if(i != 0 && grid(i-1,j)==a) sum+=pick(i-1,j,a);
  if(j != 0 && grid(i,j-1)==a) sum+=pick(i,j-1,a);
  if(iThe next function, bunch, is similar to the previous function. But it only returns 1 when the ball at the given coordinates is connected to any other ball of the same color, or it returns 0, when there aren't any connected balls.
// This function checks whether one ball is connected to an other
int bunch(int i,int j)
{
  int a = grid(i,j);
  if(i!=0 && grid(i-1,j) == a) return 1;
  if(j!=0 && grid(i,j-1) == a) return 1;
  if(iThe next function, DrawScene is really simple. We first hide the mouse cursor (comment out that line to see why we do that). After that we fill the screen with black. 
// Draw the screen
void DrawScene()
{
  SDL_ShowCursor(0);                    // Hide the mouse cursor
  // Clear the entire screen with black
  SDL_FillRect(screen,NULL,0x000000);   
Now we loop through the grid and draw all the balls on the screen.
  // Draw the balls
  for(int i=0;iThen we draw some informaton, ...
  // Draw some info and the score
  drawString(screen,font1,1,scrheight-16,
      "SPACE restarts, gray + and - change the nr  \
       of balls (%d). Score: %d",bls,score);

  // Draw the webspace url to the bottom-right of the screen
  drawString(screen,font2,scrwidth-stringWidth(font2,
     "http://cone3d.gamedev.net"),scrheight-16,
     "http://cone3d.gamedev.net");
... flip the screen and show the mouse cursor again.
  SDL_Flip(screen);        // Flip the buffers
  SDL_ShowCursor(1);       // Show the cursor again
}
The next function, newgame, puts some random balls on the grid of balls over the old ones and also sets the score to zero. After that it calls DrawScene to update the changes.
// Resets the entire grid by adding random balls to it
void newgame()
{
  // Reset the score
  score=0;

  // Fill the grid with random balls
  for(int i=0;iAnd now the final function, main. We first initalize the random number generator. After that we check if the first command line argument given to this program equals 640, 800, 1024, 1152, 1280 or 1600. If so, we change the screen dimentions to the requested ones.
// This is our main function
int main(int argc, char *argv[])
{
  srand(time(NULL));    // We initalize the random number generator

  // Depending on the command line arguments given to this program,
  // we change the screen resolution.
  if(argc>1 && strcmp(argv[1],"640")==0) 
       {scrwidth=640;scrheight=480;}
  if(argc>1 && strcmp(argv[1],"800")==0) 
       {scrwidth=800;scrheight=600;}
  if(argc>1 && strcmp(argv[1],"1024")==0) 
       {scrwidth=1024;scrheight=768;}
  if(argc>1 && strcmp(argv[1],"1152")==0) 
       {scrwidth=1152;scrheight=864;}
  if(argc>1 && strcmp(argv[1],"1280")==0) 
       {scrwidth=1280;scrheight=960;}
  if(argc>1 && strcmp(argv[1],"1600")==0) 
       {scrwidth=1600;scrheight=1200;}
We then do the usual initalization stuff.
  // Initalize SDL
  if ( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0 )
  {
    printf("Unable to init SDL: %s\n", SDL_GetError());
    exit(1);
  }
  atexit(SDL_Quit);
Now we set up a random picture of a ball as the icon of the program. So that (almost) every time you run the game, it's icon will be different.
  // We load in a random ball image as the icon for this program
  char tempstring[100];
  sprintf(tempstring,"data/balls/%d.bmp",rand()%9+1);
  SDL_WM_SetIcon(SDL_LoadBMP(tempstring),NULL);
Now we initalize the screen surface
  // Initalize the video mode
  screen=SDL_SetVideoMode(scrwidth,scrheight,32,
     SDL_SWSURFACE|SDL_HWPALETTE|SDL_FULLSCREEN);
  if ( screen == NULL )
  {
    printf("Unable to set %dx%d video: %s\n", scrwidth, scrheight, 
                                                  SDL_GetError());
    exit(1);
  }
Load in 2 fonts and 10 ball surfaces.
  // Load in the fonts
  font1 = initFont("data/font1");
  font2 = initFont("data/font2",1,1,0);

  // Load in all the balls
  for(int a=1;a<10;a++)
  {
    char temp[100];
    sprintf(temp,"data/balls/%d.bmp",a);
    balls[a-1]=SDL_LoadBMP(temp);
  }
Now we see, how many balls can the current screen resolution hold. We simply divide the screen width and height(minus 16 because of the info text) by 45 (the width/height of one ball).
  // Calculate how much balls fit on the screen with the current
  // screen resolution
  rows=(scrheight-16)/45;
  cols=(scrwidth)/45;
And now we calculate how much must we move the balls in order to get the playfield centered on the screen. First we check how much pixels does the grid occupy. We then subtract that value from the width/height of the screen. We then get the amount of free space left over by the playfield. We then divide that value by 2 and get the amount we must move the playfield on the x and y coordinates.
  // Calculate how much must the balls be moved
  // so that they would be centered.
  centx = (scrwidth-cols*45)/2;
  centy = (scrheight-16-rows*45)/2;
We now allocate memory to store the playfield and then make it contain random balls.
  // Allocate space for the playfield
  playf = new char[rows*cols];

  // Reset the score and generate the playfield
  newgame();
Now comes the game loop.
  // Loop a bit
  int done=0;
  while(done == 0)
  {
    SDL_Event event;
We check for all sorts of event.
    while ( SDL_PollEvent(&event) )
    {
      // If someone closes the prorgam, then quit
      if ( event.type == SDL_QUIT )  {  done = 1;  }
If someone presses ESC, we quit. SPACE restarts the game. Gray + and gray - increase/decrease the number of differently colored balls.
      if ( event.type == SDL_KEYDOWN )
      {
        // If someone presses ESC, then quit
        if ( event.key.keysym.sym == SDLK_ESCAPE ) { done = 1; }
        // Space restarts the game
        if ( event.key.keysym.sym == SDLK_SPACE ) { newgame(); }
        // The gray keypad + increases the nr. of different balls
        if ( event.key.keysym.sym == SDLK_KP_PLUS ) 
           { if(bls<9) {bls++;newgame();} }
        // The gray keypad - decreases the nr. of different balls
        if ( event.key.keysym.sym == SDLK_KP_MINUS ) 
           { if(bls>3) {bls--;newgame(); } }
      }
    }
And now we must check if the user clicked a ball. We first get the coordinates of the mouse. After that we check over which ball the mouse currently is. Because the playfield is centered on the screen, the ball coordinates may be negative. If they are, we restart the loop hoping that on the next run they won't be. If they aren't then we move on.
    int x,y;     // Used to hold the mouse coordinates
    int x2,y2;   // Used to hold the ball coordinates
    float x3,y3; // Used to temporarily hold the ball coordinates
    SDL_GetMouseState(&x, &y);    // Get the mouse coords
    x3=(((float)(x-centx))/45);   // Get the ball coords
    y3=(((float)(y-centy))/45);
    if(x3<0 || y3<0) continue;    // If negative, restart the loop
    else {x2=(int)x3;y2=(int)y3;} // else store them as ints
If at the moment the mouse button is released, then we make bla equal one.
    // If the mouse button is released, make bla equal 1
    if(!SDL_GetMouseState(NULL, NULL)&SDL_BUTTON(1))
    {
      bla=1;
    }
If the mouse button was released the next frame and is held down at the moment then we make bla equal zero. That way we can only reach this if statement the next time the player clicks the mouse button again.
    // If the mouse button was released the previous frame and 
    // is now held down, then ...
    if(SDL_GetMouseState(NULL, NULL)&SDL_BUTTON(1) && bla==1)
    {
      // Make bla equal zero so that we could get here only 
      // if we release the mouse button and then click it again.
      bla=0;
We now check if the clicked ball coordinates are inside the allowed area and if so, we check if coordinates don't point to an empty ball. And if they don't we check if the ball has other balls connected to it.
      // Investigate the clicked ball
      if(x2>=0 && y2>=0 && x2If so we then remove all the connected balls, increase the score by the [number of balls removed-2] squared, make the playfield collapse and redraw the screen.
        // If it really is clickable then get rid of the balls
        int a=pick(y2,x2,grid(y2,x2));
        // Increase the score by the (removed balls-2)^2
        score+=(a-2)*(a-2);
        // Make the balls fall down
        collapse();
        // And update the screen
        DrawScene();
      }
    }
  }
And now, when the loop has finished, we clean up a bit.
  // Let's clean up...
  freeFont(font1);
  freeFont(font2);

  return 0;
}
Here's the code.
阅读(1868) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~