Saturday, August 27, 2011

Fixed Frequency Uncoupled Game Loop

Version 2 of the Bouncy Rectangle program has a refactored game loop engine. After looking around at ways to separate the rate that events happen in the game from the rendering speed of the system running the program I went with an approach called Fixed-Frequency Uncoupled method. Where the game engine will run certain events at fixed frequency, while uncoupling a second module of the program that does the rendering using as much CPU speed is available. To make it interesting I also added an AI opponent, which is another blue square that follows the player around the screen. (No music this time).

To implement multi-threading I used POSIX threads (-pthreads). The fixed frequency game logic would update the game state based on user input and the physics of the moving rectangle "characters". It would then sleep for 100 ms, basically updating the game state 10 times a second.

The render loop was in the main thread and would both update the screen with the updated game state as well as do some animation interpolation based on the trajectories of the rectangles from the last game state. This also had to trap any user input (keyboard keys) and then update the game state with these inputs for when the fixed-frequency thread would next check it. To avoid synchronization issues, I used mutexes when in critical sections that accessed the shared variables containing game state info. The render loop will run as fast as the system it runs on, on my box it was humming along at 140 fps.

This is a high level diagram of the separation of the game logic:

References:

Sunday, August 14, 2011

libSDL Project

For the last few weekends I have given PunchClock and my java work a break, to get back to the fun stuff in C. I am trying to learn the open source Simple DirectMedia Layer (libSDL) api for 2d graphics.

My first simple project was to draw a background and move an object around the foreground. Enter "Bouncy Rectangle".

This is a very simple program where you control the movements of the red square by using the keyboard arrows. Pressing in a direction will either speed you up or slow you down, when you hit one of the edges your speed will be translated into the opposite direction so you "bounce" like a pool ball.

I'd like to work further on separating the movement speeds from the "game loop" which will use 100% of whatever CPU you have if you let it, which would vary the speed from system to system.

The source code is all in one file, after installing libSDL (not so simple on Ubuntu) the program was developed in Eclipse CDT.

Full Source for reference:

/*
============================================================================
Name : BouncyRectangle.c
Author : ammianus
Version : 0.0.1
Copyright : Copyright Brian Laskey 2011
Description : Bouncy rectangle SDL example
Developed in Eclipse CDT, using SDL 1.3.0
============================================================================
*/

#include <stdio.h>
#include <sdl sdl.h="">

/* game loop states */
#define GAME_LOOP_RUNNING 1
#define GAME_LOOP_STOP 0

/* movement constant */
#define MAX_VELOCITY 20
#define VELOCITY_INCREMENT 10

/* pointer to the main window surface */
SDL_Surface *screen;

/*
* Draw the graphic to the coordinates specified on to the screen.
*
* (0,0) is the top left corner of the visible screen.
*
* \param graphic the pointer to a surface containing graphic
* to draw on screen
* \param x the x-coordinate on the screen surface
* of the top-left corner of the graphic to draw
* \param y the y-coordinate on the screen surface of the top-left corner
* of the graphic to draw
*/
void drawGraphic(SDL_Surface *graphic, const int x, const int y) {
/*initialize SDL_Rect struct to hold destination blitting data*/
SDL_Rect dst;

/*fill dst with relevant information*/
dst.x = x;
dst.y = y;
dst.w = graphic->w;
dst.h = graphic->h;

/*blit the graphic to the screen*/
SDL_BlitSurface(graphic, NULL, screen, &dst);
}

/*
* Check if the velocity plus the increment is under max velocity
*/
Uint8 checkVelocity(int velocity) {
Uint8 underMax;

if (velocity + VELOCITY_INCREMENT <= MAX_VELOCITY) { underMax = 1; } else { underMax = 0; } return underMax; } /* * Check if the velocity minus the increment is under absolute max velocity */ Uint8 checkNegativeVelocity(int velocity) { Uint8 underMax; if (abs(velocity - VELOCITY_INCREMENT) <= MAX_VELOCITY) { underMax = 1; } else { underMax = 0; } return underMax; } /* * Process the SDL Event queue * * \param running pointer to running flag for game loop (passed by reference) * \param velocity_y pointer to velocity_y incremented * or decremented by up or down (passed by reference) * \param velocity_x pointer to velocity_x incremented * or decremented by left or right (passed by reference) */ void processEvents(int *running, int *velocity_y, int *velocity_x) { /*e stores the event union which is fetched from the event queue*/ SDL_Event e; /*check the event queue*/ while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_KEYDOWN: if (e.key.keysym.sym == SDLK_ESCAPE) { *running = GAME_LOOP_STOP; } else if (e.key.keysym.sym == SDLK_DOWN) { /* increase or decrease current velocity */ if (checkVelocity(*velocity_y)) *velocity_y = *velocity_y + 10; } else if (e.key.keysym.sym == SDLK_UP) { if (checkNegativeVelocity(*velocity_y)) *velocity_y = *velocity_y - 10; } else if (e.key.keysym.sym == SDLK_LEFT) { if (checkNegativeVelocity(*velocity_x)) *velocity_x = *velocity_x - 10; } else if (e.key.keysym.sym == SDLK_RIGHT) { if (checkVelocity(*velocity_x)) *velocity_x = *velocity_x + 10; } break; case SDL_QUIT: *running = GAME_LOOP_STOP; break; } } fprintf(stdout, "velocity_x: %d, velocity_y: %d\n", *velocity_x, *velocity_y); } /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ static void quit(int rc) { SDL_Quit(); exit(rc); } /* * main: no arguments * * \return: * 0 success * 1 could not init SDL * 2 could not create the screen surface * 3 graphics error */ int main(void) { Uint8 video_bpp; Uint32 videoflags; int x_offset; int y_offset; int video_w, video_h; int velocity_x, velocity_y; /*declare pointer to reference the graphic * (done here for plain C compatibility)*/ SDL_Surface *background_graphic; SDL_Surface *bouncy_rectangle; int running; int fillRectRet; /* Initialize SDL */ if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError()); return (EXIT_FAILURE); } video_bpp = 0; videoflags = SDL_HWSURFACE | SDL_DOUBLEBUF; video_w = 640; video_h = 480; puts("!!!SDL_INIT!!!"); screen = NULL; /*set a 640x480x32 double-buffered window*/ if ((screen = SDL_SetVideoMode(video_w, video_h, video_bpp, videoflags)) == NULL) { fprintf(stderr, "Couldn't set %dx%d %d video mode: %s\n", video_w, video_h, video_bpp, SDL_GetError()); quit(2); } /*Example: set up a fullscreen double-buffered hardware display * surface at 640x480x32*/ /*SDL_Surface *screen = * SDL_SetVideoMode(640,480,32,SDL_HWSURFACE|SDL_FULLSCREEN|SDL_DOUBLEBUF);*/ /*load the background graphic from a file*/ background_graphic = NULL; background_graphic = SDL_LoadBMP("background1.bmp"); if (background_graphic != NULL) { puts("!!!SDL_LoadBMP!!!"); } else { fprintf(stderr, "my_graphic is NULL: %s\n", SDL_GetError()); quit(3); } /* create moving red rectangle on the surface */ bouncy_rectangle = NULL; bouncy_rectangle = SDL_CreateRGBSurface(0, 50, 50, 32, 0, 0, 0, 0); if (bouncy_rectangle != NULL) { fillRectRet = SDL_FillRect(bouncy_rectangle, NULL, SDL_MapRGB( screen->format, 255, 0, 0));
if (fillRectRet == 0) {
puts("Filled bouncy_rectangle");
} else {
fprintf(stderr, "SDL_FillRect error: %s\n", SDL_GetError());
}
} else {
fprintf(stderr, "bouncy_rectangle is NULL: %s\n", SDL_GetError());
quit(3);
}

/* initial values */
running = GAME_LOOP_RUNNING;
x_offset = 0;
y_offset = 0;
velocity_x = 0;
velocity_y = 0;
while (running) {
/*clear the screen*/
fillRectRet = SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0,
0, 0));
if (fillRectRet == 0) {
puts("!!!Screen Cleared!!!");
} else {
fprintf(stderr, "SDL_FillRect error: %s\n", SDL_GetError());
}

/* calculate the boundary of bouncy_rectangle against edges of screen */
/* x-axis */
if ((x_offset + 50 + velocity_x > video_w) || (x_offset + velocity_x
< 0)) { velocity_x = velocity_x * -1; } x_offset = (x_offset + velocity_x); /* y-axis */ if ((y_offset + 50 + velocity_y > video_h) || (y_offset + velocity_y
< 0)) { velocity_y = velocity_y * -1; } y_offset = (y_offset + velocity_y); /*draw the background*/ drawGraphic(background_graphic, 0, 0); fprintf(stdout, "x %d, y %d\n", x_offset, y_offset); /* move the rectangle across the screen */ drawGraphic(bouncy_rectangle, x_offset, y_offset); puts("!!!Draw!!!"); /*update the screen*/ SDL_Flip(screen); puts("!!!Flip!!!"); /*check the event queue*/ processEvents(&running, &velocity_y, &velocity_x); /* pause the program so CPU doesn't go to 100% */ SDL_Delay(40); } /*deinitialize SDL*/ SDL_Quit(); puts("!!!SDL_Quit!!!"); return EXIT_SUCCESS; }


The build command I used was:

gcc -O0 -g3 -pedantic -Wall -c -fmessage-length=0 -MMD -MP -MF"src/BouncyRectangle.d" -MT"src/BouncyRectangle.d" -o"src/BouncyRectangle.o" "../src/BouncyRectangle.c"


And the linking command was:

gcc -L/usr/local/lib -o"SB-Proto1" ./src/BouncyRectangle.o -lSDLmain -lSDL


References
Helpful sites that allowed me to get this far