Topic : DOS Game Programming
Author : Alexander Russell
Page : << Previous 5  Next >>
Go to page :


)

            {

            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 >>