Getting the sprites to move
around within the window isn’t very difficult. The position of each
sprite, updated every frame, is stored in the posX and posY variables within the GameSprite structure. These variables are used during the Update function to correctly position the sprite on the screen. By manipulating the values in these variables before the Update function is called, the sprite’s position can be changed.
Changing the Sprite’s Position
Since each sprite can be moved around at a different rate, you’re going to add movement variables to the GameSprite structure so movement can be controlled on a per-sprite basis; these variables are called moveX and moveY.
The updated GameSprite structure is shown here:
// Sprite structure
typedef struct
{
// sprite dimensions
float width;
float height;
// sprite position
float posX;
float posY;
// sprite movement
float moveX;
float moveY;
BOOL visible;
} GameSprite;
The moveX and moveY
variables store the current amount that each sprite should be moved in
both the X and Y directions each frame. By changing the value in these
two variables and how often these variables are applied, it will appear
as though the sprites are moving around the screen.
The posX and posY variables, which control the actual sprite location, need to be updated each frame with the values contained in the moveX and moveY variables.
A function called MoveSprites,
which not only updates the location of a sprite but makes sure it
remains within the confines of the game window, is shown next.
/*******************************************************************
* MoveSprites
* Moves the sprites around the screen
* Inputs - void
* Outputs - void
*******************************************************************/
void MoveSprites()
{
// Loop through and update all sprites
for (int i = 0; i < MAX_SPRITES; i++)
{
// only update visible sprites
if (sprites[i].visible)
{
// clamp the sprite position to the current window
if ((sprites[i].posX > windowWidth) ||(sprites[i].posX <= 0))
{
sprites[i].moveX = -sprites[i].moveX;
}
// move the sprite in the X direction
sprites[i].posX += sprites[i].moveX;
// clamp the sprite position to the current window
if ((sprites[i].posY > windowHeight) ||(sprites[i].posY <= 0))
{
sprites[i].moveY = -sprites[i].moveY;
}
// move the sprite in the Y direction
sprites[i].posY += sprites[i].moveY;
}
}
}
Using Sprites with Transparent Areas
Most game characters aren’t
square, and they don’t normally take up the entire area of a sprite’s
image. Up until now all the sprites you’ve been drawing were square and
completely opaque, but take a look at sprites in use in games. They have
transparent areas around the characters allowing you to see what’s
behind them.
Implementing transparent areas when drawing sprites isn’t difficult,
but it does take some explanation to describe how it happens.
The blending state
dictates how Direct3D draws overlapping objects. Without blending, the
object closest to the viewer is completely opaque, obscuring anything
beneath it. When blending is enabled, overlapping objects can be made to
be partly translucent or have completely transparent areas. Blending
works by merging the colors of multiple overlapping objects to determine
the final output drawn to the screen.
Different areas of a
sprite can contain alpha components, which affect the amount of
transparency a sprite has. Areas with an alpha value of 0 have no
transparency at all and are drawn completely opaque. Areas with an alpha
value of 1 are drawn as see through. Alpha values between 0 and 1 give
the sprite a partial see-through appearance.
Whether Direct3D pays attention to the alpha component of an image is determined by the current blend state.
Note
The alpha component is applied to a sprite’s image when the source texture is created in your art tool.
Creating and Setting a New Blend State
Before a new blend state
can be applied, you have to describe how it will behave. The behavior of
a blend state is defined by the criteria set in a D3D10_BLEND_DESC structure.
The D3D10_BLEND_DESC
structure defines the criteria of how a blend operation between a
source and a destination will work. The source is the object being
applied, whereas the destination is the color already existing at that
pixel. Blending works by merging the color values of the two. For
instance, blending the red component of a source and destination will
create a final red output. How these two components are blended is up to
you.
The D3D10_BLEND_DESC structure is shown here:
typedef struct D3D10_BLEND_DESC {
BOOL AlphaToCoverageEnable;
BOOL BlendEnable[8];
D3D10_BLEND SrcBlend;
D3D10_BLEND DestBlend;
D3D10_BLEND_OP BlendOp;
D3D10_BLEND SrcBlendAlpha;
D3D10_BLEND DestBlendAlpha;
D3D10_BLEND_OP BlendOpAlpha;
UINT8 RenderTargetWriteMask[8];
} D3D10_BLEND_DESC;
The BlendEnable variable is an array of BOOL values, each one representing a rendertarget. In most cases, you’ll be dealing with only the first item in the array.
The D3D10_BLEND_DESC structure splits the full RGBA components of an object into two pieces. The first piece, controlled by the SrcBlend and DestBlend variables, dictates how the RGB components are blended. The second piece, using SrcBlendAlpha and DestBlendAlpha, controls the blending of the A (alpha) component.
As I mentioned earlier, you get to control how the components are blended; this is controlled through the BlendOp and BlendOpAlpha variables. These variables, of type D3D10_BLEND_OP, allow you to add, subtract, or otherwise manipulate how the components are blended.
An example of a completed D3D10_BLEND_DESC structure is shown next. This one blends on the sprite’s alpha component only, allowing a sprite to be drawn with transparency.
// The variable that will contain the new blend state.
ID3D10BlendState* pBlendState10 = NULL;
// Initialize the blend state for alpha drawing
D3D10_BLEND_DESC StateDesc;
ZeroMemory(&StateDesc, sizeof(D3D10_BLEND_DESC));
StateDesc.AlphaToCoverageEnable = FALSE;
StateDesc.BlendEnable[0] = TRUE;
StateDesc.SrcBlend = D3D10_BLEND_SRC_ALPHA;
StateDesc.DestBlend = D3D10_BLEND_INV_SRC_ALPHA;
StateDesc.BlendOp = D3D10_BLEND_OP_ADD;
StateDesc.SrcBlendAlpha = D3D10_BLEND_ZERO;
StateDesc.DestBlendAlpha = D3D10_BLEND_ZERO;
StateDesc.BlendOpAlpha = D3D10_BLEND_OP_ADD;
StateDesc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL;
pD3DDevice->CreateBlendState(&StateDesc, &pBlendState10);
The creation of a new blend state is handled using the function CreateBlendState. The CreateBlendState function simply takes a pointer to the D3D10_ BLEND_DESC structure you created earlier and returns an ID3D10BlendState object.
ID3D10BlendState* pBlendState10 = NULL;
// Create the new blend state
pD3DDevice->Create BlendState(&StateDesc, &pBlendState10);
The ID3D10BlendState object then needs to be applied before it can take affect. Applying a blend state object happens using the OMSetBlendState function. OMSetBlendState uses the blend state you created, as well as two more parameters, a blend factor color and a sample mask.
The blend factor color is a default color used in the instance where either the source or destination color is set as D3D10_BLEND_BLEND_FACTOR or D3D10_BLEND_ INVBLEND_FACTOR.
The sample mask is used to
determine which samples get updated when using multisampling. By
default, this value should be set to 0xffffffff.
Because of the simple nature of the blend state, default values for the blend factor and sample mask are used.
FLOAT NewBlendFactor[4] = {0,0,0,0};
pD3DDevice->OMSetBlendState(pBlendState10, NewBlendFactor, 0xffffffff);
At this point the
blend state is applied and active in the scene. When the sprite is
drawn, any areas with an alpha value above 0 will appear to be slightly
or completely transparent.
Storing the Current Blend State
Before you change the
blend state, it is a good idea to save the previous state so it can be
restored when the new one is no longer needed. The OMGetBlendState function is used to collect the current state. There are three parameters that OMGetBlendState requires.
The first parameter is a pointer to an ID3D10BlendState object. This object will be the one holding the original state.
The second parameter is a
pointer to an array holding four float values. This array will be used
to store the original blend factor.
The final parameter is an unsigned integer that will hold the original sample mask.
The following small example shows how to save the original blend state so it can be restored later.
ID3D10BlendState* pOriginalBlendState10 = NULL;
FLOAT OriginalBlendFactor[4];
UINT OriginalSampleMask = 0;
// Save the current blend state
pD3DDevice->OMGetBlendState(&pOriginalBlendState10, OriginalBlendFactor,
&OriginalSampleMask);
Restoring the original blend state when you’re done is a simple matter of calling the OMSetBlendState function with the values you stored.
// Restore the previous blend state
pD3DDevice->OMSetBlendState(pOriginalBlendState10, OriginalBlendFactor,
OriginalSampleMask);
An updated Render function supporting sprites with transparent areas is shown next.
/*******************************************************************
* Render
* All drawing happens in the Render function
* Inputs - void
* Outputs - void
*******************************************************************/
void Render()
{
FLOAT OriginalBlendFactor[4];
UINT OriginalSampleMask = 0;
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);
// Save the current blend state
pD3DDevice->OMGetBlendState(&pOriginalBlendState10,
OriginalBlendFactor, &OriginalSampleMask);
// Set the blend state for alpha drawing
if(pBlendState10)
{
FLOAT NewBlendFactor[4] = {0,0,0,0};
pD3DDevice->OMSetBlendState(pBlendState10, NewBlendFactor,
0xffffffff);
}
// Finish up and send the sprites to the hardware
spriteObject->Flush();
spriteObject->End();
}
// Restore the previous blend state
pD3DDevice->OMSetBlendState(pOriginalBlendState10,
OriginalBlendFactor, OriginalSampleMask);
// display the next item in the swap chain
pSwapChain->Present(0, 0);
}
}
Figure 1 shows a series of sprites with a transparent area at their center.