Alex Russell's Dos Game Programming in C for Beginners

Introduction . Chapter 1 . Chapter 2 . Chapter 2.1 . Chapter 3 . Chapter 4 . Chapter 5 . Chapter 6

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;

	// load up the sprite
	sprite=far_load("br1.m13", &size);
	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
	master_sprite=far_load("back1.m13", &size);
	if ( !master_sprite )
		return;
	blit_sprite(master_sprite, 0,0);  // show the background pick
	farfree(master_sprite);
	update_buffer();


	// load our master sprite.
	// all the sprites look the same, so we just re-draw the same one
	// at different places.
	master_sprite=far_load("br1.m13", &size);
	if ( !master_sprite )
		{
		free(sprites);
		return;
		}


	/*

		in this example all the sprites are the same, but just by
		loading different sprites, instead of re-using the master
		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.

Click here for Chap3.zip (27 Kb)

Introduction . Chapter 1 . Chapter 2 . Chapter 2.1 . Chapter 3 . Chapter 4 . Chapter 5 . Chapter 6

Copyright 1998 (c), Alex Russell, All rights reserved