| Games and SDL SDL Installation SDL for Embedded SDL API SDL Events | SDL Graphics SDL Threads Thread Example SDL Animation SDL Sound | Raw Video Player Video Formats Video Compression | Game Trees About The Author |
| 1 | 2 |
We shall employ two images used in the popular game Bejeweled to illustrate some basic techniques in graphical animation. In a later chapter, we shall discuss a full implementation of this game; in this Chapter, we just discuss the animated features: how to flash and rotate the jewels and how to swap two adjacent jewels. The following two images, "jboard.jpg" ( left )and "jewels.png" (right ) shown in Figure 1 will be used in the examples of our discussion ( you can download each of the images by pointing your mouse at it and right-clicking the mouse button ).
jboard.jpg |
jewels.png |
|
| Figure 1 | ||
Utilizing the above images we can form the jewel board for the game. We use "jboard.jpg" as our background image and we pick the jewels to be displayed from "jewels.png". As shwon in "jewels.png", there are seven different kinds of jewels; the first and second row show a yellow diamond with different features; the third and fourth row show a grey octagonal jewel and so on. We shall randomly populate the board with these seven kinds of jewels. At the beginning only the one shown in the first column and upper row is chosen for display. By randomly picking the jewel images from "jewels.png" and displaying them on top of "jboard.jpg" at the appropriate positions, you should be able to generate an image like the one shown below.
|
| Figure 2 |
To achieve realistic animation effect, you simply select the jewel images in a row from "jewels.png" and display them one by one at the same board position with a desired time lapse. For example, to rotate the yellow diamond-jewel, you display in the first time frame the jewel image at ( column 1, row 1 ) of "jewels.png"; in second time frame, you display the image at ( column 2, row 1 ) and so on. To flash the yellow diamond-jewel, you choose the images from the second row of "jewels.png". To swap two adjacent-column jewels of Figure 2, you move the left-jewel right one pixel and the right-jewel left one pixel in a time frame until the the two jewels have got into the right positions. We discuss how to achieve these animation effects in detail in the following paragraphs.
SDL_BlitSurface() is the main function that we shall use to achieve the rotating or flashing animation effects. We define two classes, Jewel which holds the properties of a jewel ( i.e. What kind of jewel is it? Where is it located? ), and JBoard which contains information of the jewel board. Obviously, the jewel board holds jewels; in the jargon of object-oriented programming, we say that JBoard has-a Jewel. We also define a Point class to facilitate our programming. As shown below, the Point class is particularly simple, having only two data fields, x and y and two constructors. ( Remember that we are writing programs for both PC and embedded systems which have limited resources. Also, in many cases simplicity is needed to present some concepts more clearly. So the programming style presented here may not be the most eloquent but its easy to understand. )
class Point {
public:
short x;
short y;
Point () { x = y = 0; }
Point ( short x0, short y0 ) { x = x0; y = y0; }
};
|
#define Location Point |
As shown below, the Jewel class is also very simple; it is basically a struct.
class Jewel {
public:
short jType; //type of jewel
Point dest; //position to display jewel
};
|
The JBoard class shown below represents the jewel board and has member functions to do the animation effects discussed above.
class JBoard {
public:
Jewel jewels[8][8]; //jewels on the board
Point jSource[7]; //source locations of various jewels
SDL_Surface *image; //source image of all jewels ( jewels.png )
SDL_Surface *imageMain; //source image of main board ( jboard.jpg )
SDL_Surface *screen; //destination for displaying images
short width; //size of jewel ( all the same )
short height;
JBoard( SDL_Surface *scr ); //constructor
bool init(); //initialize the board
short x2Col ( short x ); //find col # corresponding to x
short y2Row ( short y ); //find row # corresponding to y
Location jewelLocation ( Point p ); //find ( col#, row# ) of Point p
short neighbour ( Location m, Location n ); //determine if m, n are neighbours
bool swapJewels ( Location a, Location b ); //swap two adjacent jewels
};
|
The constructor of JBoard shown below saves the information where to get the seven different kind of jewels from "jewels.png" ( saved in array jSource[] ) and where to put jewels on the board ( saved in array jewels[][].dest ).
JBoard::JBoard( SDL_Surface *scr )
{
SDL_Rect source, dest;
screen = scr;
imageMain = IMG_Load ( "images/jboard.jpg" ); //source image of main board
if ( imageMain == NULL ) {
cout << "Unable to load image\n";
return;
}
image = IMG_Load ( "images/jewels.png" ); //source image of main board
if ( image == NULL ) {
cout << "Unable to load image\n";
return;
}
/*
remember where to get the jewels
totally there are 7 different kind of jewels
*/
short x, y, i, j, k;
width = 48; //size of jewel
height = 48;
x = 3; //location of first jewel
y = 3;
for ( i = 0; i < 7; ++i ) {
jSource[i] = Point ( x, y );
y += 104; //location of next jewel, same x, different y
if ( i == 4 || i == 5 ) y -= 1; //make small adjustments in positions
}
/*
remember where to put the jewels
starting position is ( 20, 204 )
*/
y = 20; //starting y position
for ( i = 0; i < 8; ++i ) {
x = 204;
for ( j = 0; j < 8 ; ++j ) {
jewels[i][j].dest = Point ( x, y );
x += 51; //next jewel
}
y += 51; //next row
if (i == 2) y-=1; //make minor adjustment
}
} //JBoard()
|
After loading the required images into memory and saving the jewel image positions in the constructor, you can construct the jewel board and populate it with various kinds of jewels using the init() member function as shown below.
bool JBoard::init()
{
short k;
SDL_Rect source, dest;
source.x = 0;
source.y = 0;
source.w = imageMain->w;
source.h = imageMain->h;
dest.x = dest.y = 0;
SDL_BlitSurface ( imageMain, &source, screen, &dest );
source.w = width; //all jewels have same size
source.h = height;
//now populate the board randomly with jewels
srand ( ( unsigned )time ( NULL ) ); //random seed
for ( short i = 0; i < 8; ++i ) {
for ( short j = 0; j < 8; ++j ) {
k = rand() % 7;
jewels[i][j].jType = k;
source.x = jSource[k].x;
source.y = jSource[k].y;
dest.x = jewels[i][j].dest.x;
dest.y = jewels[i][j].dest.y;
SDL_BlitSurface ( image, &source, screen, &dest );
}
}
if( SDL_Flip( screen ) == -1 )
return false;
return true;
}
|
SDL_BlitSurface ( imageMain, &source, screen, &dest ); |
SDL_BlitSurface ( image, &source, screen, &dest ); |
The functions x2Col(), y2Row(), and jewelLocation() are responsible for converting a point cooridinate ( x, y ) to a location coordinate ( i, j ) where i, j are column number and row number of jewel, ranging from 0 to 7.
//find the column # corresponding to x position
short JBoard::x2Col ( short x )
{
if ( x < jewels[0][0].dest.x || x > ( jewels[0][7].dest.x + JWIDTH ) )
return -1;
short i;
for ( i = 1; i < 8; ++i ) //simple linear search
if ( x < jewels[0][i].dest.x ) break;
return i - 1;
}
//find the row # corresponding to y position
short JBoard::y2Row ( short y )
{
if ( y < jewels[0][0].dest.y || y > ( jewels[7][0].dest.y + JHEIGHT ))
return -1;
short i;
for ( i = 1; i < 8; ++i ) //simple linear
if ( y < jewels[i][0].dest.y ) break;
return i - 1;
}
Location JBoard::jewelLocation ( Point p )
{
short i, j;
i = x2Col ( p.x );
if ( i < 0 ) return point_1;
j = y2Row ( p.y );
if ( j < 0 ) return point_1;
return Location ( i, j );
}
|
The neighbour() function shown below is to determine if two jewels located at Locations m and n are adjacent neighbours of each other. If the jewels are neighbours of the same row, the function returns 1 and if of same column, it returns 0 otherwise it returns -1.
short JBoard::neighbour ( Location m, Location n )
{
if ( (m.y - n.y) == 0 ) { //same row
if ( abs ( m.x - n.x ) == 1 ) //neighbouring cols
return 1;
} else if ( ( m.x - n.x ) == 0 ) { //same col
if ( abs ( m.y - n.y ) == 1 ) //neighbouring rows
return 0;
}
return -1; //non neighbours
}
|
With the few functions defined above, we can write a function to show the animation effects of swapping two adjacent jewels. The function swapJewels() shown below will do the job. In the code, the macros JWIDTH and JHEIGHT are defined to be 52 pixels, which is approximately the height and width of a square that holds a jewel. ( The height and width of a jewel are held in the data fields width and height, which are 48 pixels.
/*
Swap adjacent jewels.
*/
bool JBoard::swapJewels ( Location m, Location n )
{
short adj_col = neighbour ( m, n ); //0 => adj. rows, 1 => adj. cols, -1 => non-neighbours
if ( adj_col < 0 ) //m, n are not neighbours
return false;
SDL_Rect source_m, source_n, dest_m, dest_n, source0, source1, dest0, dest1;
short i, j, k; //not really needed, just for easier comprehension
if ( adj_col ) k = m.x - n.x;
else k = m.y - n.y;
//Ensure m is on left or top of n
if ( k > 0 ) {
Location temp = m; //swap m, n
m = n;
n = temp;
}
dest_n.w = dest_m.w = source_n.w = source_m.w = width;
dest_n.h = dest_m.h = source_n.h = source_m.h = height;
i = m.y; j = m.x;
dest_m.x = jewels[i][j].dest.x;
dest_m.y = jewels[i][j].dest.y;
k = jewels[i][j].jType;
source_m.y = jSource[k].y;
source_m.x = jSource[k].x;
i = m.y; j = m.x;
dest_m.x = jewels[i][j].dest.x;
dest_m.y = jewels[i][j].dest.y;
k = jewels[i][j].jType;
source_m.y = jSource[k].y;
source_m.x = jSource[k].x;
i = n.y; j = n.x;
dest_n.x = jewels[i][j].dest.x;
dest_n.y = jewels[i][j].dest.y;
k = jewels[i][j].jType;
source_n.y = jSource[k].y;
source_n.x = jSource[k].x;
short d; //distance two jewels need to travel in swapping
if ( adj_col )
d = jewels[n.y][n.x].dest.x - dest_m.x;
else
d = jewels[n.y][n.x].dest.y - dest_m.y;
source0.x = dest0.x = dest_m.x - 2; //backgrounds
source0.y = dest0.y = dest_m.y - 2;
source1.x = dest1.x = dest_n.x - 2;
source1.y = dest1.y = dest_n.y - 2;
source0.w = source1.w = JWIDTH;
source0.h = source1.h = JHEIGHT;
for ( int r = 0; r < d; ++r ) { //moving two images across screen
SDL_BlitSurface ( imageMain, &source0, screen, &dest0 ); //first jewel background
SDL_BlitSurface ( imageMain, &source1, screen, &dest1 ); //second jewel background
SDL_BlitSurface ( image, &source_m, screen, &dest_m ); //first jewel image
SDL_BlitSurface ( image, &source_n, screen, &dest_n ); //second jewel image
SDL_UpdateRect( screen, source0.x, source0.y, 104, 104 ); //update affected region
if ( adj_col ) {
dest_m.x += 1; //first jewel goes right
dest_n.x -= 1; //second jewel goes left
} else {
dest_m.y += 1; //first jewel goes down
dest_n.y -= 1; //second jewel goes up
}
SDL_Delay ( 5 );
}
//swap the states of the jewels
k = jewels[m.y][m.x].jType;
jewels[m.y][m.x].jType = jewels[n.y][n.x].jType;
jewels[n.y][n.x].jType = k;
} //swapJewels
|
d = jewels[n.y][n.x].dest.x - dest_m.x; |
SDL_BlitSurface ( imageMain, &source0, screen, &dest0 ); //first jewel background SDL_BlitSurface ( imageMain, &source1, screen, &dest1 ); //second jewel background |
SDL_BlitSurface ( image, &source_m, screen, &dest_m ); //first jewel image SDL_BlitSurface ( image, &source_n, screen, &dest_n ); //second jewel image |
k = jewels[m.y][m.x].jType; jewels[m.y][m.x].jType = jewels[n.y][n.x].jType; jewels[n.y][n.x].jType = k; |
SDL_Surface *screen;
SDL_Event event;
/* Fire up SDL. */
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO ) < 0) {
printf("Error initializing SDL: %s\n", SDL_GetError());
return 1;
}
atexit(SDL_Quit);
/* Set a video mode. Force 16-bit hicolor. */
if ((screen = SDL_SetVideoMode(VWIDTH, VHEIGHT, 16, 0)) == NULL) {
printf("Error setting a video, 16-bit video mode: %s\n", SDL_GetError());
return 1;
}
JBoard jb ( screen );
jb.init();
Location n ( 3, 4), m ( 4, 4 );
jb.swapJewels ( m, n );
m = Location ( 4, 5 );
n = Location ( 5, 5 );
jb.swapJewels ( m, n );
m = Location ( 2, 4 );
n = Location ( 2, 5 );
jb.swapJewels ( m, n );
|
In this section, we discuss how to write a piece of code that can rotate or flash a jewel. For simplicity, we select a jewel by pointing the mouse at it and clicking on the mouse button; the chosen jewel is randomly set for action of rotation or flashing. If two adjacent jewels are chosen, they will be swapped with each other.
We define a class JMouse that implements the actions.
class JMouse {
private:
short x;
short y;
public:
JBoard *jBoard;
JMouse ( JBoard *jb );
void doJewel( Point p, short action ); //rotate or flash jewel
};
|
JMouse::JMouse ( JBoard *jb )
{
jBoard = jb;
x = y = 0;
}
|
The function doJewel() shown below implements the rotation or flashing animation of the jewels.
/*
Rotate or flash the jewel located at p. If p is outside board, returns.
action = 0 => flash, action = 1 => rotate
*/
void JMouse::doJewel ( Point p, short action )
{
if ( action < 0 ) return;
Location loc = jBoard->jewelLocation ( p );
if ( loc.x < 0 ) {
cout << "outside board " << endl;
return;
}
short i, j, k;
i = loc.y; //i, j roles are reversed here
j = loc.x;
k = (jBoard->jewels[i][j]).jType;
SDL_Rect source, dest;
dest.w = source.w = jBoard->width;
dest.h = source.h = jBoard->height;
dest.x = jBoard->jewels[i][j].dest.x;
dest.y = jBoard->jewels[i][j].dest.y;
source.y = jBoard->jSource[k].y; //source points to row for rotation
if ( action == 0 ) //point to next row which is for flashing
source.y += JHEIGHT;
source.x = jBoard->jSource[k].x;
for ( int i = 0; i < 15; ++i ) {
SDL_BlitSurface ( jBoard->imageMain, &dest, jBoard->screen, &dest ); //background
SDL_BlitSurface ( jBoard->image, &source, jBoard->screen, &dest ); //image
SDL_UpdateRect( jBoard->screen, dest.x, dest.y, JWIDTH, JHEIGHT );
SDL_Delay ( 30 );
source.x += JWIDTH; //next image
}
}
|
Finally, to demonstrate the flashing, rotating, and swapping animation effects, we create a thread named mouse_thread() to handle the actions:
deque |
Whenever we click the mouse, the point at which we clicked at is saved in the queue mouse_down_queue. The thread examines this queue and extract the point if its not empty. It randomly sets the action for rotation or flashing. If two adjacent jewels are clicked, it swaps the two jewels.
To run the thread, you should have a piece of code like the following to create and run it:
Point p;
JMouse jm ( &jb );
SDL_Thread *mthread;
mthread = SDL_CreateThread( mouse_thread, &jm );
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SDL_MOUSEBUTTONDOWN:
p = Point ( event.button.x, event.button.y );
mouse_down_queue.push_back ( p );
break;
case SDL_KEYUP:
if (event.key.keysym.sym == SDLK_ESCAPE) {
SDL_Quit();
quit = true;
}
break;
}
}
SDL_WaitThread( mthread, NULL );
|
For clarity of presentation, we have omitted the step of using a mutex to protect the shared global variable mouse_down_queue but in practice you should include the protection mechanism as discussed in the Chapters SDL Thread and Thread Example.
|
|