The preceding version of the
sprite structure allowed for the display of the sprites on the screen,
but sprites that don’t animate aren’t that exciting. The sprites are
constantly displaying the same static image the whole time. Sprites are
commonly animated using a technique called frame-based animation. Frame-based animation
is equivalent to a movie reel, where the individual images or frames
are displayed in rapid succession giving the appearance of movement.
Sprite animation uses the same idea, quickly changing the image that the sprite displays to give the illusion of movement.
The first part of sprite animation is updating your sprites to support more than one image.
The Updated GameSprite Structure
Animating a sprite requires that the sprite support the ability to cycle through multiple frames; currently, the GameSprite
structure supports only a single frame. Adding animation support is
actually quite simple. The sprite needs to be able to track which image
it is currently displaying, as well as know how many images it can cycle
through.
The GameSprite structure needs to be updated by adding two new variables.
First, an integer called numFrames keeps track of the number of frames the sprite has available.
Second, an integer called curFrame is used to hold the current frame being displayed.
You can see an updated GameSprite structure next; the new variables are shown in bold.
// Sprite structure
typedef struct
{
// sprite dimensions
float width;
float height;
// sprite position
float posX;
float posY;
// sprite movement
float moveX;
float moveY;
BOOL visible;
// animation information
int curFrame; // the current frame of animation
int numFrames; // the number of frames in this animation
} GameSprite;
Now that you have two new variables in the GameSprite
structure, you need to set them to their default values when the
sprites are initialized. In the following code sample, the current frame
is set to 0 and the number of frames for the sprite is set to 4. In
most sprite systems, the sprites will be able to handle more than a
single series of animations; only a simple example is shown here.
// Loop through and init the active sprites
for (int curSprite = 0; curSprite < MAX_SPRITES; curSprite++)
{
// Set the width and height of the sprite
sprites[curSprite].width = 64;
sprites[curSprite].height = 64;
// Create and set a random x,y position
sprites[curSprite].posX = (float)(rand()%600);
sprites[curSprite].posY = (float)(rand()%450);
// This sprite is visible
sprites[curSprite].visible = TRUE;
// Set up the animation information
sprites[curSprite].curFrame = 0;
sprites[curSprite].numFrames = 4;
}
Updating the Sprite’s Animation
When
displaying the previous sprites, special image processing wasn’t
necessary. Now that the sprites have multiple frames that need to be
managed, an Update Sprites function is needed.
The UpdateSprites
function handles the incrementing of the current frame of animation, as
well as checking against the number of frames. If the current frame
were constantly incremented without comparing against the number of
frames, the sprite would quickly run out of images and cause an error in
your application. When the current frame reaches the maximum number of
frames, the current frame needs to be reset to 0 and the whole process
starts over again. This causes the sprite’s animation to loop
indefinitely.
The following UpdateSprites function loops through the sprites and updates their animations.
/*******************************************************************
* UpdateSprites
* Updates the sprite's animation information
* Inputs - none
* Outputs - void
*******************************************************************/
void UpdateSprites()
{
// Loop through the sprites and update the animation info
for (int i = 0; i < MAX_SPRITES; i++)
{
// only update visible sprites
if (sprites[i].visible)
{
// increment the current frame
sprites[i].curFrame++;
// if the current frame is past the number of frames
// reset to 0
if (sprites[i].curFrame >= sprites[i].numFrames)
{
sprites[i].curFrame = 0;
}
}
}
}
The UpdateSprites function can be inserted into the main application loop right after the Render function.
// Main message loop
MSG msg = {0};
while (WM_QUIT != msg.message)
{
// Process Windows messages first
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// update and render the current frame
UpdateScene();
Render();
// Update the sprites for the next frame
UpdateSprites();
}
Displaying the Animated Sprites
At this point the sprites are animating within the GameSprite structure but will still be displayed using only a single image. To remedy this, the UpdateScene function will need to be changed. If you recall, the job of the UpdateScene
function is to update the sprites in the sprite pool. Currently, those
sprites are constantly pointing to the same frame in their source
texture. The texture coordinates associated have to be updated based on
the current frame of animation.
When animating sprites, it
is common for the all the frames of an animation to appear in the same
texture. The frames are laid out horizontally in the texture, as
displayed in Figure 1.
When using this type of
texture, the texture coordinates in the X direction reference the
correct frame. The new X texture coordinate can be calculated in the UpdateScene
function by dividing the current frame number by the number of frames
in the animation. This will yield a value between 0 and 1.
Also, the texture size
should be updated to reflect the current frame. Not all frames are the
same size. The texture size is calculated by dividing the current frame
width by the width of the whole texture. In the following example, the
width of the texture is determined by multiplying the sprite width by
the number of frames. This only works because all frames in the sample
animation are the same size.
/*******************************************************************
* UpdateScene()
* Updates the scene with the current sprite information
* Inputs - void
* Outputs - void
*******************************************************************/
void UpdateScene()
{
D3DXMATRIX matScaling;
D3DXMATRIX matTranslation;
int curPoolIndex = 0;
// Loop through the sprites
for (int i = 0; i < MAX_SPRITES; i++)
{
// only update visible sprites
if (sprites[i].visible)
{
// set the proper scale for the sprite
D3DXMatrixScaling(&matScaling, sprites[i].width, sprites[i].height,
1.0f);
// Move the sprite to spritePosX, spritePosY
// SpriteWidth and SpriteHeight are divided by 2 to move the
// translation point to the top-left sprite corner instead of
// the center of the sprite.
D3DXMatrixTranslation(&matTranslation,
(float)sprites[i].posX + (sprites[i].width/2),
(float)(windowHeight - sprites[i].posY - (sprites[i].height/2)),
0.1f);
// Update the sprite's position and scale
spritePool[curPoolIndex].matWorld = matScaling * matTranslation;
// determine the texture coordinates for the current frame
spritePool[curPoolIndex].TexCoord.x = (float)(sprites[i].curFrame /
sprites[i].numFrames);
spritePool[curPoolIndex].TexCoord.y = 0.0f;
// Set the texture size for this frame
spritePool[curPoolIndex].TexSize.x = (float)(sprites[i].width /
(sprites[i].width * sprites[i].numFrames));
// Increment the pool index
curPoolIndex++;
}
}
// set the number of active sprites
numActiveSprites = curPoolIndex;
}