Drawing more than one sprite and keeping the code clean is going to
require a change to the way the sprite information is stored.
Previously, the sprite’s position and size were stored in a series of
global variables.
float spritePosX = 320;
float spritePosY = 240;
float spriteWidth = 64;
float spriteHeight = 64;
Since you’re going to need this information for multiple sprites, this information is going to be moved to a structure.
Defining a GameSprite Structure
The GameSprite
structure contains the sprite’s position and dimensions and is a much
cleaner way of storing this information. Additionally, a new Boolean
variable called visible is added. The visible
variable is used to track whether the sprite is currently able to be
seen on the screen. This enables the sprites to be shown or hidden. This
is useful if sprites were being used for bullets or other items that
have a limited lifetime.
// Sprite structure
typedef struct
{
// sprite dimensions
float width;
float height;
// sprite position
float posX;
float posY;
BOOL visible;
} GameSprite;
Since you’ll need multiple sprites, an array of GameSprite structures should be created. The small snippet below creates an array of ten GameSprite structures.
#define MAX_SPRITES 10
GameSprite sprites[MAX_SPRITES] = {0};
Initializing the GameSprite Structures
Initializing the sprite data in the GameSprite structure is very similar to how you set up one sprite. Create a loop to allow all the sprites to be set up at one time.
In the following code, the ten GameSprite structures are initialized with a size of 64 × 64 and a random screen position. Each of the sprites also has its visible variable set to TRUE.
// 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;
}
The Sprite Pool
Instead of associating a single D3DX10_SPRITE structure with each GameSprite, you’re going to employ a sprite pool. A sprite pool is an array of D3DX10_SPRITE structures where each structure is used on an as-needed basis. The structures are filled dynamically with information from the GameSprite objects each frame. These sprites are updated using the Update
function. By using a sprite pool, the amount of dynamic allocations is
kept down and places a restriction on the memory sprites can use.
Because the pool is
pre-allocated, the size of the sprite pool array needs to be large
enough to hold all the possible sprites that may be visible at one time.
In the following code, there is enough space for thirty-two sprites to
be allocated. An additional variable, numActiveSprites,
is also being declared here. Since the number of sprites visible on the
screen will be less than the number of available slots in the sprite
pool, it is best to keep track of the number of sprites to be drawn.
This variable comes in handy later when the function to draw the sprites
is called.
// Maximum number of sprites possible in the pool
#define NUM_POOL_SPRITES 32
// Create the sprite pool array
D3DX10_SPRITE spritePool[NUM_POOL_SPRITES];
// the number of active sprites
int numActiveSprites = 0;
Tip
Separating the game logic contained in the GameSprite from the rendering of the sprites in the pool allows for the drawing method to be changed without affecting the game logic.
Clearing the Sprites in the Pool
It is always a good idea to
clear out all the items in the sprite pool to a default value before
they’re used. Doing so keeps you from accidentally using garbage data.
// Loop through and set the defaults for the
// sprites in the pool
for (int i = 0; i < NUM_POOL_SPRITES; i++)
{
// Texture for this sprite to use
spritePool[i].pTexture = gSpriteTextureRV;
spritePool[i].TextureIndex = 0;
// top-left location in U,V coords
spritePool[i].TexCoord.x = 0;
spritePool[i].TexCoord.y = 0;
// Determine the texture size in U, V coordinates
spritePool[i].TexSize.x = 1;
spritePool[i].TexSize.y = 1;
spritePool[i].ColorModulate = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
}
All the sprites in the pool default to using the same texture in the pTexture variable.
Updating the Sprites
During the main loop, the information contained in each of the GameSprite structures needs to be copied to available sprites available in the sprite pool. Since the GameSprites do not have a specific sprite associated with them, they must be updated dynamically each frame. The UpdateScene
function below sets up each sprite in the sprite pool with the correct
information and keeps a running total of the number of sprites currently
active.
In the following code sample, only sprites that have their visible variable set to TRUE are being updated.
/*******************************************************************
* 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 sprites position and scale
spritePool[curPoolIndex].matWorld = matScaling * matTranslation;
// Increment the pool index
curPoolIndex++;
}
}
// set the number of active sprites
numActiveSprites = curPoolIndex;
}
The UpdateScene function should be called before the Render function in the main game loop.
Drawing More Than One Sprite
Now that you have multiple sprites created and updating, how do you draw them? Well, you remember before where I mentioned that DrawSpritesImmediate was capable of drawing more than one sprite? The first parameter to the DrawSpritesImmediate function is a pointer to an array of D3DX10_SPRITE structures. Because the sprite pool is an array of this type, it can be passed directly into the DrawSpritesImmediate function.
Previously, a value
of 1 had been passed into this function to draw only a single sprite.
Now that there’s an array of sprites to draw, the number of active
sprites should be passed in. The variable numActiveSprites contains the current number of valid sprites in the array.
The DrawSpritesImmediate function isn’t the only available function for drawing sprites. The ID3DX10Sprite object also includes the function DrawSpritesBuffered. The behavior of the two functions is slightly different.
The DrawSpritesImmediate function sends the sprites to the video hardware as soon as it is called.
The DrawSpritesBuffered
function builds up a list of sprites to be drawn before actually
sending them to the card. This is useful if you have functions where one
or only a few sprites are needed each time. Once you’re ready to
finally draw the sprites, calling the function Flush sends the sprites to the video card.
The following code sample shows an example usage of the DrawSpritesBuffered function.
/*******************************************************************
* Render
* All drawing happens in the Render function
* Inputs - void
* Outputs - void
*******************************************************************/
void Render()
{
if (pD3DDevice != NULL)
{
// clear the target buffer
pD3DDevice->ClearRenderTargetView(pRenderTargetView, D3DXCOLOR(0.0f, 0.0f,
0.0f, 0.0f));
if (spriteObject != NULL)
{
HRESULT hr = spriteObject->SetProjectionTransform(&matProjection);
// start drawing the sprites
spriteObject->Begin(D3DX10_SPRITE_SORT_TEXTURE);
// Draw all the sprites in the pool
spriteObject->DrawSpritesBuffered(spritePool,
numActiveSprites);
// Finish up and send the sprites to the hardware
spriteObject->Flush();
spriteObject->End();
}
// display the next item in the swap chain
pSwapChain->Present(0, 0);
}
}