Part Two - Foundations



Welcome back to the XNAResources.com Star Defense tutorial series. In this portion of the series, we are going to look at getting our project set up and putting together some of the basics we will need later on. By the end of this segment we will have put together a simple class to do sprite animations and shown that we can display them easily.

Before You Begin... Installing XNA Fonts

Visual Studio doesn't recognize any fonts you add after it is up and running. In keeping with our intent to use only self-generated or freely redistributable content, we are going to need to install the XNA Redistributable Font Package from:

http://go.microsoft.com/fwlink/?LinkId=104778&clcid=0x409


This pack contains a few fonts licensed by Microsoft to be freely redistributable by XNA developers. This is an often overlooked issue, but even the fonts that come with Windows aren't free to redistribute. There are a number of freely available fonts on the web, but even that doesn't mean you can convert them to .spritefont files and repackage them with your game. Check the license for the fonts you intend to use beforehand!

Instructions are included with the package above for installing the fonts. If you have Visual Studio open when you install them, you must close it and reopen it or your builds will fail saying it can't find the font file.

Creating the Project

As with any XNA project, launch Visual Studio and select New Project... For the project type, select either "Windows Game (4.0)" or "Xbox 360 Game (4.0)". For the purposes of this tutorial series I'll be doing everything as a Windows Game, however the Xbox game project should work without changing any of the code.

Adding our Content

Let's begin by updating the Content project to keep things organized. Right click on the topmost node of the "Star Defense Content" project click Add -> New Folder from the popup menu. Name the folder "Fonts". Create another new folder at the same level called "Textures".

Right click on the "Fonts" folder and click Add -> New Item from the popup menu. Under "Visual Studio Installed Templates" select the "SpriteFont" template. In the Name box at the bottom, enter "Pericles.spritefont". (Pericles is one of the freely redistributable fonts provided by Microsoft for XNA Developers that we installed above.)

This will generate the SpriteFont resource for you and add it to your project. The .spritefont file itself should pop open in Visual Studio and you can browse the parameters of the font here. We will make use of the .spritefont later when we display things such as the current wave number, the player's score, and the like. Any time you add a new SpriteFont to your project, the default Font Face will be "Segoe UI Mono", so in the font's XML file, under FontName, change the name to "Pericles". Save the SpriteFont file and close the file in the editor.

The way we handle Texture resources has changed drastically since the early days of XNA. Back before the Content Pipeline we used to convert all of our images to .DDS files and use the FromFile() method of the Texture2D class to load them directly from disk. Now, we can supply our images in a number of convenient (for us) formats and the Content Pipeline will convert them to XNA's internal format which we will load using the Load() method of the Content Manager class.

Throughout this tutorial series, we will be adding content to our project as we need it for the classes we are working on. Since we want to be able to see at least SOMETHING at the end of this second segment, we will add the explosions texture we will use when enemies and the player ship explodes.

Save the image below (Explosions.png) in the Textures folder inside your project. This is normally located in your Documents directory under "Visual Studio 2010/Projects/ProjectName" (The image is 1024x512, but I've reduced it to half size via HTML in order to display it properly on the page. It should save at full size.)



This won't make the texture show up in Visual Studio, however, so one more step is necessary. In the Solution Explorer window, click on the little button in the toolbar called "Show All Files". Now click on the "+" next to the Textures folder and the Explosions.png file should appear. Right click on it and select "Include in Project".

We will add additional content to our Textures folder throughout the development of our tutorial series, but this texture is enough to illustrate our AnimatedSprite class.

AnimatedSprite.cs

The first bit of code we are going to tackle is to allow us to create animated sprites. A "sprite" is a term that goes back, as far as I know, to the Commodore 64 (perhaps earlier, though I know on the early Atari computers they were called "Player Missile Graphics") and just means a 2D image that we will render more or less directly to the screen (as opposed to a Texture, which would normally be mapped onto a 3D object). We are calling all of our images Textures as that has become a common term for any type of image handled by the video card, but you may also see what we are working with called "Sprite Sheets", which are single images that contain multiple frames of pixel-based graphics.

Since we are making a 2D space shooter we will need AnimatedSprites all over our game, so this will be our first real building block of code.

We want our AnimatedSprite class to do a few things:
  • Store a Texture associated with the sprite
  • Automatically handle sprite animation based on a Frame Rate
  • Draw itself to the screen when requested

Lets begin by right-clicking on the Star Defense project in Solution Explorer (Should be the second item, as the topmost item is the solution itself) and select Add -> Class... from the popup menu. This brings up the "Add New Item" window with Class as the selected type. Name the class file "AnimatedSprite.cs".

Visual Studio will now create the AnimatedSprite.cs file with the shell of the class opening in the right-hand pane of the window. The first thing we will need to do is add a few "Using" directives to the top of the file:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

This will allow our class to access classes from the XNA Framework without spelling out the entire namespace every time we want to use one.

Next, lets add the member variables that our AnimatedSprite class will use. These can go right after the "class AnimatedSprite" line (but inside the curly brace!):
Texture2D t2dTexture;

float fFrameRate = 0.02f;
float fElapsed = 0.0f;

int iFrameOffsetX = 0;
int iFrameOffsetY = 0;
int iFrameWidth = 32;
int iFrameHeight = 32;

int iFrameCount = 1;
int iCurrentFrame = 0;
int iScreenX = 0;
int iScreenY = 0;

bool bAnimating = true;

Lets go through each of these and take a look at what they will be used for:
  • t2dTexture: The graphics for our sprites are stored on "sprite sheets", which are just images we have imported into XNA. The way we are going to set up our AnimatedSprite class, each frame of any individual sprite's animation must be contained on a single "line" in our sprite sheet image. You can see in the explosions image above how each explosion is contained on a single "row" of same sized sub images. Additionally, all frames must be the same size.

  • fFrameRate and fElapsed: These two values are used to control how fast the frames of animation play. The fFrameRate value is the time (in seconds) that each frame is displayed. fElapsed accumulates the elapsed time since the frame was last advanced. When fElapsed becomes greater than fFrameRate, we advance the frame and set fElapsed back to 0.

  • iFrameOffsetX and iFrameOffsetY: These values identify the upperleft corner of the first frame of the sprite. This allows us to have multiple sprites on the same sprite sheet by starting them in different locations. Our explosion texture above uses this feature (we have 8 animated explosions on the texture, each one at an increment of 64 pixels from the top of the image.

  • iFrameWidth and iFrameHeight: These two integers specify the width and height of each animation frame. All frames of the animation are the same size.

  • iFrameCount: The total number of frames in the animation.

  • iCurrentFrame: The frame of animation that is currently being displayed.

  • iScreenX and iScreenY: The on-screen coordinates the sprite occupies. I should note that these are here to make our sprite independant of what other code is using it, but we will generally be setting these to 0,0 and using an offset in our draw routine equal to the X and Y position of the object that represents what is being drawn by our sprite. (This sentance will make more sense later... essentially, our sprite is capable of functioning on it's own but since we will be using them as members of other classes that have other uses for the X and Y location, we will not use these values directly in many cases.)

  • bAnimating: If this value is set to false, the frames in the sprite won't be automatically updated.

So far everything we have declared are "private" declarations. That means that they will not be accessable by any code outside the AnimatedSprite class itself. We will need to add a few properties to our class to allow other code to interact with it. Right below our declarations, lets add the following properties:
public int X
{
  get { return iScreenX; }
  set { iScreenX = value; }
}
 
public int Y
{
  get { return iScreenY; }
  set { iScreenY = value; }
}
 
public int Frame
{
  get { return iCurrentFrame; }
  set { iCurrentFrame = (int)MathHelper.Clamp(value, 0, iFrameCount); }
}
 
public float FrameLength
{
  get { return fFrameRate; }
  set { fFrameRate = (float)Math.Max(value, 0f); }
}
 
public bool IsAnimating
{
    get { return bAnimating; }
    set { bAnimating = value; }
}


By declaring these properties as "public", any class using our AnimatedSprite class will have access to these values. While it is true that we could have made the member variables above public to allow outside code to access them, using properties has a couple of advantages:

Properties can be "read only". For example, if we remove the "set { iScreenX = value;}" from the X property, outside code could read, but not alter the iScreenX value.

Properties can implement other code. While you probably won't use this very often in "get" statements, in "sets" it is very handy. In the "Frame" property above, I utilize this feature to limit the value of iCurrentFrame to a frame that is actually defined in the animation.

The only "special" things we do with these properties are in the Frame property where, as mentioned above, we use MathHelper.Clamp to limit the Frame value to a number between 0 and iFrameCount. Also, in the FrameRate property set we use Math.Max to ensure that fFrameRate doesn't end up being a negative number (Not that a negative number would cause any problems with our code, but it doesn't really make any sense so we'll rule it out.)

Next we will put together our constructor. A constructor is a function with the same name as the class itself (in our case, AnimatedSprite). This is the function that is called whenever you use "new" to create a new instance of a class. (ie, AnimatedSprite MySprite = new AnimatedSprite()
public AnimatedSprite(
  Texture2D texture,
  int FrameOffsetX,
  int FrameOffsetY,
  int FrameWidth,
  int FrameHeight,
  int FrameCount)
{
  t2dTexture = texture;
  iFrameOffsetX = FrameOffsetX;
  iFrameOffsetY = FrameOffsetY;
  iFrameWidth = FrameWidth;
  iFrameHeight = FrameHeight;
  iFrameCount = FrameCount;
}


Our constructor here is very straightforward. We pass in the basic parameters for creating our AnimatedSprite and simply set the member variables of our instance to those values.

All that is left at this point is to write our Update and Draw routines (well, almost all anyway). Before we do that, I want to include a quick helper function that we will use later:
public Rectangle GetSourceRect()
{
  return new Rectangle(
  iFrameOffsetX + (iFrameWidth *iCurrentFrame), 
  iFrameOffsetY, 
  iFrameWidth, 
  iFrameHeight);
}


This function will be used in our Draw method to determine where on the sprite sheet (based on iCurrentFrame) we will pull from when drawing our sprite. Recall that iFrameOffsetX is the left point for our first frame. To this, we add the iFrameWidth multiplied by iCurrentFrame to get the X position of the frame we desire.

Because we have predetermined that all frames of an animation must appear on the same "line" in the sprite sheet and be the same size, iScreenY, iFrameWidth, and iFrameHeight never need to be changed.

Updating our sprite's animation is also a relatively painless process:
public void Update(GameTime gametime)
{
    if (bAnimating)
    {
        // Accumulate elapsed time...
        fElapsed += (float)gametime.ElapsedGameTime.TotalSeconds;
 
        // Until it passes our frame length
        if (fElapsed > fFrameRate)
        {
            // Increment the current frame, wrapping back to 0 at iFrameCount
            iCurrentFrame = (iCurrentFrame + 1) % iFrameCount;
 
            // Reset the elapsed frame time.
            fElapsed = 0.0f;
        }
    }
}


The Update method is passed our game's current GameTime value. If we are animating (bAnimating==true), the elapsed game time is added to fElapsed. If more time has passed than our desired frame length, we will move to the next frame and reset fElapsed to 0.

The reason for all this mucking about with elapsed time is the structure of an XNA game itself. You can think of an XNA game as a giant loop. After the game does all of the initial setup (things like setting up the display, running the LoadContent method,etc) it basically starts calling the Game class' Update and Draw routines over and over and over again until the game exist.

While there are targets you can set for how fast you want these to run, you can't guarentee that you will get any particular framerate while your game is running. This means you can't rely on the number of calls to Update/Draw as an accurate timing method as this may differ depending on what is going on both in the game and on the system.

So instead, we track elapsed time and base all of our timed events off of this elapsed time. You will see this come up again when we want to keep things from happening too fast. When we add player bullets to the game, if we didn't set a real-time delay before the player could fire a second bullet, we would be spitting them out so fast they would be a constant stream on the screen.

The method used to increment the frame also deserves a bit of explanation here. The line is:

iCurrentFrame = (iCurrentFrame + 1) % iFrameCount;

You might expect to see something more along the lines of:

iCurrentFrame++;
if (iCurrentFrame >= iFrameCount)
{
iFrameCount=0;
}

And indeed this would work just fine. The shorthand version above uses the Modulo operator (%) which returns the remainder of an integer division. So, for example if we have a 16 frame animation and we are on frame 0, iCurrentFrame+1 will be 1, divided by 16 is zero with a remainder of 1, so frame 1.

The magic happens when we reach frame 15 (which is actually the 16th frame, since we start counting from zero). iCurrentFrame+1 will now be 16, divided by 16 (iFrameCount) is 1 with a remainder of 0. Since we are never using the actual division product and just the remainder, the result is that iCurrentFame gets set back to 0 without ever having to do an "if" test on the value.

Ok! So let's draw something already! Here are two "Draw" methods. I'll explain why we have two in a moment:
public void Draw(
  SpriteBatch spriteBatch, 
  int XOffset, 
  int YOffset, 
  bool NeedBeginEnd)
{
    if (NeedBeginEnd)
        spriteBatch.Begin();
 
    spriteBatch.Draw(
        t2dTexture,
        new Rectangle(
          iScreenX + XOffset, 
          iScreenY + YOffset, 
          iFrameWidth, 
          iFrameHeight),
        GetSourceRect(),
        Color.White);
 
    if (NeedBeginEnd)
        spriteBatch.End();
}

public void Draw(SpriteBatch spriteBatch, int XOffset, int YOffset)
{
    Draw(spriteBatch, XOffset, YOffset, true);
}


This demonstrates a concept called Method Overloading. Basically that means you can have any number of methods with the same name, as long as they all have different parameters (the method name along with it's parameters forms the "Method Signature" which is what needs to be unique). Note that it isn't the parameter names that must be different, but the types or number of parameters.

In the first Draw method above (which is actually the one we will be using most of the time) we pass in a SpriteBatch object, an offset to the coordinates that the sprite thinks it is in, and a boolean value that determines if the AnimatedSprite should call it's own SpriteBatch.Begin and .End methods.

The method itself simply uses the GetSourceRect() method we wrote above to draw the needed portion of the texture onto the screen, optionally wrapping that with the .Begin and .End code.

The second Draw method leaves out the last parameter (NeedBeginEnd). All it does is pass the parameters along to the first draw method along with a "true" for the final parameter.

In theory, this makes the AnimatedSprite class more flexible, as we don't have to be inside a SpriteBatch sequence to draw. In practice though if we are drawing a lot of sprites we don't want to Begin and End batches for each one, as that will cut into our sprite drawing performance.

That's it! We now have an AnimatedSprite class that we can use to define and draw sprites!

What's that you say? We haven't actually drawn anything?

Ok, that's true. Since it is nice to see our work pay off a bit, lets head over to the "game1.cs" file and add a few lines to show off our sprite class. (I'll go into full detail about the things we are doing here in our next segment when we really start working with our textures and content… for now, I'll give a more brief overview of what we are doing.)

Right under the lines that say:

GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;

Add:
        AnimatedSprite Explosion;


This adds an instance of the AnimatedSprite class, called Explosion, to our game.

Now scroll down to "LoadContent" and update it to look like this:
protected override void LoadContent()
{
    // Create a new SpriteBatch, which can be used to draw textures.
    spriteBatch = new SpriteBatch(GraphicsDevice);
    Explosion = new AnimatedSprite(
      Content.Load<Texture2D>(@"Textures\Explosions"), 
      0, 0, 64, 64, 16);
    Explosion.X = 0;
    Explosion.Y = 0;
    // TODO: use this.Content to load your game content here
}


The LoadContent method is called automatically by XNA as the game starts up. This method is used to load all of our textures and objects. In the code above, we are calling the constructor of our AnimatedSprite class and using the Content object (which is an instance of the XNA Content Manager class) to load the Explosions texture. We pass the constructor the location (0,0), size (64x64 pixels) and number of frames (16) in our sprite. The class constructor will copy all of these to it's internal variables. Finally, we access the X and Y properties of our sprite to place it at 0,0 on the screen.

Add the following line to the "Update" method, right above the "base.Update(gameTime);" line:
            Explosion.Update(gameTime);


This calls our AnimatedSprite's Update method, which will accumulate elapsed time and increment the iCurrentFrame variable as necessary.

Finally, add the following three lines to the Draw method, right above the "base.Draw(gameTime);" line:
            spriteBatch.Begin();
            Explosion.Draw(spriteBatch,0,0,false);
            spriteBatch.End();


Here we start off a SpriteBatch.Begin and ask our AnimatedSprite to draw itself with an offset of 0,0, setting NeedBeginEnd to false since we are doing that ourself. For this little test, we could have left out the SpriteBatch.Begin and .End calls and simply set the last parameter in our Draw call to true (or left it off thanks to our overloaded Draw method) and it would have done that for us.

Run your project and you will see the familiar blue XNA window but have a repeating explosion playing in the upper right corner.

If you look at the "Explosions.png" file you will notice it is comprised of 8 different explosion animations. To change which explosion you see, simply change the 3rd parameter of the "Explosion = new AnimatedSprite" line in the LoadContent routine. Bumping it by 64 pixels at a time will select each of the different explosions in the file (note, the first numeric parameter should remain 0. It is the second 0 in the line above you want to update).



Ok! We have gotten off to a great start that will give us a foundation for creating the rest of our game objects. In the next installment of the tutorial series we will implement our scrolling background star fields.

(Continued in Part 3...)


































 

 
 
Site Contents Copyright © 2006 Full Revolution, Inc. All rights reserved.
This site is in no way affiliated with Microsoft or any other company.
All logos and trademarks are copyright their respective companies.
RSS FEED