Copyright (c) Hyperion Entertainment and contributors.

Graphics Sprites, Bobs and Animation

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
WIP.png This page is currently being updated to AmigaOS 4.x. Some of the information contained here may not yet be applicable in part or totally.

Contents

Graphics Sprites, Bobs and Animation

This article describes how to use the functions provided by the graphics library to manipulate and animate Graphic Elements (also called GELs). It is divided into six sections:

  • An overview of the GELs animation system, including fundamental terms and structures
  • Explanation of simple (hardware) Sprites and an example showing their usage
  • Explanation of VSprites and an example showing their usage
  • Explanation of Bobs and an example showing their usage
  • Discussion of topics that apply to all GELs such as collision detection and data structure extensions.
  • Discussion of animation, using AnimComps and AnimObs and an example showing their usage

About the GELs System

Before going into details, a quick glossary is in order. A playfield forms the background that GELs operate in. It encompasses the View, ViewPort, and RastPort data structures. (VSprites appear over, and Bobs appear in the playfield.) Playfields can be created and controlled at several levels. Refer to Graphics Primitives and Layers Library for details on lower-level playfield control. Intuition Screens explains how to get higher-level access to playfields.

GELs, or graphic elements, are special graphic objects that appear in the foreground and can be moved easily around the display. They are software constructs based on the Amiga's sprite and blitter hardware. The GELs system is compatible with all playfield modes, including dual-playfield. All the various types of GELs are defined by data structures found in <graphics/gels.h>.

Types of GELs

The GEL types are (in order of increasing complexity):

VSprites
for Virtual Sprites. These are represented by the VSprite data structure and implemented with sprite hardware.
Bobs
Blitter Objects. These are represented by the VSprite and Bob data structures and implemented with blitter hardware.
AnimComps
Animation Components. These are represented by the VSprite, Bob and AnimComp data structures and implemented with blitter hardware.
AnimObjs
Animation Objects. These are used to group AnimComps. They are not strictly GELs, but are described here.

Simple Sprites

Simple Sprites (also known as hardware sprites) are not really part of the GELs system but are the basis for VSprites. Simple Sprites are graphic objects implemented in hardware that are easy to define and easy to animate. The Amiga hardware has the ability to handle up to eight such sprite objects. Each Simple Sprite is produced by one of the Amiga's eight sprite DMA channels. They are 16-bits wide and arbitrarily tall.

The Amiga system software offers a choice of how to use these hardware sprites. After a sprite DMA channel has displayed the last line of a Simple Sprite, the system can reuse the channel for a different sprite lower on the screen. This is how VSprites are implemented; as a software construct based on the sprite hardware.

Hence, Simple Sprites are not really part of the animation system (they are not GELs). In fact, if Simple Sprites and GELs are used in the same display, the GELs system must be told specifically which Simple Sprites to avoid. Simple Sprites are described in this article because they are alternatives to VSprites.

VSprites

The VSprite, or virtual sprite, is the simplest type of GEL. The VSprite data structure contains just a bit more information than is needed to define a hardware sprite. VSprites take advantage of the system's ability to reuse sprite DMA channels - each VSprite can be temporarily assigned to a hardware sprite, as needed. This makes it appear to an application program that it has a virtually unlimited supply of VSprites.

Since VSprites are based on hardware sprites, rules that apply to hardware sprites apply to VSprites too. VSprites are not rendered into the underlying BitMap of the playfield and so do not affect any bits in the BitMap. Because they are hardware based, they are positioned at absolute display coordinates and are not affected by the movement of screens. The starting position of a sprite must not occur before scanline 20, because of certain hardware DMA time constraints. VSprites have the same size limitations as hardware sprites, they are 16-bits wide and arbitrarily tall.

The VSprite data structure also serves as the root structure of more complex GEL types: Bobs and AnimComps.

Bobs and AnimComps

Like VSprites, Bobs and AnimComps are graphics objects that make animation easier. They are rendered using the blitter. The blitter is a special Amiga hardware component used to move data quickly and efficiently, optionally performing logical operations as it does. It can be used to move any kind of data but is especially well suited to moving rectangular blocks of display data.

It is important to keep in mind that Bobs and AnimComps are based on the blitter hardware while VSprites use the sprite hardware. However all three GEL types use the VSprite structure as their root data structure. The system uses pointers to link the VSprite, Bob and AnimComp structures, "extending" the VSprite structure to include all GEL types.

Since Bobs and AnimComps are rendered with the blitter they actually change the underlying playfield BitMap. The BitMap area where the GEL is rendered can be saved. By moving the GEL to new locations in small increments while also saving and restoring the Bitmap as you proceed, you can create an animation effect. Bobs and AnimComps use the same coordinates as the playfield and can be any size.

AnimObs

The AnimOb (Animation Object) is a data structure that is used to group one or more AnimComps for convenient movement. For example, an AnimOb could be created that consists of two AnimComps, one that looks like a planet and another containing a sequence that describes orbiting moons. By moving just the AnimOb the image of the planet can be moved across the display and the moons will travel along with it, orbiting the planet the entire time. The system automatically manages the movement of all the AnimComps associated with the AnimOb.

VSprites vs. Bobs

If you are going to manage the movement and sequencing of GELS yourself, you need to decide if sprite animation (VSprites) or blitter animation (Bobs, AnimComps and AnimObs) best suit your needs. If you've got simple requirements or lots of coding time, you may even opt to use only Simple Sprites, and control them yourself. On the other hand if you want the system to manage your animations, AnimComps must be used and they are Bobs at heart.

Some fundamental differences between VSprites and Bobs are:

  • VSprite images and coordinates currently low-resolution pixels, even on a high resolution display. Bob images and coordinates have the same resolution as the playfield they are rendered into.
  • VSprites have a maximum width of 16 (low resolution) pixels. Bobs can be any width (although large Bobs tend to slow down the system). The height of either VSprites or Bobs can be as tall as the display.
  • VSprites have a maximum of three colors (Simple Sprites can have fifteen if they're attached). Because the system uses the Copper to control VSprite colors on the fly, the colors are not necessarily the same as those in the background playfield. Bobs can use any or all of the colors in the background playfield. Limiting factors include playfield resolution and display time. Bobs with more colors take longer to display.
  • VSprites are positioned using absolute display coordinates, and don't move with screens. Bobs follow screen movement.

In general, VSprites offer speed, while Bobs offer flexibility.

The following figure shows how the various GEL data structures, VSprites, Bobs and AnimComps are linked together.

GEL Structure Layout

The GELs System

Before you can use the GELs system, you must set up a playfield. The GELs system requires access to a View, ViewPort, and RastPort structure. These structures may be set up through the graphics library or Intuition. For most examples in this article, the Intuition library is used for this purpose.

All GELs have a VSprite structure at their core. The system keeps track of all the GELs that it will display (the active GELs) by using a standard Exec list structure to link the VSprites. This list is accessed via the GelsInfo data structure, which in turn is associated with the RastPort. The GelsInfo structure is defined in the file <graphics/rastport.h>.

A new GEL is introduced to the system by calling AddGel() to link it into the GelsInfo list. The new GEL is added immediately ahead of the first existing GEL whose x,y value is greater than or equal to that of the new GEL, always trying to keep the list sorted.

As GELs are moved about the screen, their x,y values are constantly changing. SortGList() re-sorts this list by the x,y values. (Although this is a list of VSprite structures, bear in mind that some or all may really be Bobs or AnimComps.)

The basic set up of the GelsInfo structure requires three important fields: sprRsrvd, gelHead and gelTail. The sprRsrvd field tells the system which hardware sprites not to use when managing true VSprites. For instance, Intuition uses sprite 0 for the mouse pointer so this hardware sprite is not available for assignment to a VSprite. The gelHead and gelTail are VSprite structures that are used to manage the list of GELs. They are never displayed. To activate or deactivate a GEL, a system call is made to add it to or delete it from this list.

Other fields must be set up to provide for collision detection, color optimization, and other features. A complete example for setting up the GelsInfo structure is shown in the animtools.c lisitng at the end of this article.

Initializing the GEL System

To initialize the animation system, call the system function InitGels(). It takes the form:

struct VSprite  *vsHead;
struct VSprite  *vsTail;
struct GelsInfo *gInfo;
 
IGraphics->InitGels(vsHead, vsTail, gInfo);

The vsHead argument is a pointer to the VSprite structure to be used as the GEL list head. (You must allocate an actual VSprite structure for vsHead to point to.) The vsTail argument is a pointer to the VSprite structure to be used as the GEL list tail. (You must allocate an actual VSprite structure for vsTail to point to.) The gInfo argument is a pointer to the GelsInfo structure to be initialized.

InitGels() forms these structures into a linked list of GELs that is empty except for these two dummy elements (the head and tail). It gives the head VSprite the maximum negative x and y positions and the tail VSprite the maximum positive x and y positions. This is to aid the system in keeping the list sorted by x, y values, so GELs that are closer to the top and left of the display are nearer the head of the list. The memory space that the VSprites and Gelsinfo structures take up must already have been allocated. This can be done either by declaring them statically or explicitly allocating memory for them.

Once the GelsInfo structure has been allocated and initialized, GELs can be added to the system. Refer to the setupGelSys() and cleanupGelsys() functions in the "animtools.c" lisitng at the end of the article for examples of allocating, initializing and freeing a GelsInfo structure.

Collisions and GEL Structure Extensions

This section covers two topics that are applicable to all GELs: how to extend GEL data structures for your own purposes and how to detect collisions between GELs and other graphics objects.

Detecting GEL Collisions

All GELs, including VSprites, can participate in the software collision detection features of the graphics library. Simple Sprites must use hardware collision detection. See the Amiga Hardware Reference Manual for information about hardware collision detection.

Two kinds of collisions are handled by the system routines: GEL-to-boundary hits and GEL-to-GEL hits. You can set up as many as 16 different routines to handle different collision combinations; one routine to handle the boundary hits, and up to fifteen more to handle different inter-GEL hits.

You supply the actual collision handling routines, and provide their addresses to the system so that it can call them as needed (when the hits are detected). These addresses are kept in a collision handler table pointed to by the CollHandler field of the GelsInfo list. Which routine is called depends on the 16-bit MeMask and HitMask members of the VSprite structures involved in the collision.

When you call DoCollision(), the system goes through the GelsInfo list which, is constantly kept sorted by x,y position. If a GEL intersects the display boundaries and the GELs HitMask indicates it is appropriate, the boundary collision routine is called. When DoCollision() finds that two GELs overlap, it compares the MeMask of one with the HitMask of the other. If corresponding bits are set in both, it calls the appropriate inter-GEL collision routine at the table position corresponding to the bits in the HitMask and MeMask, as outlined below.

Preparing for Collision Detection

Before you can use the system to detect collisions between GELS, you must allocate and initialize a table of collision-detection routines and place the address of the table in the GelsInfo.CollHandler field. This table is an array of pointers to the actual routines that you have provided for your collision types. You must also prepare some members of the VSprite structure: CollMask, BorderLine, HitMask, and MeMask.

Building a Table of Collision Routines

The collision handler table is a structure, CollTable, defined in <graphics/gels.h>. It is accessed as the CollHandler member of the GelsInfo structure. The table only needs to be as large as the number of bits for which you wish to provide collision processing. It is safest, though, to allocate space for all 16 entries, considering the small amount of space required.

Call the routine SetCollision() to initialize the table entries that correspond to the HitMask and MeMask bits that you plan to use. Do not set any of the table entries directly, instead give the address to SetCollision() routine and let it handle the set up of the GelsInfo.CollTable field.

For example, SetCollision() could be called as follows:

ULONG            num;
VOID           (*routine)();
struct GelsInfo *GInfo;
 
VOID myCollisionRoutine(GELA, GELB)   /* sample collision routine */
struct VSprite *GELA;
struct VSprite *GELB;
{
    /* process GELs here - GELA and GELB point to the base VSprites of */
    /* the GELs, you can use the user extensions to identify what hit  */
    /* (if you need the info).                                         */
}
 
/* GelsInfo must be allocated and initialized */
 
routine = myCollisionRoutine;
 
IGraphics->SetCollision(num, routine, GInfo)

The num argument is the collision table vector number (0-15). The (*routine)() argument is a pointer to your collision routine. And the GInfo argument is a pointer to the GelsInfo structure.

VSprite Collision Mask

The CollMask member of the VSprite is a pointer to a memory area allocated for holding the collision mask of that GEL. This area must be in Chip memory and its size is the equivalent of one bitplane of the GEL's image. The collision mask is usually the same as the shadow mask of the GEL, formed from a logical-OR combination of all planes of the image. The following figure shows an example collision mask.

A Collision Mask

Alternatively, you may have a collision mask that is not derived from the image. In this case, the actual image isn't relevant. The system will not register collisions unless the other objects touch the collision mask. If the collision mask is smaller than the image, other objects will pass through the edges without a collision.

VSprite BorderLine

For faster collision detection, the system uses the BorderLine member of the VSprite structure. BorderLine specifies the location of the horizontal logical-OR combination of all of the bits of the object. It may be compared to taking the whole object's shadow/collision mask and squishing it down into a single horizontal line. You provide the system with a place to store this line. The size of the data area you allocate must be at least as large as the image width.

In other words, if it takes three 16-bit words to hold one line of a GEL, then you must reserve three words for the BorderLine. In the VSprite examples, the routine makeVSprite() correctly allocates and initializes the collision mask and borderline. For example:

WORD myBorderLineData[3];    /* reserve space for BorderLine for this Bob */
 
myVSprite.BorderLine = myBorderLineData;   /* tell the system where it is */

Here is a sample of an object and its BorderLine image:

011000001100    Object
001100011000
001100011000
000110110000
000010100000

011110111100    BorderLine image

Using this squished image, the system can quickly determine if the image is touching the left or rightmost boundary of the drawing area.

To establish default BorderLine and CollMask data, call the InitMasks() function.

VSprite HitMask and MeMask

Software collision detection is independently enabled and disabled for each GEL. Further, you can specify which of 16 possible collision routines you wish to have automatically executed. DoCollision(), in addition to sensing an overlap between objects, uses these masks to determine which routine (if any) the system will call when a collision occurs.

When the system determines a collision, it performs a logical-AND of the HitMask of the upper-leftmost object in the colliding pair with the MeMask of the lower-rightmost object of the pair. The bits that are 1s after the logical-AND operation choose which one of the 16 possible collision routines to perform.

  • If the collision is with the boundary, bit 0 is always a 1 and the system calls the collision handling routine number 0. Always assign the routine that handles boundary collisions to vector 0 in the collision handling table.
  • The system uses the flag called BORDERHIT to indicate that an object has landed on or moved beyond the outermost bounds of the drawing area (the edge of the clipping region). The VSprite example earlier in this article uses collision detection to check for border hits.
  • If any one of the other bits (1 to 15) is set, then the system calls your collision handling routine corresponding to the bit set.
  • If more than one bit is set in both masks, the system calls the vector corresponding to the rightmost (the least significant) bit only.

Using HitMask and MeMask

This section illustrates the use of the HitMask and MeMask to define one type of collision.

Suppose there are two classes of objects that you wish to control on the display: ENEMYTANK and MYMISSILE. Objects of class ENEMYTANK should be able to pass across one another without registering any collisions. Objects of class MYMISSILE should also be able to pass across one another without collisions. However, when MYMISSILE and ENEMYTANK collide, the system should generate a call to a collision routine.

Choose a pair of collision detect bits not yet assigned within MeMask, one to represent ENEMYTANK, the other to represent MYMISSILE. You will use the same two bits in the corresponding HitMask. In this example, bit 1 represents ENEMYTANK objects and bit 2 represents MYMISSLE objects.

MeMask HitMask
Bit Number 2 1 2 1
ENEMYTANK 1 0 1 1 0
ENEMYTANK 2 0 1 1 0
MYMISSLE 1 0 0 1

In the MeMask, bit 1 is set to indicate that this object is an ENEMYTANK. Bit 2 is clear (zero) indicating this object is not a MYMISSILE object. In the HitMask for ENEMYTANK objects, bit 1 is clear (zero) which means, "I will not register collisions with other ENEMYTANK objects." However, bit 2 is set (one) which means, "I will register collisions with MYMISSILE objects."

Thus when a call to DoCollision() occurs, for any objects that appear to be colliding, the system ANDs the MeMask of one object with the HitMask of the other object. If there are non-zero bits present, the system will call one of your collision routines.

In this example, suppose that the system senses a collision between ENEMYTANK 1 and ENEMYTANK 2. Suppose also that ENEMYTANK 1 is the top/leftmost object of the pair. Here is the way that the collision testing routine performs the test to see if the system will call any collision-handling routines:

Bit Number 2 1
ENEMYTANK 1 MeMask 0 1
ENEMYTANK 2 HitMask 1 0
Result of logical-AND 0 0

Therefore, the system does not call a collision routine. But suppose that DoCollision() finds an overlap between ENEMYTANK 1 and MYMISSILE, where MYMISSILE is the top/leftmost of the pair:

Bit Number 2 1
MYMISSLE MeMask 1 0
ENEMYTANK 2 HitMask 1 0
Result of logical-AND 1 0

Therefore, the system calls the collision routine at position 2 in the table of collision-handling routines.

Setting up for Boundary Collisions

To specify the region in the playfield that the system will use to define the outermost limits of the GEL boundaries, you use these GelsInfo members: topmost, bottommost, leftmost, and rightmost.

The DoCollision() routine tests these boundaries when determining boundary collisions within this RastPort. They have nothing whatsoever to do with graphical clipping. Graphical clipping makes use of the RastPort's clipping rectangle.

Here is a typical program segment that assigns the members correctly (for boundaries 50, 100, 80, 240). It assumes that you already have a RastPort structure pointer named myRastPort.

myRastPort->GelsInfo->topmost    = 50;
myRastPort->GelsInfo->bottommost = 100;
myRastPort->GelsInfo->leftmost   = 80;
myRastPort->GelsInfo->rightmost  = 240;

Parameters To Your Boundary Collision Routine

During the operation of the DoCollision() routine, if you have enabled boundary collisions for a GEL (by setting the least significant bit of its HitMask) and it has crossed a boundary, the system calls the boundary routine you have defined. The system will call the routine once for every GEL that has hit, or gone outside of the boundary. The system will call your routine with the following two arguments:

  • A pointer to the VSprite structure of the GEL that hit the boundary
  • A flag word containing one to four bits set, representing top, bottom, left and right boundaries, telling you which of the boundaries it has hit or exceeded.
  • To test these bits, compare to the constants TOPHIT, BOTTOMHIT, LEFTHIT, and RIGHTHIT.

See the VSprite example given earlier for an example of using boundary collision.

Parameters To Your Inter-GEL Collision Routines

If, instead of a GEL-to-boundary collision, DoCollision() senses a GEL-to-GEL collision, the system calls your collision routine with the following two arguments:

  • Address of the VSprite that is the uppermost (or leftmost if y coordinates are identical) GEL of a colliding pair.
  • Address of the VSprite that is the lowermost (or rightmost if y coordinates are identical) GEL of the pair.

Handling Multiple Collisions

When multiple elements collide within the same display field, the following set of sequential calls to the collision routines occurs:

  • The system issues each call in a sorted order for GELs starting at the upper left-hand corner of the screen and proceeding to the right and down the screen.
  • For any colliding GEL pair, the system issues only one call, to the collision routine for the object that is the topmost and leftmost of the pair.

Adding User Extensions to GEL Data Structures

This section describes how to expand the size and scope of the VSprite, Bob and AnimOb data structures. In the definition for these structures, there is an item called UserExt at the end of each. If you want to expand these structures (to hold your own special data), you define the UserExt member before the <graphics/gels.h> file is included. If this member has already been defined when the <graphics/gels.h> file is parsed, the compiler preprocessor will extend the structure definition automatically. If these members have not been defined, the system will make them SHORTs, and you may still consider these as being reserved for your private use.

To show the kind of use you can make of this feature, the example below adds speed and acceleration figures to each GEL by extending the VSprite structure. When your collision routine is called, it could use these values to transfer energy between the two colliding objects (say, billiard balls). You would have to set up additional routines, executed between calls to DoCollision(), that would add the values to the GELs position appropriately. You could do this with code similar o the following:

struct myInfo
    {
    short xvelocity;
    short yvelocity;
    short xaccel;
    short yaccel;
    };

These members are for example only. You may use any definition for your user extensions. You would then also provide the following line, to extend the VSprites structure, use:

/* Redefine VUserStuff for my own use. */
#define VUserStuff struct myInfo

To extend the Bobs structure, use:

#define BUserStuff struct myInfo

To extend the AnimObs structure, use:

#define AUserStuff struct myInfo

When the system is compiling the <graphics/gels.h> file with your program, the compiler preprocessor substitutes "struct myInfo" everywhere that UserExt is used in the header. The structure is thereby customized to include the items you wish to associate with it.

Typedef Cannot Be Used
You cannot use the C-language construct typedef for the above statements. If you want to substitute your own data type for one of the UserStuff variables, you must use a #define.

Animation with GELs

An animation sequence is composed of a series of drawings. Each drawing differs from the preceding one so that when they are arranged in a stack and viewed sequentially, the images appear to flow naturally.

In classic film animation, image drawing is done in two stages. The background for each scene is painted just once. Then, the cartoon characters and any other foreground objects are painted on transparent sheets of celluloid called cells which are placed over the background. With cells, animation can be achieved by redrawing only the parts of the scene that move while the background stays the same. Animation on the Amiga works similarly. The background is formed by the playfield while the objects that move can be conveniently handled with the GELs system.

Animation Data Structures

There are two main data structures involved in Amiga animation: AnimComp and AnimOb.

The AnimComp (Animation Component), is an extension of the Bob structure discussed in the previous section. An AnimComp provides a convenient way to link together a series of images so that they can be sequenced automatically, and so multiple sequences can be grouped together. An AnimComp is analogous to one sheet of celluloid representing a single image to be placed over the background.

struct AnimComp
{
    WORD Flags;                 /* AnimComp flags for system & user */
    WORD Timer;
    WORD TimeSet;
    struct AnimComp  *NextComp;
    struct AnimComp  *PrevComp;
    struct AnimComp  *NextSeq;
    struct AnimComp  *PrevSeq;
    WORD (*AnimCRoutine)();
    WORD YTrans;
    WORD XTrans;
    struct AnimOb    *HeadOb;   /* Pointer back to the controlling AnimOb     */
    struct Bob       *AnimBob;  /* Underlying Bob structure for this AnimComp */
};

The AnimComp structure contains pointers, PrevSeq and NextSeq, that lets you group these cells into stacks that will be viewed sequentially. The AnimComp structure also has PrevComp and NextComp pointers that let you group stacks into complex objects containing multiple independently moving parts.

The second animation data structure is the AnimOb which provides the variables needed for overall control over a group of AnimComps. The AnimOb itself contains no imagery; it simply provides a common reference point for the sequenced images and specifies how the system should move that point.

struct AnimOb
{
    struct AnimOb    *NextOb, *PrevOb;
    LONG Clock;
    WORD AnOldY, AnOldX;            /* old y,x coordinates          */
    WORD AnY, AnX;                  /* y,x coordinates of the AnimOb*/
    WORD YVel, XVel;                /* velocities of this object    */
    WORD YAccel, XAccel;            /* accelerations of this object */
    WORD RingYTrans, RingXTrans;    /* ring translation values      */
    WORD (*AnimORoutine)();         /* address of user procedure    */
    struct AnimComp  *HeadComp;     /* pointer to first component   */
    AUserStuff AUserExt;            /* AnimOb user extension        */
};

These structures can be used in various ways. A simple animation of a rotating ball could be created with three or four AnimComps linked together in a circular list by their NextSeq and PrevSeq fields. The system displays the initial AnimComp (the "top of the stack"), then switches to the AnimComp pointed to by NextSeq, and then switches to its NextSeq and so on until it reaches the end of the sequence. The sequence starts over again automatically if the last AnimComp.NextSeq points back to the first AnimComp in the stack.

For a more complex animation of a walking human, you could use five stacks, i.e., five circular lists of AnimComps; four stacks for the arms and legs and a single stack for the head and torso. To group these stacks into one cohesive unit showing a human figure, you use the PrevComp and NextComp pointers in the AnimComp structure. All the stacks would also share a common AnimOb, so that the combined sequences can be moved as a single object.

Linking AnimComps For a Multiple Component AnimOb

Animation Types

The GELs system provides several ways of setting up automatic animation, loosely based on some categories of movement in real life. Some things (like balls or arrows) can move independently of the background, and look even more realistic if they tumble or rotate as they move; other things (like worms, wheels, and people) must be anchored to the background, or they will appear to slide unnaturally.

The system software allows these types of animation through simple motion control, motion control with sequenced drawing, and sequenced drawing using Ring Motion Control.

Simple Motion Control

To produce motion of a simple object, such as a ball, the object is simply moved relative to a background display, a little at a time. This is simple motion control, and can be accomplished with one AnimComp and one AnimOb, by simply changing the AnimOb's position every N video frames. The apparent speed of the object is a combination of how often it is moved (every frame, every other frame, etc.) and how far it is moved (how much the AnimOb's AnX and AnY are changed).

Sequenced Drawing

To make the ball appear to rotate is a little more complex. To produce apparent movement within the image, sequencing is used. This is done by having a stack of AnimComp's that are laid down one after the other, a frame at a time. The stack can be arranged in a circular list for repeating movement. So, when you combine a sequence of drawings using AnimComps with simple motion control using an AnimOb, you can perform more complex animations such as having a rotating ball bounce around.

Ring Motion Control

Making a worm appear to crawl is similar to the rotating ball. There is still a stack of AnimComps that are sequenced automatically, and one controlling AnimOb. But each AnimCop image is drawn so that it appears to move relative to an internal point that remains stationary throughout the stack. So instead of the Anim's common reference point moving in each frame, you tell the system how far to move only at the end of each AnimComp sequence.

Ring Motion Control

As illustrated in the figure, the sequence of events for Ring Motion Control look like this:

Draw AnimComp1, Draw AnimComp2, Draw AnimComp3, Move AnimOb,
Draw AnimComp1, Draw AnimComp2, Draw AnimComp3, Move AnimOb,
Draw AnimComp1 ...

Specifying Animation Components

For each AnimComp, you initially specify:

  • A pointer to the AnimComp's controlling AnimOb.
  • Initial and alternate views, their timing and order.
  • The initial inter-component drawing priorities (for multiple AnimComp sequences, this specifies which sequence to display frontmost).
  • A pointer to a special animation routine related to this component (optional).
  • Your own extensions to this structure (optional).

Sequencing AnimComps

To specify the sequencing of AnimComp images, the pointers called PrevSeq and NextSeq are used to build a doubly-linked list. The sequence can be made circular (and usually is) by linking the first and last AnimComps in the sequence: the NextSeq of the last AnimComp must point back to the first AnimComp, and the PrevSeq of the first AnimComp must point to the last AnimComp. If the list is a loop, then the system will continue to cycle through the list until it is stopped. If the list is not a loop, then the program must act to restart the sequence after the last item is displayed. The AnimCRoutine field of the last AnimComp can be used to do this.

Position of an AnimComp

To specify the placement of each AnimComp relative to its controlling AnimOb, you set the AnimComp members XTrans and YTrans. These values can be positive or negative.

The system is designed so that only one of the AnimComps in any given sequence is "active" (being displayed) at a given point in time. It is the only image in the sequence that is (or is about to be) linked into the GelsInfo list. The Timer determines how long each Component in the sequence remains active, as described below.

Specifying Time for Each Image

The AnimComp members Timer and TimeSet are used to specify how long the system should keep each sequential image on the screen.

When the system makes an animation component active, it copies the value you have put in the TimeSet member into the Timer member. As the animation proceeds, the system decrements Timer; as long as it is greater than zero, then that AnimComp remains active. When the Timer value reaches zero, the system makes the next AnimComp in the sequence active, and the process repeats.

If you initialize the value in TimeSet to zero, the system will not sequence this component at all (and Timer will remain zero).

Linking Multiple AnimComp Sequences

When an AnimOb is built from multiple AnimComp sequences, the sequences are linked together by the the PrevComp and NextComp fields of the AnimComps. These pointers must be initialized only in the initial AnimComp of each sequence. The other components that are not initially active should have their PrevComp and NextComp pointers set to NULL.

Do Not Use Empty Fields
You cannot store data in the empty PrevComp and NextComp fields. As the system cycles through the AnimComps, the NextComp and PrevComp fields are set to NULL when an old AnimComps is replaced by a new AnimComp. The new AnimComp is then linked in to the list of sequences in place of the old one.

Component Ordering

The PrevSeq, NextSeq, PrevComp and NextComp linkages have no bearing on the order in which AnimComps in any given video frame are drawn. To specify the inter-component priorities (so that the closest objects appear frontmost) the Before and After pointers in the initially active AnimComp's underlying Bob structure are linked in to the rest of the system, as described previously in the discussion of Bobs.

This setup needs to be done once, for the initially active AnimComps of the AnimOb only.

The animation system adjusts the Before and After pointers of all the underlying Bob structures to constantly maintain the inter-component drawing sequence, even though different components are being made active as sequencing occurs.

These pointers also assure that one complete object always has priority over another object. The Bob Before and After pointers are used to link together the last AnimComp's Bob of one AnimOb to the first AnimComp's Bob of the next AnimOb.

Specifying the Animation Object

For each AnimOb, you initially specify:

  • The starting position of this object
  • Its velocity and acceleration (optional).
  • A pointer to the first of its AnimComps.
  • A pointer to a special animation routine related to this object (optional).
  • Your own extensions to this structure (optional).

Linking the AnimComp Sequences to the AnimOb

Within each AnimOb there may be one or more AnimComp sequences. The HeadComp of the AnimOb points to the first AnimComp in the list of sequences.

Each sequence is identified by its "active" AnimComp. There can only be one active AnimComp in each sequence. The sequences are linked together by their active AnimComps; for each of these the NextComp and PrevComp fields link the sequences together to create a list. The first sequence in the list (HeadComp of the AnimOb), has its PrevComp set to NULL. The last sequence in the list has its NextComp set to NULL. None of the inactive AnimComps should have NextComp or PrevComp fields set.

To find the active AnimComp at run time, you can look in the AnimOb's HeadComp field. To find the active AnimComp from any another AnimComp, use the HeadOb field to find the controlling AnimOb first and then look in its HeadComp field to find the active AnimComp.

The figure below shows all the linkages in data structures needed to create the animation GELs.

Linking of an AnimOb

Position of an AnimOb

To position the object and its component parts, use the AnimOb structure members AnX and AnY. The following figure illustrates how each component has its own offset from the AnimOb's common reference point.

Specifying an AnimOb Position

When you change the animation object's AnX and AnY, all of the component parts will be redrawn relative to it the next time DrawGList() is called.

Setting Up Simple Motion Control

In this form of animation, you can specify objects that have independently controllable velocities and accelerations in the X and Y directions. Components can still sequence.

The variables that control this motion are located in the AnimOb structure and are called:

YVel, XVel
the velocities in the y and x directions. These values are added to the position values on each call to Animate() (see below).
YAccel, XAccel
the accelerations in the x and y directions. These values are added to the velocity values on each call to Animate() (see below). The velocity values are updated before the position values.

Setting Up Ring Motion Control

To make a given component trigger a move of the AnimOb you set the RINGTRIGGER bit of that AnimComp's Flags field.

When the system software encounters this flag, it adds the values of RingXTrans and RingYTrans to the AnX and AnY values of the controlling AnimOb. The next time you execute DrawGList(), the drawing sequence will use the new position.

You usually set RINGTRIGGER in only one of the animation components in a sequence (the last one); however, you can use this flag and the translation variables any way you wish.

Using Sequenced Drawing and Motion Control

If you are using Ring Motion Control, you will probably set the velocity and acceleration variables to zero. For instance, consider the example of a person walking. With Ring Motion Control, as each foot falls it is positioned on the ground exactly where originally drawn. If you included a velocity value, the person's foot would not be stationary with respect to the ground, and the person would appear to "skate" rather than walk. If you set the velocity and acceleration variables at zero, you avoid this problem.

When the system activates a new AnimComp, it checks the Flags field to see if the RINGTRIGGER bit is set. If so, the system adds RingYTrans and RingXTrans to AnY and AnX respectively.

The AnimKey

The system uses one pointer, known as the AnimKey, to keep track of all the AnimObs via their PrevOb and NextOb linkage fields. The AnimKey acts as the anchor for the list of AnimObs you are using and is initialized with code such as the following:

struct AnimOb *animKey;

InitAnimate(&animKey);  /* Only do this once to initialize the AnimOb list */

As each new object is added (via AddAnimOb()), it is linked in at the beginning of the list, so AnimKey will always point to the object most recently added to the list. To search forward through the list, start with the AnimKey and move forward on the NextOb link. Continue to move forward until the NextOb is NULL, indicating the end of the list. The PrevOb link will allow you to move back to a previous object.

Set Up PrevOb and NextOb Correctly
It is important that the NextOb link of the last object is NULL, and that the PrevOb of the first object is NULL. In fact, the system expects the animation object lists to be exactly the way that they are described above. If they are not, the system will produce unexpected results.

Adding Animation Objects

Use the routine AddAnimOb() to add animation objects to the controlled object list. This routine will link the PrevOb and NextOb pointers to chain all the AnimObs that the system is controlling.

struct RastPort myRPort;
struct AnimOb myAnimOb;
struct AnimOb *animKey;  /* Must be initialized with InitAnimate() */

AddAnimOb(&myAnimOb, &animKey, &myRPort);

Moving the Objects

When you have defined all of the structures and have established all of the links, you can call the Animate() routine to move the objects. Animate() adjusts the positions of the objects as described above, and calls the various subroutines (AnimCRoutines and AnimORoutines) that you have specified.

After the system has completed the Animate() routine, some GELs may have been moved, so the GelsInfo list order may possibly be incorrect. Therefore, the list must be re-sorted with SortGList() before passing it to a system routine.

If you are using collision detection, you then perform DoCollision(). Your collision routines may also have an effect on the relative position of the GELs. Therefore, you should again call SortGList() to assure that the system correctly orders the objects before you call DrawGList().

When you call DrawGList(), the system renders all the GELs it finds in the GelsInfo list and any changes caused by the previous call to Animate() can then be seen.

This is illustrated in the following typical call sequence:

struct AnimOb **myAnimKey;
struct RastPort *rp;
struct ViewPort *vp;

/* ... setup of graphics elements and objects */

Animate(myAnimKey, rp);       /* "move" objects per instructions */
SortGList(rp);                /*  put them in order */
DoCollision(rp);              /*  software collision detect/action */
SortGList(rp);                /*  put them back into right order */
DrawGList(vp, rp);            /*  draw into current RastPort */

Your own Animation Routine Calls

The AnimOb and AnimComp structures can include pointers for your own routines that you want the system to call. These pointers are stored in the AnimOb’s AnimORoutine field and in the AnimComp's AnimCRoutine field, respectively.

When Animate() is called, the system performs the following steps for every AnimOb in the AnimKey list:

  • Updates the AnimOb's location and velocities.
  • Calls the AnimOb.AnimORoutine routine if one is supplied.
  • Then for each AnimComp of the AnimOb:
    • If this sequence times out, switches to the new AnimComp.
    • Calls the AnimComp.AnimCRoutine if one is supplied.
    • Sets the underlying VSprite's x,y coordinates.

If you want a routine to be called, you put the address of the routine in either AnimComp.AnimCRoutine or AnimOb.AnimORoutine member as needed. If no routine is to be called, you must set these fields to NULL. Your routines will be passed one parameter, a pointer to the AnimOb or AnimComp it was related to. You can use the user structure extensions discussed earlier to hold the variables you need for your own routines.

For example, if you provide a routine such as this:

VOID MyOCode(struct AnimOb *anOb)
{
/* whatever needs to be done */
}

Then, if you put the address of the routine in an AnimOb structure:

myAnimOb.AnimORoutine = MyOCode;

MyOCode() will be called with the address of this AnimOb when Animate() processes this AnimOb.

Standard GEL Rules Still Apply

Before you use the animation system, you must have called the routine InitGels(). The section called "Bob Priorities" describes how the system maintains the list of GELs to draw on the screen according to their various data fields. The animation system selectively adds GELs to and removes GELs from this list of screen objects during the Animate() routine. On the next call to DrawGList(), the system will draw the GELs in the list into the selected RastPort.

Animations Special Numbering System

Velocities and accelerations can be either positive or negative. The system treats the velocity, acceleration and Ring values as fixed-point binary fractions, with the decimal point at position 6 in the word. That is: vvvvvvvvvv.ffffff where v stands for actual values that you add to the x or y (AnX, AnY) positions of the object for each call to Animate(), and f stands for the fractional part. By using a fractional part, you can specify the speed of an object in increments as precise as 1/64th of an interval.

If you set the value of XVel at 0x0001, it will take 64 calls to the Animate() routine before the system will modify the object's x coordinate position by a step of one.

The system constant ANFRACSIZE can be used to shift values correctly. So if you set the value to (1 << ANFRACSIZE), it will be set to 0x0040, the value required to move the object one step per call to Animate().

The system constant ANIMHALF can be used if you want the object to move every other call to Animate().

Each call you make to Animate() simply adds the value of XAccel to the current value of XVel, and YAccel to the current value of YVel, modifying these values accordingly.

"animtools.h" and "animtools.c"

Here is the listing of the "animtools.h" header file and the "animtools.c" link file used by the examples in this chapter. The makeSeq() and makeComp() subroutines here demonstrate how to use the GELs animation system.

/* animtools.h */
#ifndef GELTOOLS_H
#define GELTOOLS_H

/*
** These data structures are used by the functions in animtools.c to
** allow for an easier interface to the animation system.
*/

/* Data structure to hold information for a new VSprite.                */
typedef struct newVSprite {
        WORD           *nvs_Image;      /* image data for the vsprite   */
        WORD           *nvs_ColorSet;   /* color array for the vsprite  */
        SHORT           nvs_WordWidth;  /* width in words               */
        SHORT           nvs_LineHeight; /* height in lines              */
        SHORT           nvs_ImageDepth; /* depth of the image           */
        SHORT           nvs_X;          /* initial x position           */
        SHORT           nvs_Y;          /* initial y position           */
        SHORT           nvs_Flags;      /* vsprite flags                */
        USHORT          nvs_HitMask;    /* Hit mask.                    */
        USHORT          nvs_MeMask;     /* Me mask.                     */
        } NEWVSPRITE;

/* Data structure to hold information for a new Bob.                */
typedef struct newBob {
        WORD       *nb_Image;       /* image data for the bob       */
        SHORT       nb_WordWidth;   /* width in words               */
        SHORT       nb_LineHeight;  /* height in lines              */
        SHORT       nb_ImageDepth;  /* depth of the image           */
        SHORT       nb_PlanePick;   /* planes that get image data   */
        SHORT       nb_PlaneOnOff;  /* unused planes to turn on     */
        SHORT       nb_BFlags;      /* bob flags                    */
        SHORT       nb_DBuf;        /* 1=double buf, 0=not          */
        SHORT       nb_RasDepth;    /* depth of the raster          */
        SHORT       nb_X;           /* initial x position           */
        SHORT       nb_Y;           /* initial y position           */
        USHORT      nb_HitMask;     /* Hit mask.                    */
        USHORT      nb_MeMask;      /* Me mask.                     */
        } NEWBOB ;

/* Data structure to hold information for a new animation component.       */
typedef struct newAnimComp {
        WORD  (*nac_Routine)(); /* routine called when Comp is displayed.   */
        SHORT   nac_Xt;         /* initial delta offset position.           */
        SHORT   nac_Yt;         /* initial delta offset position.           */
        SHORT   nac_Time;       /* Initial Timer value.                     */
        SHORT   nac_CFlags;     /* Flags for the Component.                 */
        } NEWANIMCOMP;

/* Data structure to hold information for a new animation sequence.         */
typedef struct newAnimSeq {
        struct AnimOb  *nas_HeadOb; /* common Head of Object.               */
        WORD   *nas_Images;         /* array of Comp image data             */
        SHORT  *nas_Xt;             /* arrays of initial offsets.           */
        SHORT  *nas_Yt;             /* arrays of initial offsets.           */
        SHORT  *nas_Times;          /* array of Initial Timer value.        */
        WORD (**nas_Routines)();    /* Array of fns called when comp drawn  */
        SHORT   nas_CFlags;         /* Flags for the Component.             */
        SHORT   nas_Count;          /* Num Comps in seq (= arrays size)     */
        SHORT   nas_SingleImage;    /* one (or count) images.               */
        } NEWANIMSEQ;

#define INTUITIONNAME "intuition.library" /* intuitionbase.h does not define a library name. */

#include "animtools_proto.h"              /* Include Prototyping. */
#endif


/* animtools_proto.h */
#include        <clib/dos_protos.h>
#include        <clib/exec_protos.h>
#include        <clib/graphics_protos.h>
#include        <clib/intuition_protos.h>

struct GelsInfo *setupGelSys(struct RastPort *rPort, BYTE reserved);
VOID            cleanupGelSys(struct GelsInfo *gInfo, struct RastPort *rPort);
struct VSprite  *makeVSprite(NEWVSPRITE *nVSprite);
struct Bob      *makeBob(NEWBOB *nBob);
struct AnimComp *makeComp(NEWBOB *nBob, NEWANIMCOMP *nAnimComp);
struct AnimComp *makeSeq(NEWBOB *nBob, NEWANIMSEQ *nAnimSeq);
VOID            freeVSprite(struct VSprite *vsprite);
VOID            freeBob(struct Bob *bob, LONG rasdepth);
VOID            freeComp(struct AnimComp *myComp, LONG rasdepth);
VOID            freeSeq(struct AnimComp *headComp, LONG rasdepth);
VOID            freeOb(struct AnimOb *headOb, LONG rasdepth);


/* animtools.c
**
** This file is a collection of tools which are used with the VSprite, Bob and Animation
** system software. It is intended as a useful EXAMPLE, and while it shows what must be
** done, it is not the only way to do it.  If Not Enough Memory, or error return, each
** cleans up after itself before returning.  NOTE that these routines assume a very specific
** structure to the GEL lists.  Make sure that you use the correct pairs together
** (i.e. makeOb()/freeOb(), etc.)
**
** Compile with SAS/C 5.10b: lc -b1 -cfist -v -y -oanimtools.o animtools.c
*/
#include <exec/types.h>
#include <exec/memory.h>
#include <graphics/gfx.h>
#include <graphics/gels.h>
#include <graphics/clip.h>
#include <graphics/rastport.h>
#include <graphics/view.h>
#include <graphics/gfxbase.h>
#include "animtools.h"

/* Setup the GELs system.  After this call is made you can use VSprites, Bobs, AnimComps
** and AnimObs.  Note that this links the GelsInfo structure into the RastPort, and calls
** InitGels().  It uses information in your RastPort structure to establish boundary collision
** defaults at the outer edges of the raster.  This routine sets up for everything - collision
** detection and all. You must already have run LoadView before ReadyGelSys is called.
*/
struct GelsInfo *setupGelSys(struct RastPort *rPort, BYTE reserved)
{
struct GelsInfo *gInfo;
struct VSprite  *vsHead;
struct VSprite  *vsTail;

if (NULL != (gInfo = (struct GelsInfo *)AllocMem(sizeof(struct GelsInfo), MEMF_CLEAR)))
        {
        if (NULL != (gInfo->nextLine = (WORD *)AllocMem(sizeof(WORD) * 8, MEMF_CLEAR)))
            {
            if (NULL != (gInfo->lastColor = (WORD **)AllocMem(sizeof(LONG) * 8, MEMF_CLEAR)))
                {
                if (NULL != (gInfo->collHandler = (struct collTable *)
                        AllocMem(sizeof(struct collTable),MEMF_CLEAR)))
                    {
                    if (NULL != (vsHead = (struct VSprite *)
                            AllocMem((LONG)sizeof(struct VSprite), MEMF_CLEAR)))
                        {
                        if (NULL != (vsTail = (struct VSprite *)
                                AllocMem(sizeof(struct VSprite), MEMF_CLEAR)))
                            {
                            gInfo->sprRsrvd   = reserved;
                            /* Set left- and top-most to 1 to better keep items */
                            /* inside the display boundaries.                   */
                            gInfo->leftmost   = gInfo->topmost    = 1;
                            gInfo->rightmost  = (rPort->BitMap->BytesPerRow << 3) - 1;
                            gInfo->bottommost = rPort->BitMap->Rows - 1;
                            rPort->GelsInfo = gInfo;
                            InitGels(vsHead, vsTail, gInfo);
                            return(gInfo);
                            }
                        FreeMem(vsHead, (LONG)sizeof(*vsHead));
                        }
                    FreeMem(gInfo->collHandler, (LONG)sizeof(struct collTable));
                    }
                FreeMem(gInfo->lastColor, (LONG)sizeof(LONG) * 8);
                }
            FreeMem(gInfo->nextLine, (LONG)sizeof(WORD) * 8);
            }
        FreeMem(gInfo, (LONG)sizeof(*gInfo));
        }
return(NULL);
}


/* Free all of the stuff allocated by setupGelSys().  Only call this routine if
** setupGelSys() returned successfully.  The GelsInfo structure is the one returned
** by setupGelSys().   It also unlinks the GelsInfo from the RastPort.
*/
VOID cleanupGelSys(struct GelsInfo *gInfo, struct RastPort *rPort)
{
rPort->GelsInfo = NULL;
FreeMem(gInfo->collHandler, (LONG)sizeof(struct collTable));
FreeMem(gInfo->lastColor, (LONG)sizeof(LONG) * 8);
FreeMem(gInfo->nextLine, (LONG)sizeof(WORD) * 8);
FreeMem(gInfo->gelHead, (LONG)sizeof(struct VSprite));
FreeMem(gInfo->gelTail, (LONG)sizeof(struct VSprite));
FreeMem(gInfo, (LONG)sizeof(*gInfo));
}


/* Create a VSprite from the information given in nVSprite.  Use freeVSprite()
** to free this GEL.
*/
struct VSprite *makeVSprite(NEWVSPRITE *nVSprite)
{
struct VSprite *vsprite;
LONG            line_size;
LONG            plane_size;

line_size = sizeof(WORD) * nVSprite->nvs_WordWidth;
plane_size = line_size * nVSprite->nvs_LineHeight;

if (NULL != (vsprite = (struct VSprite *)AllocMem((LONG)sizeof(struct VSprite), MEMF_CLEAR)))
        {
        if (NULL != (vsprite->BorderLine = (WORD *)AllocMem(line_size, MEMF_CHIP)))
            {
            if (NULL != (vsprite->CollMask = (WORD *)AllocMem(plane_size, MEMF_CHIP)))
                {
                vsprite->Y          = nVSprite->nvs_Y;
                vsprite->X          = nVSprite->nvs_X;
                vsprite->Flags      = nVSprite->nvs_Flags;
                vsprite->Width      = nVSprite->nvs_WordWidth;
                vsprite->Depth      = nVSprite->nvs_ImageDepth;
                vsprite->Height     = nVSprite->nvs_LineHeight;
                vsprite->MeMask     = nVSprite->nvs_MeMask;
                vsprite->HitMask    = nVSprite->nvs_HitMask;
                vsprite->ImageData  = nVSprite->nvs_Image;
                vsprite->SprColors  = nVSprite->nvs_ColorSet;
                vsprite->PlanePick  = vsprite->PlaneOnOff = 0x00;
                InitMasks(vsprite);
                return(vsprite);
                }
            FreeMem(vsprite->BorderLine, line_size);
            }
        FreeMem(vsprite, (LONG)sizeof(*vsprite));
        }
return(NULL);
}


/* Create a Bob from the information given in nBob.  Use freeBob() to free this GEL.
** A VSprite is created for this bob.  This routine properly allocates all double
** buffered information if it is required.
*/
struct Bob *makeBob(NEWBOB *nBob)
{
struct Bob         *bob;
struct VSprite     *vsprite;
NEWVSPRITE          nVSprite ;
LONG                rassize;

rassize = (LONG)sizeof(UWORD) * nBob->nb_WordWidth * nBob->nb_LineHeight * nBob->nb_RasDepth;

if (NULL != (bob = (struct Bob *)AllocMem((LONG)sizeof(struct Bob), MEMF_CLEAR)))
        {
        if (NULL != (bob->SaveBuffer = (WORD *)AllocMem(rassize, MEMF_CHIP)))
            {
            nVSprite.nvs_WordWidth  = nBob->nb_WordWidth;
            nVSprite.nvs_LineHeight = nBob->nb_LineHeight;
            nVSprite.nvs_ImageDepth = nBob->nb_ImageDepth;
            nVSprite.nvs_Image      = nBob->nb_Image;
            nVSprite.nvs_X          = nBob->nb_X;
            nVSprite.nvs_Y          = nBob->nb_Y;
            nVSprite.nvs_ColorSet   = NULL;
            nVSprite.nvs_Flags      = nBob->nb_BFlags;
            /* Push the values into the NEWVSPRITE structure for use in makeVSprite(). */
            nVSprite.nvs_MeMask     = nBob->nb_MeMask;
            nVSprite.nvs_HitMask    = nBob->nb_HitMask;

            if ((vsprite = makeVSprite(&nVSprite)) != NULL)
                {
                vsprite->PlanePick = nBob->nb_PlanePick;
                vsprite->PlaneOnOff = nBob->nb_PlaneOnOff;
                vsprite->VSBob   = bob;
                bob->BobVSprite  = vsprite;
                bob->ImageShadow = vsprite->CollMask;
                bob->Flags       = 0;
                bob->Before      = NULL;
                bob->After       = NULL;
                bob->BobComp     = NULL;

                if (nBob->nb_DBuf)
                    {
                    if (NULL != (bob->DBuffer = (struct DBufPacket *)
                            AllocMem((LONG)sizeof(struct DBufPacket), MEMF_CLEAR)))
                        {
                        if (NULL != (bob->DBuffer->BufBuffer = (WORD *)AllocMem(rassize, MEMF_CHIP)))
                            return(bob);
                        FreeMem(bob->DBuffer, (LONG)sizeof(struct DBufPacket));
                        }
                    }
                else
                    {
                    bob->DBuffer = NULL;
                    return(bob);
                    }
                freeVSprite(vsprite);
                }
            FreeMem(bob->SaveBuffer, rassize);
            }
        FreeMem(bob, (LONG)sizeof(*bob));
        }
return(NULL);
}


/*
** Create a Animation Component from the information given in nAnimComp and nBob.  Use
** freeComp() to free this GEL.  makeComp() calls makeBob(), and links the Bob into an AnimComp.
*/
struct AnimComp *makeComp(NEWBOB *nBob, NEWANIMCOMP *nAnimComp)
{
struct Bob      *compBob;
struct AnimComp *aComp;

if ((aComp = AllocMem((LONG)sizeof(struct AnimComp),MEMF_CLEAR)) != NULL)
        {
        if ((compBob = makeBob(nBob)) != NULL)
            {
            compBob->After   = compBob->Before  = NULL;
            compBob->BobComp = aComp;   /* Link 'em up. */
            aComp->AnimBob      = compBob;
            aComp->TimeSet      = nAnimComp->nac_Time; /* Num ticks active. */
            aComp->YTrans       = nAnimComp->nac_Yt; /* Offset rel to HeadOb */
            aComp->XTrans       = nAnimComp->nac_Xt;
            aComp->AnimCRoutine = nAnimComp->nac_Routine;
            aComp->Flags        = nAnimComp->nac_CFlags;
            aComp->Timer        = 0;
            aComp->NextSeq      = aComp->PrevSeq  = NULL;
            aComp->NextComp     = aComp->PrevComp = NULL;
            aComp->HeadOb       = NULL;
            return(aComp);
            }
        FreeMem(aComp, (LONG)sizeof(struct AnimComp));
        }
return(NULL);
}



/* Create an Animation Sequence from the information given in nAnimSeq and nBob.  Use
** freeSeq() to free this GEL.  This routine creates a linked list of animation components
** which make up the animation sequence.  It links them all up, making a circular list of
** the PrevSeq and NextSeq pointers. That is to say, the first component of the sequences'
** PrevSeq points to the last component; the last component of * the sequences' NextSeq
** points back to the first component.  If dbuf is on, the underlying Bobs will be set up
** for double buffering.  If singleImage is non-zero, the pImages pointer is assumed to
** point to an array of only one image, instead of an array of 'count' images, and all
** Bobs will use the same image.
*/
struct AnimComp *makeSeq(NEWBOB *nBob, NEWANIMSEQ *nAnimSeq)
{
int seq;
struct AnimComp *firstCompInSeq = NULL;
struct AnimComp *seqComp = NULL;
struct AnimComp *lastCompMade = NULL;
LONG image_size;
NEWANIMCOMP nAnimComp;

/* get the initial image.  this is the only image that is used
** if nAnimSeq->nas_SingleImage is non-zero.
*/
nBob->nb_Image = nAnimSeq->nas_Images;
image_size = nBob->nb_LineHeight * nBob->nb_ImageDepth * nBob->nb_WordWidth;

/* for each comp in the sequence */
for (seq = 0; seq < nAnimSeq->nas_Count; seq++)
        {
        nAnimComp.nac_Xt        = *(nAnimSeq->nas_Xt + seq);
        nAnimComp.nac_Yt        = *(nAnimSeq->nas_Yt + seq);
        nAnimComp.nac_Time      = *(nAnimSeq->nas_Times + seq);
        nAnimComp.nac_Routine   = nAnimSeq->nas_Routines[seq];
        nAnimComp.nac_CFlags    = nAnimSeq->nas_CFlags;
        if ((seqComp = makeComp(nBob, &nAnimComp)) == NULL)
            {
            if (firstCompInSeq != NULL)
                freeSeq(firstCompInSeq, (LONG)nBob->nb_RasDepth);
            return(NULL);
            }
        seqComp->HeadOb = nAnimSeq->nas_HeadOb;
        /* Make a note of where the first component is. */
        if (firstCompInSeq == NULL) firstCompInSeq = seqComp;
        /* link the component into the list */
        if (lastCompMade != NULL) lastCompMade->NextSeq = seqComp;
        seqComp->NextSeq = NULL;
        seqComp->PrevSeq = lastCompMade;
        lastCompMade = seqComp;
        /* If nAnimSeq->nas_SingleImage is zero, the image array has nAnimSeq->nas_Count images. */
        if (!nAnimSeq->nas_SingleImage)
            nBob->nb_Image += image_size;
        }
/* On The last component in the sequence, set Next/Prev to make */
/* the linked list a loop of components.                        */
lastCompMade->NextSeq = firstCompInSeq;
firstCompInSeq->PrevSeq = lastCompMade;

return(firstCompInSeq);
}


/* Free the data created by makeVSprite().  Assumes images deallocated elsewhere. */
VOID freeVSprite(struct VSprite *vsprite)
{
LONG    line_size;
LONG    plane_size;

line_size = (LONG)sizeof(WORD) * vsprite->Width;
plane_size = line_size * vsprite->Height;
FreeMem(vsprite->BorderLine, line_size);
FreeMem(vsprite->CollMask, plane_size);
FreeMem(vsprite, (LONG)sizeof(*vsprite));
}




/* Free the data created by makeBob().  It's important that rasdepth match the depth you */
/* passed to makeBob() when this GEL was made. Assumes images deallocated elsewhere.     */
VOID freeBob(struct Bob *bob, LONG rasdepth)
{
LONG    rassize =  sizeof(UWORD) * bob->BobVSprite->Width * bob->BobVSprite->Height * rasdepth;

if (bob->DBuffer != NULL)
        {
        FreeMem(bob->DBuffer->BufBuffer, rassize);
        FreeMem(bob->DBuffer, (LONG)sizeof(struct DBufPacket));
        }
FreeMem(bob->SaveBuffer, rassize);
freeVSprite(bob->BobVSprite);
FreeMem(bob, (LONG)sizeof(*bob));
}


/* Free the data created by makeComp().  It's important that rasdepth match the depth you */
/* passed to makeComp() when this GEL was made. Assumes images deallocated elsewhere.    */
VOID freeComp(struct AnimComp *myComp, LONG rasdepth)
{
freeBob(myComp->AnimBob, rasdepth);
FreeMem(myComp, (LONG)sizeof(struct AnimComp));
}


/* Free the data created by makeSeq().  Complimentary to makeSeq(), this routine goes through
** the NextSeq pointers and frees the Components.  This routine only goes forward through the
** list, and so it must be passed the first component in the sequence, or the sequence must
** be circular (which is guaranteed if you use makeSeq()).  It's important that rasdepth match
** the depth you passed to makeSeq() when this GEL was made.   Assumes images deallocated elsewhere!
*/
VOID freeSeq(struct AnimComp *headComp, LONG rasdepth)
{
struct AnimComp *curComp;
struct AnimComp *nextComp;

/* Break the NextSeq loop, so we get a NULL at the end of the list. */
headComp->PrevSeq->NextSeq = NULL;

curComp = headComp;         /* get the start of the list */
while (curComp != NULL)
        {
        nextComp = curComp->NextSeq;
        freeComp(curComp, rasdepth);
        curComp = nextComp;
        }
}


/* Free an animation object (list of sequences).  freeOb() goes through the NextComp
** pointers, starting at the AnimObs' HeadComp, and frees every sequence.  It only
** goes forward. It then frees the Object itself.  Assumes images deallocated elsewhere!
*/
VOID freeOb(struct AnimOb *headOb, LONG rasdepth)
{
struct AnimComp *curSeq;
struct AnimComp *nextSeq;

curSeq = headOb->HeadComp;          /* get the start of the list */
while (curSeq != NULL)
        {
        nextSeq = curSeq->NextComp;
        freeSeq(curSeq, rasdepth);
        curSeq = nextSeq;
        }
FreeMem(headOb, sizeof(struct AnimOb));
}

Function Reference

The following are brief descriptions of the Amiga's graphics animation functions. See the SDK for details on each function call.

Animation Function Description
AddAnimOb() Add an AnimOb to the linked list of AnimObs.
AddBob() Add a Bob to the current GEL list.
AddVSprite() Add a VSprite to the current GEL list.
Animate() Process every AnimOb in the current animation list.
ChangeSprite() Change the sprite image pointer.
DoCollision() Test every GEL in a GEL list for collisions.
DrawGList() Process the GEL list, queueing VSprites, drawing Bobs.
FreeGBuffers() Deallocate memory obtained by GetGBuffers().
FreeSprite() Return sprite for use by others and virtual sprite machine.
GetGBuffers() Attempt to allocate all buffers of an entire AnimOb.
GetSprite() Attempt to get a sprite for the simple sprite manager.
InitGels() Initialize a GEL list; must be called before using GELs.
InitGMasks() Initialize all of the masks of an AnimOb.
InitMasks() Initialize the BorderLine and CollMask masks of a VSprite.
MoveSprite() Move sprite to a point relative to top of ViewPort.
RemBob() Remove a Bob from the GEL list.
RemIBob() Immediately remove a Bob from the GEL list and the RastPort.
RemVSprite() Remove a VSprite from the current GEL list.
SetCollision() Set a pointer to a user collision routine.
SortGList() Sort the current GEL list, ordering its x,y coordinates.