Author : Alexander Russell
Page : << Previous 5 Next >>
)
{
fwrite(&width, 1, 2, fp);
fwrite(&height, 1, 2, fp);
far_fwrite(out.bitmap, 1, size, fp);
fclose(fp);
}
else
err=3;
}
else
err=1;
farfree(out.bitmap);
}
else
err=2;
return err;
}
void main(int argc, char *argv[])
{
int is_rle=0;
int i, width, height;
struct ffblk fb;
unsigned char far *buff;
unsigned char pal[PAL_SIZE]; // we just throw it away
printf("Makespr V1.0e\n");
printf("Alex Russell's dos mode13h graphic course\n");
// argc is the number of parameters passed in on the command line
// including the name of the exe which is always the first parm
if ( argc < 2 )
{
printf("USE: makespr filename... [/r]\n");
return;
}
// check for the RLE save flag in the command line parms
// argv[] is an array of pointers to the command line parameters
for ( i=1; i < argc; i++ )
{
if ( *argv[i] == '/' )
{
if ( *(argv[i] + 1) == 'r' )
{
is_rle=1;
break;
}
}
}
// memory to load the raw PCX into
buff=farmalloc(64000u);
if ( buff )
{
// for each file name, create a sprite
for ( i=1; i < argc; i++ )
{
if ( *argv[i] != '/' )
{
// it isn't a /r switch, so lets process it
memset((char *)&fb, 0, sizeof(fb));
// filename can have wild cards, so look for all matching files
// see your c compiler run time lib manual for info on findfirst()
if ( !findfirst(argv[i], &fb, 0) )
{
do
{
printf("Processing: %s\n", fb.ff_name);
// load and unpack the pcx into buff
if ( !unpack_pcx(fb.ff_name, buff, pal, &width, &height) )
{
// save the unpacked image
if ( is_rle )
save_bitmap_rle(buff, width, height, fb.ff_name);
else
save_bitmap(buff, width, height, fb.ff_name);
}
}
while ( !findnext(&fb) );
}
}
}
farfree(buff);
}
else
printf("OUT of mem! [1]\n");
}
/* -------------------------- EOF ------------------------------ */
Chapter 2.1 Exercises1. Write a function to draw a solid sprite that doesn't use memcpy().2. Write a function to draw a string of text on the screen. Use this prototype:
void draw_string(char *s, int x, int y, int text_colour);
3. Write a function to completely fill the screen with a solid sprite. Do not fill the right and bottom edge if it will cause the sprite to go past the edge of the screen.
4. Write a program that draws two sprites side by side, gets both sprites from the off screen buffer as ONE large sprite, and finally draws this new large sprite below the original two sprites.
5. Write a program that tiles a solid sprite on the screen, completely filling it (as 4) then re- tiles a transparent tile on top. Specify the names of both sprites on the command line.
6. Explain in plain language how a PCX file is laid out, and how it compresses images.
7. Write a function that compares the speed of masked versus RLE transparent sprites. Do not wait for the vertical blank when timing. Clock() may be used to time. Try various sprites. Which method is faster? What type of sprite sees the largest speed up?
8. Write a program that loads solid sprites specified on the command line, and shows them on the screen, one at a time. ESC quits, any other key shows the next sprite. Centre the sprites in the middle of the screen.
9. Extend the draw_text() function to handle variable width fonts to a max of 8 pixels wide. The height will remain fixed. Use any format that is convenient to store the widths.
10. Write a function to load any other file format other than PCX, convert to a linear bitmap, and save as a .M13 file. Use makespr as a template, and support the same command line features. A simple file format, if you have access to dpaint, is LBM/BBM. BMP files are another option, but they have a relatively complex file format.
Chapter 3Animation
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
Page : << Previous 5 Next >>