Feedback Search Top Backward Forward
EDM/2

Sprites and Animation - Part 3

Written by Larry Salomon, Jr.

 
Part 1 Part 2 Part 4

Introduction

In the final part of this series, we will wrap things up by looking at how I495.EXE was built. This will hopefully get you on your way to using animation within your own applications, although it should be noted that no article or series of articles will ever be able to give an exhaustive treatment of this topic. And when there are applications like Autodesk and Renderman available (albeit expensive), hard-coded animations are rare.

Capabilities

Before we can begin designing, we have to know what it is that we are designing - what will the application do, how much of the behavior can be controlled by the user, etc. Fortunately, I495.EXE allows for little control by the user, so the majority of the application is deterministic, i.e. we can predict what will happen with high accuracy (this statement is a bit inaccurate depending on how it is interpreted, since we use random number generators). Enumerated below are the intended capabilities of the application:

  1. It should have vehicles enter from the top right which proceed to the left and from the bottom left which proceed to the right.
  2. It should not allow the vehicles to have individual velocities, i.e. all vehicles have the same speed.
  3. It should allow the user to control the density of the traffic via the keyboard.

Now that we have a good idea of the capabilities of the application, we can begin the design with the goals that we have set in mind.

Design

Because we have an easy to use "create sprite" function, it is easier for us to create two bitmaps for each vehicle type, one for each direction, and to create two sprites from these bitmaps. Although this is not a good design policy, by knowing which bitmap id's correspond to which vehicle type and direction, we can build a table of sprite information which contains the following information:

  • the sprite handle
  • the direction that the sprite should travel
  • the vehicle type
  • ...plus other information such as sprite size

The corresponding data structure for this is

#define SIT_CAR                  0x0001L
#define SIT_TRUCK                0x0002L
#define SIT_GOESLEFT             0x0004L
#define SIT_GOESRIGHT            0x0008L

#define SIS_UNUSED               0L
#define SIS_VISIBLE              1L
#define SIS_ENTERTOP             2L
#define SIS_EXITTOP              3L
#define SIS_ENTERBOTTOM          4L
#define SIS_EXITBOTTOM           5L

typedef struct _SPRITEINFO {
   HSPRITE hsSprite;
   SIZEL szlSize;
   ULONG ulType;
   ULONG ulState;
} SPRITEINFO, *PSPRITEINFO;
The explanation of the SPRITEINFO structure follows:
hsSprite
handle of the sprite.
szlSprite
size of the sprite. This is used to determine the value of ulState.
ulType
type of the sprite and direction it travels (SIT_* constant).
ulState
state of the sprite (SIS_* constant).
    SIS_UNUSED not currently being used
    SIS_VISIBLE on screen
    SIS_ENTERTOP entering on the top half of the road
    SIS_EXITTOP exiting on the top half of the road
    SIS_ENTERBOTTOM entering on the bottom half of the road
    SIS_EXITBOTTOM exiting on the bottom half of the road

Animation

The animation itself is nothing more than good management of an array of SPRITEINFO structures from within a WM_TIMER message. The pseudocode for the logic is

  1. If nothing is entering on top and a random number is 0, find an unused sprite that goes from right to left and set its state to "enter on top".
  2. If nothing is entering on bottom and a random number is 0, find an unused sprite that goes from left to right and set its state to "enter on bottom".
  3. For all sprites that are not marked as "unused", increment or decrement their horizontal position as appropriate, and update their status to reflect if they are entering, on screen, exiting, or no longer visible ("unused").
Once you understand this, you should have no trouble interpreting the corresponding code below:
case WM_TIMER:
   switch (SHORT1FROMMP(mpParm1)) {
   case TID_TRAFFIC:
      {
         HPS hpsWnd;
         PSPRITEINFO psiSprite;
         POINTL ptlPos;
         ULONG ulIndex;

         hpsWnd=WinGetPS(hwndWnd);

         psiSprite=findSprite(pidData->asiSprites,SIS_ENTERTOP,0);
         if ((psiSprite==NULL) && (rand()%pidData->lMod==0)) {
            psiSprite=findSprite(pidData->asiSprites,
                                 SIS_UNUSED,
                                 SIT_GOESLEFT);
            if (psiSprite!=NULL) {
               psiSprite->ulState=SIS_ENTERTOP;

               ptlPos.x=pidData->szlPlay.cx;
               ptlPos.y=pidData->szlPlay.cy/2+40;

               SprSetSpritePosition(hpsWnd,psiSprite->hsSprite,&ptlPos);
               SprSetSpriteVisibility(hpsWnd,psiSprite->hsSprite,TRUE);
            } /* endif */
         } /* endif */

         psiSprite=findSprite(pidData->asiSprites,SIS_ENTERBOTTOM,0);
         if ((psiSprite==NULL) && (rand()%pidData->lMod==0)) {
            psiSprite=findSprite(pidData->asiSprites,
                                 SIS_UNUSED,
                                 SIT_GOESRIGHT);
            if (psiSprite!=NULL) {
               psiSprite->ulState=SIS_ENTERBOTTOM;

               ptlPos.x=(-psiSprite->szlSize.cx);
               ptlPos.y=pidData->szlPlay.cy/2-60;

               SprSetSpritePosition(hpsWnd,psiSprite->hsSprite,&ptlPos);
               SprSetSpriteVisibility(hpsWnd,psiSprite->hsSprite,TRUE);
            } /* endif */
         } /* endif */

         for (ulIndex=0; ulIndex<MAX_VEHICLES; ulIndex++) {
            psiSprite=&pidData->asiSprites[ulIndex];

            if (psiSprite->ulState==SIS_UNUSED) {
               continue;
            } /* endif */

            SprQuerySpritePosition(psiSprite->hsSprite,&ptlPos);

            if ((psiSprite->ulType & SIT_GOESLEFT)!=0) {
               ptlPos.x-=DELTA_X;

               if (ptlPos.x+psiSprite->szlSize.cx<0) {
                  psiSprite->ulState=SIS_UNUSED;
               } else
               if (ptlPos.x<0) {
                  psiSprite->ulState=SIS_EXITTOP;
               } else
               if (ptlPos.x+psiSprite->szlSize.cx>pidData->szlPlay.cx) {
                  psiSprite->ulState=SIS_ENTERTOP;
               } else {
                  psiSprite->ulState=SIS_VISIBLE;
               } /* endif */
            } else {
               ptlPos.x+=DELTA_X;

               if (ptlPos.x>pidData->szlPlay.cx) {
                  psiSprite->ulState=SIS_UNUSED;
               } else
               if (ptlPos.x+psiSprite->szlSize.cx>pidData->szlPlay.cx) {
                  psiSprite->ulState=SIS_EXITBOTTOM;
               } else
               if (ptlPos.x<0) {
                  psiSprite->ulState=SIS_ENTERBOTTOM;
               } else {
                  psiSprite->ulState=SIS_VISIBLE;
               } /* endif */
            } /* endif */

            if (psiSprite->ulState!=SIS_UNUSED) {
               SprSetSpritePosition(hpsWnd,psiSprite->hsSprite,&ptlPos);
            } else {
               SprSetSpriteVisibility(hpsWnd,psiSprite->hsSprite,FALSE);
            } /* endif */
         } /* endfor */

         WinReleasePS(hpsWnd);
      }
      break;
   default:
      return WinDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
   } /* endswitch */
   break;
You should notice the reference to pidData->lMod in the determination of whether a sprite is to begin entering the display area. This is a changing value which can be adjusted using the "+" and "-" keys; the code is trivial WM_COMMAND processing with the two keys mapped to accelerators.

The rest of the code is, honestly, initialization and termination processing (other than the "standard" PM code). Everything you need to compile I495.EXE is provided as I495.ZIP, which can be found in sources 0207.zip

Summary

This month we said our farewells to this series by examining the animation in a simple application - I495.EXE. And while it was said earlier that hard-coded animations are rare, I would like to conclude that, although rare, a good animation sequence can spruce up any "About box" (or any other part of an application) and is well worth the effort of coding it.

Any questions can be sent directly to me via email at the address listed at the end of this issue.
Part 1 Part 2 Top Part 4