Alex Russell's Dos Game Programming in C for Beginners

Chapter 3

Animation

To make an object appear to move it is re-drawn many times a second, but it is moved a little bit each time it is re-drawn. Each drawing is referred to as a `frame'. To get smooth animation you want to draw at least 30 frames per second, and frames rates of 60 or more are ideal. Modern hardware makes it easy to reach these speeds with straight forward techniques.

So how do you move an object? You change its x and y coordinates. As a simple example here is code that will draw a series of pixels in a line

```int x;

For ( x=10; x < 200; x++ )
draw_pixel(x, 100, 5);
```

The same basic technique is used to animate sprites, and everything else.

Here is code to make a pixel bounce around the screen.

```// PC bios data area pointer to incrementing unsigned long int
#define TICKS	    (*(volatile unsigned long far *)(0x0040006CL))

// this returns a number that increases by one 18 times a second
unsigned long get_tick(void)
{
return (TICKS);
}

// make a pixel bounce around, leaving a trail on screen,
// on a black background
void bounce_pixel1(void)
{
int done;
int x, y, dx, dy;
long next_time;

x=10;  // current x position
y=20;  // current y position
done=0; // flag for done
dx=1;   // amount to move in x direction
dy=1;   // amount to move in y direction
next_time=get_tick() + 1;  // a timer

while ( !done )
{
// move at a steady speed on all computers
// if not enough time has NOT passed, redraw the
//screen with out moving
if ( get_tick() >= next_time )
{
// move
x+=dx;
y+=dy;

// check for bouncing
if ( x < 0 )
{
x=0;
dx=-dx;  // move in other direction
}
else
{
if ( x > 319 )
{
x=319;
dx=-dx; // move in other direction
}
}

if ( y < 0 )
{
y=0;
dy=-dy;   // move in other direction
}
else
{
if ( y > 199 )
{
y=199;
dy=-dy;   // move in other direction
}
}

next_time=get_tick();
}

// draw, as fast as we can
draw_pixel(x, y, 5);
update_buffer();

// check for user input
if ( kbhit() )
if ( getch() == ESC )
done=1;
}

}
```

This next piece of code animates one sprite on the screen, and restores what was on the screen so that it doesn't leave a trail on the screen.

```// bounce one sprite,  restoring what is on the screen as it moves
void bounce_sprite2(void)
{
int done;
int x, y, dx, dy, old_x, old_y;
long next_time;
unsigned int size;
int max_x, max_y;
unsigned int width, height;
unsigned char far *sprite, far *erase;

if ( !sprite )
return;  // was an error, so quit

// get and save width and height from the sprite
// set max_x and max_y so that the sprite stays completely on screen
_fmemcpy(&width, sprite, 2);
max_x=screen_width - width;
max_x--;
_fmemcpy(&height, sprite+2, 2);
max_y=screen_height - height;
max_y--;

// make space for our erase sprite that will be used to erase
// the old sprite as it move
erase=farmalloc(width*height + 4);  // +4 for width and height
if ( !erase )
{
// was an error, so quit
farfree(sprite);
return;
}

// set starting position and speed
x=150;
y=100;
done=0;
dx=-1;       // this how much the x coordinate moves each frame
dy=-1;      // same for y

// initialize the erase sprite
update_buffer();
get_sprite(erase, x, y, width, height);
old_x=x;
old_y=y;

next_time=get_tick() + 1;
while ( !done )
{
// erase old sprite from where it was
blit_sprite(erase, old_x, old_y);

// move at a steady speed on all computers
if ( get_tick() >= next_time )
{
// move
x+=dx;
y+=dy;

// check for bouncing
if ( x < 0 )
{
x=0;
dx=-dx;  // move in other direction
}
else
{
if ( x > max_x )
{
x=max_x;
dx=-dx;      // move in other direction
}
}

if ( y < 0 )
{
y=0;
dy=-dy;  // move in othr direction
}
else
{
if ( y > max_y )
{
y=max_y;
dy=-dy; // move in other direction
}
}

next_time=get_tick();
}

// draw, as fast as we can

// get what is ON the screen to be restored for the next frame
get_sprite(erase, x, y, width, height);
old_x=x;  // save where we got it from
old_y=y;

// draw the sprite in its new position
blit_sprite(sprite, x, y);
update_buffer();

// check for user input
if ( kbhit() )
if ( getch() == ESC )
done=1;
}

// all done, free up the memory we allocated
farfree(erase);
farfree(sprite);

}
```

Things are only slightly more complicated when working with many sprites. You do the exact same thing for each sprite, but when restoring the erase sprites (the sprites holding what was under the each sprite as it was drawn) you have to restore them in reverse order.

Another option is to re-draw the whole screen from scratch each frame. This is much simpler, but much slower. Most 3D games do this.

As you look at this code you will see that it almost identical to the code that animated one sprite, except that the sprites are stored in an array, all draw and erase actions are now done in loops, and we restore the erase sprites in reverse order.

```// structure to hold all the information needed to draw one sprite

typedef struct
{
int x, y, dx, dy, width, height;
int max_x, max_y;
int old_x, old_y;
unsigned char far *sprite, far *erase;
}
sprite_t;

// this bounces multiple sprites on a colourful background
// Note the restored backgrounds are drawn in REVERSE order
void bounce_sprites(int num)
{
int done;
int i;
long next_time;
unsigned int size;
unsigned far char *master_sprite;
sprite_t *sprites, *sp;

// allocate memory to hold position, speed, and other information
// for each sprite we want to animate
sprites=malloc(sizeof(sprite_t)*num);
if ( !sprites )
return;

// load and display our background picture
if ( !master_sprite )
return;
blit_sprite(master_sprite, 0,0);  // show the background pick
farfree(master_sprite);
update_buffer();

// all the sprites look the same, so we just re-draw the same one
// at different places.
if ( !master_sprite )
{
free(sprites);
return;
}

/*

in this example all the sprites are the same, but just by
sprite, this code would show as all different sprites

*/

// load all the sprites up.
// each sprites needs seperate memory for erasing, but they all share
// the master_sprite for drawing
for ( sp=sprites, i=0; i < num; i++, sp++ )
{
// just make the sprite POINT to the master sprite
sp->sprite=master_sprite;  // could easily load any sprite here

// sets its width and height
_fmemcpy(&sp->width, sp->sprite, 2);
sp->max_x=screen_width - sp->width;
sp->max_x--;
_fmemcpy(&sp->height, sp->sprite+2, 2);
sp->max_y=screen_height - sp->height;
sp->max_y--;

// make space for our erase rect that will be used to erase
// the old sprite as it moves
sp->erase=farmalloc(sp->width*sp->height + 4);
if ( !sp->erase )
{
// malloc failed - quit
// should free the erase rects malloc to here also
farfree(master_sprite);
free(sprites);
return;
}

// randomly set a start position and speed (vector)
sp->x=150 - random(100);
sp->y=100 + random(75);
sp->dx= random(10) < 5 ? -1 : 1;
sp->dy= random(10) < 5 ? -1 : 1;
if ( random(10) < 5 )
sp->dx*=2;
if ( random(10) < 5 )
sp->dy*=2;

// initialize the erase sprite
get_sprite(sp->erase, sp->x, sp->y, sp->width, sp->height);
sp->old_x=sp->x;
sp->old_y=sp->y;
}

done=0;
next_time=get_tick() + 1;
while ( !done )
{
// erase all old sprite, in REVERSE order
for ( sp=sprites+num-1, i=0; i < num; i++, sp-- )
blit_sprite(sp->erase, sp->old_x, sp->old_y);

// move all the sprites
for ( sp=sprites, i=0; i < num; i++, sp++ )
{
// move at a steady speed on all computers
if ( get_tick() >= next_time )
{
// move
sp->x+=sp->dx;
sp->y+=sp->dy;

// check for bouncing
if ( sp->x < 0 )
{
sp->x=0;
sp->dx=-sp->dx;  // move in othr direction
}
else
{
if ( sp->x > sp->max_x )
{
sp->x=sp->max_x;
sp->dx=-sp->dx;
}
}

if ( sp->y < 0 )
{
sp->y=0;
sp->dy=-sp->dy;  // move in othr direction
}
else
{
if ( sp->y > sp->max_y )
{
sp->y=sp->max_y;
sp->dy=-sp->dy;
}
}

next_time=get_tick();
}
}

// draw all the sprites, as fast as we can
for ( sp=sprites, i=0; i < num; i++, sp++ )
{
// get what was under the sprite
get_sprite(sp->erase, sp->x, sp->y, sp->width, sp->height);
sp->old_x=sp->x;
sp->old_y=sp->y;
// draw new sprite
blit_sprite(sp->sprite, sp->x, sp->y);
}

update_buffer();

// check for user input
if ( kbhit() )
if ( getch() == ESC )
done=1;
}

for ( sp=sprites, i=0; i < num; i++, sp++ )
farfree(sp->erase);
farfree(master_sprite);
free(sprites);

}
```

This method allows you to make a sprite drawing function that includes getting the erase sprite which could then be called from many different functions without knowing in advance what order things will be drawn in. This simplifies your code for larger projects.

Chapter 3 Exercises

1. Write a function that bounces a single pixel in straight lines on the screen that does not leave a trail behind it. Use a plain black background.

2. Write a function that bounces a single pixel in straight lines on the screen that does not leave a trail behind it. Load a full screen picture as the background.

3. Write a function that makes a pixel bounce like a ball. Hint: x+=dx; dy+=ddy; y+=dy;

4. Write a function that bounces 10 pixels on the screen. When a pixel has hit an edge 10 times it 'dies'. When all the pixels die the function quits.

5. Write a function that bounces at least 4 identical sprites on a black screen, and erases where the sprites were with a call to rect_fill().

6. Write a function that bounces at least 4 different sprites on a black screen, and erases where the sprites were with a call to rect_fill().

7. Write a function that bounces at least 4 different sprites on a loaded picture, and erases where the sprites were with a sprite grabbed by get_sprite().

8. Write a program that loads 4 different sprites, and bounces them. When a sprite hits the edge for the tenth time it 'dies', and a new sprite (pick one of the four randomly) starts at a random position.