Saturday, October 5, 2013

Tutorial : Creating a small game from scratch : game logic

Hello all !

In order to let people know about the process of making a game for bitbox (or a toy 2D Game in general, this post will have very few specifics to bitbox, next ones will be more involved) , I'll blog a few times to show you that in detail.

The game will consist in a bunch of people jumping out of a building on fire, and a team of two firemen who will make them bounce to the ambulance. It'll be programmed in C99, without any complex techniques.

Sounds familiar ?



Most of the game logic will be calculated each frame (no need to update it each line !)
So, we're putting this in the game_frame callback.

Global state

The global state  will consist in player score and number of lives. Easy peasy.

   int score, lives; 

Firemen

The game state (ie what we need to know to describe current game status) will be quite simple for the player.
 
   int firemen_pos; // position of the firemen, 0 1 or 2
   const int firemen_steps = 3;
   const int firemen_x = {100,300,600}; // places to display the firemen.
   const int firemen_y=450; // fixed !

Each frame we shall move firemen according to gamepad . For this we will use the standard macro PRESSED included in kernel.h

if (PRESSED(left) && firemen_pos >0) 
{
    firemen_pos--;

}
else if (PRESSED(right) && firemen_x<FIREMEN_STEPS)
{
    firemen_pos++;
}

Emitter building

The building emitter will check if you should instantiate a new guy, based more or less randomly  on when was the last emitted guy and your score. Y position will be taken randomly from building floors. Some constants or factors will have to be tuned.

int last_emitted_frame;  

int emit_guy()
{
     return (rand()<(frame-last_emitted_frame)/score);
}

Guys 

Guys will have a parabolic path, with bounces on firemen & stopping in the ambulance or falling on the ground.

We're going to have to store a static array of state for each guy. A record will be inactivated with a special value if not needed.

storing guys

We're going to store for each guy the position and speed. Since the acceleration is only vertical, there is no need storing the horizontal speed for each guy (it will be constant).

    #define MAX_GUYS 40 // should be enough even for hardcore Fire gamers !
    #define INACTIVE -1;
    const int start_y[3] = {400,300,200}; // floors
    const int start_x = 50;


    const int guy_vx 2; // pixels per frame
    typedef struct {
        int x,y,vy; 
        // set x negative to disable this guy. 
        // vx is not needed as it's a constant.
    } Guy;

    Guy falling_guys[MAX_GUYS];

creating guys

creating a guy is nothing more than finding an available place in the array and initializing its values. 

void initialize_guy(int floor)
{
    for (int i=0;i<MAX_GUYS;i++)
    {
        if (guys[i].x==INACTIVE) {
            // found one
            guys[i].x = start_x;
            guys[i].y = start_y[floor];
            last_fired_guy = frame;
            break;
        }
    }
}

moving guys


Each frame, we shall
  • apply the constant vertical gravity by integrating a constant vertical acceleration vector (in plain words, Vy -= GRAVITY, each frame - gravity is expressed in pixels / frames^2),
  • move each guy along its horizontal / vertical speed.
  • To make a guy hesitating before jumping, let's move him only after a few moments (if he has not moved, he is in starting position. Its date of appearance is the last_guy_arrived).
// moving active guys
const int gravity=1;

for (int guy=0;guy<MAX_GUYS;guy++)
{
    if (guys[guy].x == INACTIVE) continue; // skip inactive

    // moving the guy    guys[guy].vy -= gravity;
    guys[guy].x += guys[guy].x;
    guys[guy].y += guys[guy].y;
  
    // collision check
    // ...
}


When a guy position is lower than a given Y position (on the ground, including guy height), then test X position and either
  • ambulance is here : score !
  • firemen are here : rebound (invert Y speed, lower it a little to simulate damping)
  • no one here : fail (ie decrease lives, and restart a new life, or no more lives, restart the game)
Let's implement it in the loop :

// collision check
if (y>=COLLISION_Y)
{
    if (can_bounce(guy))
    {
        guys[guy].vy = -guys.vy - 2;

    }  
    else if (guys[guy].x>AMBULANCE_X ) 
    {
         score += 1;
         guys[guy].x = INACTIVE;
    } else {
         if (lives) {
             lives --;
             reset_life();
         }  else {
             reset_game();
         }
    }
}

And thus we'll wrap it in a update_guy function.

Game functions : testing bounce, resetting game state

integer can_bounce(int guy)
{
    return (guys[guy].x > firemen_x[firemen_pos] \ 
       && guys[guy].x > firemen_x[firemen_pos]+firemen_width);
}

void reset_life()
{

    // reset other elements state
    firemen_pos = 0;
    for (int guy=0;guy<MAX_GUYS;guy++)    {
        guys[guy].x  = INACTIVE;
    }
    last_fired_guy = frame+10; // issue a small pause.
}

void reset_game()
{
    score = 0;
    lives = 3;
    reset_life();
}


At the start of the game, let's initialize everything by implementing a game init

void game_init(void)
{
    reset_game();
}

The full listing is thus :


OK, we'll just have to display this now !
You can follow the next tutorial (blitting) here


No comments:

Post a Comment