Published: January 26, 2010
Validated with Amaya
By Richard G. Baldwin
XNA Programming Notes # 0122
|
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.)
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.
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.
|
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.
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.
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.
Figure 3 A bottom row image.
Figure 4 A bottom row image
flipped horizontally.
Figure 5 A top row image
flipped horizontally.
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.
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 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 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.
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 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 |
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.
Among other things, you learned:
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 2009, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
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.
-end-