1. Sprite Animation
In this section we cover spite animation, which is
the technique that brings a 2D game to life. Sprite Animation is a
matter of showing frames or individual sprites at set intervals to give
the illusion of motion, no different than flipping a drawing book that
has pictures of a stick man running. Each picture is static, but when
drawn in the correct order with the right position, the stick man
appears animated when flipping through the images.
Project references to the SpriteSheetRuntime and SpriteSheetPipeline are added to the SpriteAnimation and SpriteAnimationContent
projects respectively in order to take advantage of the automatic
Sprite Sheet creation and easy sprite access via file name within the
Sprite Sheet without having to remember coordinates or the math to track
frames.
The Sprite Sheet is created from the AlienShooter
textures. For the hero ship, we want to add rocket engine exhaust
animations as well as for the missile. For the alien spaceship, a bit of
glow is added underneath that shimmers and some tilt to the left and
right is added to the ship. To create these effects I fired up Paint.NET
and created 10 sprites for each object, varying, the patterns enough to
make cartoonish flame for the hero ship and missile as well as the glow
and tilt for the spaceship. Figure 1 shows the individually edited sprite files in the file system.
The files are added to the Sprite Sheet XML file as shown in Listing 1
Example 1. SpriteAnimationSpriteSheet.xml Content File
<?xml version="1.0" encoding="utf-8" ?>
<XnaContent>
<Asset Type="System.String[]">
<Item>Sprites/heroship0.tga</Item>
<Item>Sprites/heroship1.tga</Item>
<Item>Sprites/heroship2.tga</Item>
<Item>Sprites/heroship3.tga</Item>
<Item>Sprites/heroship4.tga</Item>
<Item>Sprites/heroship5.tga</Item>
<Item>Sprites/heroship6.tga</Item>
<Item>Sprites/heroship7.tga</Item>
<Item>Sprites/heroship8.tga</Item>
<Item>Sprites/heroship9.tga</Item>
<Item>Sprites/spaceship0.tga</Item>
<Item>Sprites/spaceship1.tga</Item>
<Item>Sprites/spaceship2.tga</Item>
<Item>Sprites/spaceship3.tga</Item>
<Item>Sprites/spaceship4.tga</Item>
<Item>Sprites/spaceship5.tga</Item>
<Item>Sprites/spaceship6.tga</Item>
<Item>Sprites/spaceship7.tga</Item>
<Item>Sprites/spaceship8.tga</Item>
<Item>Sprites/spaceship9.tga</Item>
<Item>Sprites/missile0.tga</Item>
<Item>Sprites/missile1.tga</Item>
<Item>Sprites/missile2.tga</Item>
<Item>Sprites/missile3.tga</Item>
<Item>Sprites/missile4.tga</Item>
<Item>Sprites/missile5.tga</Item>
<Item>Sprites/missile6.tga</Item>
<Item>Sprites/missile7.tga</Item>
<Item>Sprites/missile8.tga</Item>
<Item>Sprites/missile9.tga</Item>
</Asset>
</XnaContent>
|
The Content Processor is configured to
SpriteSheetProcessor, which instructs the Content Pipeline to collect
the individual files and mash them together into a single texture, as
shown in Figure 2.
Personally, I would find it tedious to hand create a sprite texture, as shown in Figure 2 and prefer drawing individual images and letting the SpriteSheet sample code do the hard work for me. Once you see how easy it is to animate sprites using this method I think you will agree.
Drawing the frames so that it animates between the
images is pretty straightforward. In the Game1 class for the
SpriteAnimation sample, the following private fields are declared:
SpriteSheet SpriteAnimationSpriteSheet;
int spriteIndex = 0;
Rectangle screenRect;
TimeSpan timeToNextFrame = new TimeSpan();
TimeSpan frameTime = TimeSpan.FromMilliseconds(50d);
The spriteIndex variable is used to append a number from 0 to 9 to the sprite name of heroship, missile, and spaceship. Note in Figure 1 and Listing 1 how the sprite images are named. Incrementing spriteIndex steps over to the next sprite by name in Figure 2.
the timeToNextFrame field is used to sum elapsed time for the game. The frameTime field stores how often the spriteIndex should change over to the next frame. Here is the code from Game1.Update that performs this calculation:
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
// Add elapsed game time between calls to Update
// Once enough time has passed, i.e. timeToNextFrame > frameTime
//increment sprite index.
timeToNextFrame += gameTime.ElapsedGameTime;
if (timeToNextFrame > frameTime)
{
if (spriteIndex < 9)
spriteIndex++;
else spriteIndex = 0;
frameElapsedTime = TimeSpan.FromMilliseconds(0d); }
base.Update(gameTime);
}
Essentially, the frameTime variable is how long any given frame is displayed. If you want the frames to animate more slowly, increase the value of the frameTime variable, currently set to 50.
Drawing a frame is pretty straightforward as well.
Here is the code to draw the center of the hero ship at the center of
the screen:
spriteBatch.Draw(SpriteAnimationSpriteSheet.Texture,
new Vector2((screenRect.Width / 3) -
SpriteAnimationSpriteSheet.SourceRectangle("spaceship" + spriteIndex.ToString()).Width / 2,
(screenRect.Height / 3) -
SpriteAnimationSpriteSheet.SourceRectangle("spaceship" + spriteIndex.ToString()).Height / 2),
SpriteAnimationSpriteSheet.SourceRectangle("spaceship" + spriteIndex.ToString()),
Color.White);
The previous code takes the entire sprite sheet and uses this parameter for the source rectangle to select which sprite to draw:
SpriteAnimationSpriteSheet.SourceRectangle("spaceship" + spriteIndex.ToString())
You can only see the actual animation by running the code, but Figure 3 provides a snapshot.
We copy over all of the heroship, alienship, and missile images and SpriteSheet XML file to the AlienShooterContent content project's Sprites folder so that we can leverage the assets we created in our primary game.
Now that you understand how to animate sprites, we can move forward and create a GameObject class that handles the animation logic, as well as other object state allowing the code to focus more on game play and let the GameObject class handle rendering and state management.
Remember from our ScreenManager coverage that the actual game functionality exists in the GameplayScreen class.