baldwin.jpg

Programming with XNA Game Studio

Frame Animation using a SpriteSheet

In this lesson you will learn:

Published: January 26, 2010
Validated with Amaya

By Richard G. Baldwin

XNA Programming Notes # 0122


Preface

General

XNA Game Studio 3.1
Note that all references to XNA in this lesson are references to version 3.1 or later.

This tutorial lesson is part of a continuing series dedicated to programming with the XNA Game Studio. I am writing this series of lessons primarily for the benefit of students enrolled in an introductory XNA game programming course that I teach. However, everyone is welcome to study and benefit from the lessons.

An earlier lesson titled Getting Started provided information on how to get started programming with Microsoft's XNA Game Studio. (See Baldwin's XNA programming website in Resources.)

Viewing tip

I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.

Figures

Listings

Supplemental material

I recommend that you also study the other lessons in my extensive collection of online programming tutorials.  You will find a consolidated index at www.DickBaldwin.com.

General background information

Credit for sprite artwork
If you are the artist that drew these sprites, please contact me and identify the original source and I will gladly give you credit for the artwork.

Frame animation typically involves displaying a series of images one at a time in quick succession where each image is similar to but different from the one before it. For example, Figure 1 shows a series of images of a small dog running, jumping, and stopping to answer nature's call.

Figure 1 Sprite sheet used to animate a dog.
SpriteSheet used to animate a dog.

A sprite sheet

The format that you see in Figure 1 is a small scale version of a format that is commonly known as a sprite sheet. If you Google "animation sprite sheet", you will find hundreds and possibly thousands of examples of animation sprite sheets on the web.

Hundreds of sprite images

Many of the sprite sheets that you will find on the web will contain hundreds of individual images usually arranged is several groups. One group may have several images that can be animated to create the illusion of a character running. Another group may have several images that can be animated to create the illusion of the character engaging in a martial arts battle. Other groups can be animated to create the illusion of other activities.

There are two groups of sprite images in Figure 1. The images in the top row can be animated to show the dog running, jumping, playing and generally having fun.

The images in the bottom row can be animated to show the dog answering nature's call.

Frame animation

By displaying the individual images from a group sequentially with an appropriate time delay between images, you can create the illusion that the character is engaging in some particular activity. When displayed in this manner, each image is often referred to as a frame. The overall process is often referred to as frame animation.

Downloading the sprite sheet

If you would like to replicate my program using the same sprite sheet, you should be able to right-click on Figure 1 and save the image on your disk. Be sure to save it as an image file of type JPEG.

Preview

I will explain a program in this lesson that causes the dog to run back and forth across a small game window always facing in the right direction as shown in Figure 2. Figure 2 through Figure 5 show four random screen shots taken while the program was running.

Figure 2 A top row image.
Sprite

Figure 3 A bottom row image.
Sprite

Figure 4 A bottom row image flipped horizontally.
Sprite

Figure 5 A top row image flipped horizontally.
Sprite

You should be able to correlate the images of the dog in Figure 2 through Figure 5 with the images in Figure 1. Note, however, that the images in Figure 4 and Figure 5 were flipped horizontally so that the dog would be facing the right way when moving from left to right across the game window.

Discussion and sample code

Will discuss in fragments

I will explain the code in this program in fragments, and I will only discuss the code that I modified relative to the skeleton code produced by Visual C# when I created the project. A complete listing of the file named Game1.cs is shown in Listing 14 near the end of the lesson.

Beginning of the class named Game1

The class named Game1 begins in Listing 1.

Listing 1. Beginning of the class named Game1.

namespace XNA0122Proj {
  public class Game1 : Microsoft.Xna.Framework.Game {

    //Declare and populate instance variables
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    Texture2D myTexture;
    Vector2 spritePosition = new Vector2(0.0f,0.0f);
    int slide = 8;//Used to move sprite across screen.
    int scale = 4;//Size scale factor.
    int fast = 175;//Used for fast frame rate.
    int slow = 525;//Used for slow frame rate.
    int msPerFrame = 0;//Gets set for fast or slow.
    int msElapsed;//Time since last new frame.
    int spriteCol;//Sprite column counter.
    int spriteColLim = 5;//Number of sprite columns.
    int spriteRow;//Sprite row counter.
    int spriteRowLim = 2;//Number of sprite rows.
    int frameWidth;//Width of an individual image
    int frameHeight;//Height of an individual image
    int xStart;//Corner of frame rectangle
    int yStart;//Corner of frame rectangle
    SpriteEffects noEffect = SpriteEffects.None;
    SpriteEffects flipEffect =
                          SpriteEffects.FlipHorizontally;
    SpriteEffects spriteEffect;//noEffect or flipEffect
    int winWidth;//Width of the game windos.
    int funSequenceCnt = 0;
    int pauseSequenceCnt = 0;

Listing 1 declares a large number of instance variables that are used by code throughout the program. I will explain the purpose of the instance variables when we encounter them in the program code later.

The modified constructor for the Game1 class

The constructor for the Game1 class is shown in Listing 2.

Listing 2. The constructor for the Game1 class.

    public Game1() {//constructor
      graphics = new GraphicsDeviceManager(this);
      Content.RootDirectory = "Content";

      //Set the size of the game window.
      graphics.PreferredBackBufferWidth = 450;
      graphics.PreferredBackBufferHeight = 100;

    }// end constructor

I added the code with the yellow highlight to the standard constructor that is generated by Visual C# when you create a new project.

Setting the game window size

Although it isn't very clear in the documentation, the values for PreferredBackBufferWidth and PreferredBackBufferHeight set the size of the game window provided that they are consistent with the screen resolution. These two statements caused the game window to be small as shown in Figure 2 instead of the default size of 800 pixels by 600 pixels.

The overridden LoadContent method

The overridden LoadContent method is shown in Listing 3.

Listing 3. The overridden LoadContent method.

    protected override void LoadContent() {
      //Create a new SpriteBatch object, which can be
      // used to draw textures.
      spriteBatch = new SpriteBatch(GraphicsDevice);

      //Load the image
      myTexture = Content.Load<Texture2D>("dogcropped");

      //Initialize instance variables
      spriteCol = 0;
      spriteRow = 0;

      frameWidth = myTexture.Width / spriteColLim;
      frameHeight = myTexture.Height / spriteRowLim;

      msPerFrame = fast;

      spriteEffect = flipEffect;

      winWidth = Window.ClientBounds.Width;
    }//end LoadContent

Load the image

The code to load the image, which is a sprite sheet, is the same as code that I have explained in earlier lessons.

Initialization of variables

Some of the instance variables in Listing 1 were initialized when they were declared. Others couldn't be initialized when they were declared for a variety of reasons.

Some could have been initialized in the constructor and others couldn't because the required information wasn't yet available.

I elected to initialize variables in the LoadContent method. By the time the LoadContent method executes, all of the information necessary to initialize the variables is available.

spriteCol and spriteRow

The variables named spriteCol and spriteRow will be used as counters to keep track of and to specify the column and row for a particular sprite image as shown in Figure 1. The columns are numbered from 0 through 4 (five columns) and the rows are numbered from 0 through 1 (two rows).

frameWidth and frameHeight

These two variables specify the width and the height of an individual sprite image (see Figure 1). The width and the height of the individual sprite images are computed by dividing the total width of the image loaded in Listing 3 by the number of sprite images in a row and by dividing the total height of the image loaded in Listing 3 by the number of sprite images in a column.

msPerFrame

Listing 1 declares two variables named fast and slow and initializes their values to 175 milliseconds and 525 milliseconds respectively. These two values are used to switch the animation frame rate between a fast rate and a slow rate by assigning one or the other value to the variable named msPerFrame. The fast value is assigned to msPerFrame in Listing 3 to specify a fast frame rate when the animation begins.

spriteEffect

The SpriteEffects enumeration lists the following effects that can be applied to a sprite when it is drawn:

The images in the raw sprite sheet shown in Figure 1 are all facing to the left. The FlipHorizontally enumeration value will be used to cause the images to face to the right when the dog is moving from left to right across the game window. The None enumeration value will be used to cause the images to face to the left (the default) when the dog is moving from right to left across the game window.

This is accomplished with the variables named spriteEffect, flipEffect, and noEffect. The value of spriteEffect is initialized to flipEffect in Listing 3 because the dog starts off moving from left to right.

winWidth

The variable named winWidth is set to the width of the game window. The value Window.ClientBounds.Width could have been used everywhere that winWidth is used but the length of the expression created some formatting problems when attempting to format the source code for this narrow publication format.

The overridden Update method

You will recall that after initialization, the XNA game loop switches back and forth between calling the Update method and the Draw method. The Update method is overridden to implement the game logic and the Draw method is overridden to render the current state of the game on the computer screen.

(There are some subtle timing issues that I explained in earlier lessons and I won't get into them here.)

Update method is fairly complex

The Update method in the earlier lessons has been fairly simple. That is not the case in this lesson. The Update method in this lesson, which begins in Listing 4, contains some fairly complex logic.

Listing 4. Beginning of the Update method.

    protected override void Update(GameTime gameTime) {
      // Allows the game to exit
      if(GamePad.GetState(PlayerIndex.One).Buttons.Back
                                  == ButtonState.Pressed)
        this.Exit();
      //-----------------------------------------------//

      //New code begins here.

      msElapsed += gameTime.ElapsedGameTime.Milliseconds;
      if(msElapsed > msPerFrame){
        //Reset the elapsed time and draw the new frame.
        msElapsed = 0;

The animation frame rate

The code at the beginning of Listing 4 is the standard code that is generated by Visual C# when you create a new Windows Game project.

The new code in Listing 4 deals with the animation frame rate. The animation frame rate needs to be much slower than the default repetition rate of the game loop, which is 60 iterations per second. Otherwise the dog would run around so fast that it wouldn't look natural.

Many drawings will be repeated

Therefore, we won't change the drawing parameters during every iteration of the game loop. Instead, we will cause the sprite to be drawn in the game window sixty times per second, but many of those drawings will look just like the previous drawing.

We will accomplish this by changing the drawing parameters only once every msPerFrame milliseconds. (Recall that msPerFrame can have either of two values: fast and slow.)

The GameTime parameter

Each time the Update method is called, an incoming parameter contains information in an object of type GameTime that allows us to determine the number of milliseconds that have elapsed since the last time the Update method was called.

The documentation for the GameTime class has this to say:

"Snapshot of the game timing state expressed in values that can be used by variable-step (real time) or fixed-step (game time) games."

The ElapsedGameTime property

The GameTime object has several properties, one of which is named ElapsedGameTime. This property, which is a structure of type TimeSpan provides

"The amount of elapsed game time since the last update."

The TimeSpan structure

A TimeSpan structure has a large number of properties including one named Milliseconds. This property

"Gets the milliseconds component of the time interval represented by the current TimeSpan structure."

Therefore, the expression highlighted in yellow in Listing 4 returns the elapsed time in milliseconds since the last call to the Update method.

Accumulate and compare elapsed time

Listing 4 adds the value in milliseconds to an accumulator variable named msElapsed each time the Update method is called.

Listing 4 also compares the accumulated value with the desired animation interval stored in msElapsed. If the accumulated value exceeds the desired animation interval, the accumulated value is set to zero and the body of the if statement shown in Listing 4 is executed to modify the drawing parameters.

Compute the location of the sprite to draw

The code in Listing 5 computes the location in pixel coordinates of the sprite image that needs to be drawn the next time the Draw method is called.

Listing 5. Compute the location of the sprite to draw.

        xStart = spriteCol * frameWidth;
        yStart = spriteRow * frameHeight;

That sprite image is identified by the intersection of the spriteCol column and the spriteRow row in Figure 1.

The coordinates of the upper left corner of the sprite image

The column and row values are used in conjunction with the width and height of the sprite images to compute the coordinates of the upper-left corner of the sprite image to be drawn. These values are stored in xStart and yStart, which will be used in the Draw method to select and draw the correct sprite image.

The overall animation cycle

The program plays five animation cycles of the five sprite images in the top row of Figure 1. These five cycles are played with the fast animation frame rate discussed earlier. This is controlled by a counter variable named funSequenceCnt. (This name was chosen because these images portray the dog running and jumping and having fun.)

Two animation cycles from the bottom row of sprite images

Then the program plays two animation cycles of the five sprite images in the bottom row of Figure 1. These five cycles are played with the slow animation frame rate discussed earlier.

Pause and animate in the same location

During this period, the dog doesn't move across the game window but rather the animation cycles are played with the dog remaining in the same location. This is controlled by a counter variable named pauseSequenceCnt. (This name was chosen because the dog pauses and animates in the same location.)

After that, the overall cycle repeats.

Complex logic

This is where the logic becomes a little complex and it remains to be seen how well I can explain it. However, my students are supposed to have the prerequisite knowledge that prepares them to dissect and understand complex logic directly from source code.

Adjust column and row counters

The drawing parameters have already been established to identity the sprite image that will be drawn the next time the Draw method is called. The code that follows is preparing for the sprite selection that will take place after that one.

Increment the column counter and compare

Listing 6 increments the column counter and compares it with the number of columns in the sprite sheet in Figure 1. If they match, Listing 6 resets the column counter to 0 and increments the funSequenceCnt to indicate that another one of the five cycles through the five images in the top row of Figure 1 has been completed.

Listing 6. Adjust column and row counters.

        if(++spriteCol == spriteColLim){
          //Column limit has been hit, reset the
          // column counter and increment the
          // funSequenceCnt.
          spriteCol = 0;

          funSequenceCnt++;

Execute the pause sequence if it is time for it

The last statement in Listing 6 incremented the funSequenceCnt. The first statement in Listing 7 tests to see if it has a value of 5. If so, all five cycles of the fun sequence have been executed and the code in the body of the if statement that begins at the top of Listing 7 will be executed. The purpose of this code is to execute two cycles of the pause sequence.

Listing 7. Execute the pause sequence if it is time for it.

          if((funSequenceCnt == 5) || (spriteRow == 1)){
            spriteRow = 1;//advance to second row
            //Increment the pause sequence counter.
            pauseSequenceCnt++;
            //After two cycles in the pause mode, reset
            // variables and start the overall cycle
            // again.
            if(pauseSequenceCnt == 3){
              spriteRow = 0;
              funSequenceCnt = 0;
              pauseSequenceCnt = 0;
            }//end if on pauseSequenceCnt
          }//end if on funSequenceCnt

        }//end if on spriteColLim in Listing 6

The conditional clause

The conditional clause in the if statement at the top of Listing 7 also tests to see if the row counter is pointing to row 1. If so, this means that the pause cycle has already begun and should be continued. Therefore, the body of that if statement will be executed. In other words, the body of the if statement will be executed if the funSequenceCnt is equal to 5 or the spriteRow is equal to 1.

Set the row counter to 1

The first statement in the body of the if statement sets the row counter to 1. This is necessary because control may have just entered the body of the if statement for the first time following completion of five cycles using the sprites in row 0 (the top row in Figure 1).

Increment the pauseSequenceCnt

Then Listing 7 increments the pauseSequenceCnt and compares it with the literal value 3. If there is a match, two cycles of animation using the sprite images in the bottom row of Figure 1 have been completed and it's time to return to the five cycles using the sprite images in the top row of Figure 1.

To accomplish this, the row counter, the funSequenceCnt, and the pauseSequenceCnt are all set to 0. This will cause five cycles using the sprite images in the top row of Figure 1 to be executed before control will once again enter the code in Listing 7.

Adjust sprite position and frame rate

The code that we have examined so far mainly deals with selecting the sprite image to draw each time the Draw method is called. We haven't dealt with the location where the sprite will be drawn in the game window, the orientation of the sprite when it is drawn, and the frame animation rate of fast versus slow.

Listing 8 adjusts these drawing parameters.

Listing 8. Adjust sprite position and frame rate.

        if((spriteRow == 0) || 
                ((spriteRow == 1) && (spriteCol == 0))) {
          msPerFrame = fast;
          spritePosition.X += frameWidth * scale / slide;
        }
        else if ((spriteRow == 1) ||
                 ((spriteRow == 0) && (spriteCol == 0))){
          //Stop and display images.
          msPerFrame = slow;
        }//end if-else
        

More complex logic

The logic in Listing 8 is fairly complex due mainly to the need to adjust the frame rate from fast to slow or from slow to fast when transitioning between the two rows of sprites in Listing 1. Rather than to try to explain this logic, I am going to leave it as an exercise for the student to analyze the code and determine where the frame rate transitions occur.

Scaling

Although I haven't mentioned it before, the SpriteBatch.Draw method (not the Game.Draw method) that will be used to draw the sprites in the game window has a scaling parameter that can be used to scale the images before drawing them.

Listing 1 declares a variable named scale and sets its value to 4. This will be used as a scale factor when the sprite images are drawn.

The slide variable

Listing 1 also declares a variable named slide and sets its value to 8. This variable is used to control how far the sprite moves each time it is drawn.

That distance, along with the new sprite position, is computed in Listing 8 as the product of the width of the sprite image and the scale factor divided by the value of slide. This is a distance that I determined experimentally to cause the animation to look like I wanted it to look.

The sign of the variable named slide

It is worth noting that the sign of the variable named slide determines whether the incremental distance is positive or negative.

It is also worth noting that the change in spritePosition.X only occurs when sprites from the top row in Figure 1 are being drawn. When sprites from the bottom row in Figure 1 are being drawn, the sprite is animated in place.

Move the sprite image back and forth across the game window

The code in Listing 9 causes the sprite image to move back and forth across the game window always facing in the right direction.

Listing 9. Move the sprite image back and forth across the game window.

        if(spritePosition.X > 
                         winWidth - frameWidth * scale) {
          slide *= -1;
          spriteEffect = noEffect;
        }//end if

        if(spritePosition.X < 0){
          slide *= -1;
          spriteEffect = flipEffect;
        }//end if

      }//end if
      
      //-----------------------------------------------//
      //New code ends here.
      base.Update(gameTime);
    }//end Update

Test for a collision with an edge

Listing 9 tests for a collision between the sprite and the right edge or the left edge of the game window. If the sprite collides with the right edge, the sign on the variable named slide is changed to cause future incremental distance movements to be negative (from right to left).

If the sprite collides with the left edge, the sign on the variable named slide is changed to cause future incremental distance movements to be positive (from left to right).

The spriteEffect variable

In addition, when the sprite collides with one edge or the other, the value of the spriteEffect variable is set such that the dog will be facing the right direction as it moves toward the other edge of the game window.

That concludes the explanation of the overridden Update method.

The overridden Draw method

The overridden Draw method selects the correct sprite image by extracting a rectangular area from the sprite sheet and draws the rectangle containing the sprite image at a specified location in the game window.

A white non-transparent background

Note in Figure 1 that the sprite sheet has a white non-transparent background. The game window is also caused to have a white background so that the white background of the rectangle containing the sprite image can't be distinguished from the game window background.

The overridden Draw method

The overridden Draw method begins in Listing 10.

Listing 10. Beginning of the overridden Draw method.

    protected override void Draw(GameTime gameTime) {

      GraphicsDevice.Clear(Color.White);//Background

The statement in Listing 10 erases everything in the game window by painting over it with a solid white background.

Begin the drawing process

You learned in an earlier lesson that the drawing process consists of a minimum of three statements.

The first statement is a call to the SpriteBatch.Begin method. This is followed by one or more calls to the SpriteBatch.Draw method. This is followed by a call to the SpriteBatch.End method.

Four overloaded Begin methods

There are four overloaded versions of the SpriteBatch.Begin method. The parameters for the different versions establish drawing parameters that apply to all of the subsequent calls to the SpriteBatch.Draw method until there is a call to the SpriteBatch.End method.

The simplest one

This program uses the simplest version of the SpriteBatch.Begin method with no parameters as shown in Listing 11. This version simply

"Prepares the graphics device for drawing sprites."

Listing 11. Begin the drawing process.

      spriteBatch.Begin();
        

Call the SpriteBatch.Draw method

This program makes a single call to the SpriteBatch.Draw method followed by a call to the SpriteBatch.End method each time the Game.Draw method is called.

Seven overloaded versions

There are seven overloaded versions of the SpriteBatch.Draw method and the one shown in Listing 12 is one of the most complex. It was necessary to use this version to cause the sprite to be scaled by the value of the variable named scale when it is drawn.

Listing 12. Call the SpriteBatch.Draw method.

      spriteBatch.Draw(myTexture,//sprite sheet
                       spritePosition,//position to draw
                       //Specify rectangular area of the
                       // sprite sheet.
                       new Rectangle(
                         xStart,//Upper left corner
                         yStart,// of rectangle.
                         frameWidth,  //Width and height
                         frameHeight),// of rectangle
                       Color.White,//Don't tint sprite
                       0.0f,//Don't rotate sprite
                       //Origin of sprite. Can offset re
                       // position above.
                       new Vector2(0.0f,0.0f),
                       //X and Y scale size scale factor.
                       new Vector2(scale,scale),
                       spriteEffect,//Face correctly
                       0);//Layer number
      spriteBatch.End();

The nine parameters required for this version of the method are identified in the documentation as shown below:

The first two parameters

The first two parameters that identify the sprite sheet and the position at which to draw the image are the same as the version that I explained in an earlier lesson.

Three parameters

The remaining three parameters highlighted in yellow both in the above list and in Listing 12 are parameters that are new to this lesson and in which I have an interest.

The remaining four parameters

The remaining four parameters are new to this lesson, but I don't have any interest in them. The program passes "harmless" values to them.

The rectangle

The first thing that is new to this lesson in Listing 12 is the passing of a new object of type Rectangle as the third parameter.

With this version of the Draw method, only the contents of the sprite sheet that fall within that rectangle are drawn. The size and position of the rectangle are specified by specifying:

The upper left corner of the rectangle

The upper left corner of the rectangle is specified in Listing 12 by the contents of the variables named xStart and yStart. The values in these two variables were assigned in Listing 5. They specify the coordinates of the upper left corner of a small rectangle that contains one of the sprite images shown in Figure 1.

The width and the height of the rectangle

The width and the height are specified by the contents of the variables named frameWidth and frameHeight. The values in these two variables were assigned in Listing 3 as the width and height of the small rectangle that contains one of the sprite images in Figure 1.

The horizontal and vertical scale factors

The seventh parameter requires a Vector2D object containing the scale factors to be applied to the horizontal and vertical sizes of the sprite before it is drawn. (You learned about the Vector2D class in an earlier lesson.)

A new object

Listing 12 instantiates a new object of the Vector2D class that encapsulates the value stored in the variable named scale for the horizontal and vertical scale factors.

A value of 4 was assigned to the variable named scale when it was initialized in Listing 1.

Four times larger

Therefore, the sprite images that are drawn in the game window are actually four times larger than the sprite images in the sprite sheet shown in Figure 1. (Note that the images were also scaled in Figure 1 for display purposes in order to make them more visible.)

Causing the sprite to face in the right direction

The eighth parameter to the Draw method in Listing 12 requires an object of type SpriteEffects. The contents of the variable named spriteEffect are passed as this parameter. The contents of this variable were set to one of the following two values by the code in Listing 9:

Face in the right direction

The purpose of this parameter is to cause the sprite to face in the right direction.

The sprite needs to face to the left when it is moving from right to left. This is the default state of the sprite sheet shown in Figure 1 so no flip is required.

The sprite needs to face to the right when it is moving from left to right. This requires a horizontal flip on the sprite images shown in Figure 1.

Call the SpriteBatch.End method

The last statement in Listing 12 calls the SpriteBatch.End method to terminate the drawing process for the current iteration of the game loop.

The visual frame rate

By default, the Update and Draw methods are each called approximately 60 times per second or approximately once every 16.67 milliseconds. This can be changed by program code but it was not changed in this program.

The fast frame rate

When the program is drawing the sprite images in the top row of Figure 1, a new sprite image is selected for drawing only once every 175 milliseconds (see the variable named fast). Therefore, the same sprite image is drawn during ten or eleven successive iterations of the game loop.

The slow frame rate

When the program is drawing the sprite images in the bottom row of Figure 1, a new sprite image is selected for drawing only once every 525 milliseconds (see the variable named slow). Therefore, each of the sprites in the bottom row is drawn during about 33 successive iterations of the game loop.

Avoid flicker but animate more slowly

By drawing the sprite images 60 times per second, the image can be maintained on the computer screen with no visible flicker. By drawing the same image multiple times in succession, the overall visual frame rate can be slowed down to produce a pleasing animation effect.

The end of the program

Listing 13 shows the required call to the superclass of the Draw method, the end of the Draw method, the end of the class, and the end of the namespace.

Listing 13. The end of the program.

      //Required standard code.
      base.Draw(gameTime);
    }//end Draw method
  }//End class
}//End namespace

Run the program

I encourage you to copy the code from Listing 14.  Use that code to create an XNA project.  Compile and run the project.  Experiment with the code, making changes, and observing the results of your changes.  Make certain that you can explain why your changes behave as they do. 

Summary

Among other things, you learned:

Complete program listing

A complete listing of the XNA program discussed in this lesson is provided in Listing 14.

Listing 14. Class Game1 from the project named XNA0122Proj.

/*Project XNA0122Proj
R.G.Baldwin, 12/28/09
Animation demonstration. Animates a dog running, jumping,
and stopping to ponder and scratch the ground. Uses two 
different frame rates and a 5x2 sprite sheet. Runs back
and forth across the game window always facing in the
right direction.
********************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace XNA0122Proj {
  public class Game1 : Microsoft.Xna.Framework.Game {

    //Declare and populate instance variables
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    Texture2D myTexture;
    Vector2 spritePosition = new Vector2(0.0f,0.0f);
    int slide = 8;//Used to move sprite across screen.
    int scale = 4;//Size scale factor.
    int fast = 175;//Used for fast frame rate.
    int slow = 525;//Used for slow frame rate.
    int msPerFrame = 0;//Gets set for fast or slow.
    int msElapsed;//Time since last new frame.
    int spriteCol;//Sprite column counter.
    int spriteColLim = 5;//Number of sprite columns.
    int spriteRow;//Sprite row counter.
    int spriteRowLim = 2;//Number of sprite rows.
    int frameWidth;//Width of an individual image
    int frameHeight;//Height of an individual image
    int xStart;//Corner of frame rectangle
    int yStart;//Corner of frame rectangle
    SpriteEffects noEffect = SpriteEffects.None;
    SpriteEffects flipEffect =
                          SpriteEffects.FlipHorizontally;
    SpriteEffects spriteEffect;//noEffect or flipEffect
    int winWidth;//Width of the game windos.
    int funSequenceCnt = 0;
    int pauseSequenceCnt = 0;

    public Game1() {//constructor
      graphics = new GraphicsDeviceManager(this);
      Content.RootDirectory = "Content";

      //Set the size of the game window.
      graphics.PreferredBackBufferWidth = 450;
      graphics.PreferredBackBufferHeight = 100;

    }// end constructor

    protected override void Initialize() {
      //No special initialization needed
      base.Initialize();
    }//end Initialize

    protected override void LoadContent() {
      //Create a new SpriteBatch object, which can be
      // used to draw textures.
      spriteBatch = new SpriteBatch(GraphicsDevice);

      //Load the image
      myTexture = Content.Load<Texture2D>("dogcropped");

      //Initialize instance variables
      spriteCol = 0;
      spriteRow = 0;

      frameWidth = myTexture.Width / spriteColLim;
      frameHeight = myTexture.Height / spriteRowLim;

      msPerFrame = fast;

      spriteEffect = flipEffect;

      winWidth = Window.ClientBounds.Width;
    }//end LoadContent

    protected override void UnloadContent() {
      //No unload code needed.
    }//end UnloadContent

    protected override void Update(GameTime gameTime) {
      // Allows the game to exit
      if(GamePad.GetState(PlayerIndex.One).Buttons.Back
                                  == ButtonState.Pressed)
        this.Exit();
      //-----------------------------------------------//

      //New code begins here.

      //Compute the elapsed time since the last new 
      // frame. Draw a new frame only if this time 
      // exceeds the desired frame interval given by
      // msPerFrame
      msElapsed += gameTime.ElapsedGameTime.Milliseconds;
      if(msElapsed > msPerFrame){
        //Reset the elapsed time and draw the new frame.
        msElapsed = 0;

        //Compute the location of the next sprite to 
        // draw from the sprite sheet.
        xStart = spriteCol * frameWidth;
        yStart = spriteRow * frameHeight;

        //Adjust sprite column and row counters to 
        // prepare for the next iteration.
        if(++spriteCol == spriteColLim){
          //Column limit has been hit, reset the
          // column counter
          spriteCol = 0;
          //Increment the funSequenceCnt. The program 
          // plays five cycles of the fun sequence with 
          // the dog running and jumping using sprites 
          // from row 0 of the sprite sheet. Then it 
          // plays two cycles of the pause sequence 
          // using sprites from row 1 of the sprite
          // sheet. Then the entire cycle repeats.
          funSequenceCnt++;
          if((funSequenceCnt == 5) || (spriteRow == 1)){
            spriteRow = 1;//advance to second row
            //Increment the pause sequence counter.
            pauseSequenceCnt++;
            //After two cycles in the pause mode, reset
            // variables and start the overall cycle
            // again.
            if(pauseSequenceCnt == 3){
              spriteRow = 0;
              funSequenceCnt = 0;
              pauseSequenceCnt = 0;
            }//end if
          }//end if
        }//end if-else

        //Adjust position of sprite in the output window.
        //Also adjust the animation frame rate between
        // fast and slow depending on which set of five
        // sprite images will be drawn.
        if((spriteRow == 0) || 
                ((spriteRow == 1) && (spriteCol == 0))) {
          msPerFrame = fast;
          spritePosition.X += frameWidth * scale / slide;
        }
        else if ((spriteRow == 1) ||
                 ((spriteRow == 0) && (spriteCol == 0))){
          //Stop and display images.
          msPerFrame = slow;
        }//end if-else

        //Cause the image to move back and forth across
        // the game window always facing in the right
        // direction.
        if(spritePosition.X > 
                         winWidth - frameWidth * scale) {
          slide *= -1;
          spriteEffect = noEffect;
        }//end if

        if(spritePosition.X < 0){
          slide *= -1;
          spriteEffect = flipEffect;
        }//end if

      }//end if
      
      //-----------------------------------------------//
      //New code ends here.
      base.Update(gameTime);
    }//end Update

    protected override void Draw(GameTime gameTime) {
      GraphicsDevice.Clear(Color.White);//Background

      //Select the sprite image from a rectangular area
      // on the sprite sheet and draw it in the game
      // window. Note that this sprite sheet has a white
      // non-transparent background.
      spriteBatch.Begin();

      spriteBatch.Draw(myTexture,//sprite sheet
                       spritePosition,//position to draw
                       //Specify rectangular area of the
                       // sprite sheet.
                       new Rectangle(
                         xStart,//Upper left corner
                         yStart,// of rectangle.
                         frameWidth,  //Width and height
                         frameHeight),// of rectangle
                       Color.White,//Don't tint sprite
                       0.0f,//Don't rotate sprite
                       //Origin of sprite. Can offset re
                       // position above.
                       new Vector2(0.0f,0.0f),
                       //X and Y scale size scale factor.
                       new Vector2(scale,scale),
                       spriteEffect,//Face correctly
                       0);//Layer number
      spriteBatch.End();

      //Required standard code.
      base.Draw(gameTime);
    }//end Draw method
  }//End class
}//End namespace


Copyright

Copyright 2009, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is object-oriented programming using Java and other OOP languages.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers.

In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP). His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments. (TI is still a world leader in DSP.) In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

Baldwin@DickBaldwin.com

-end-