Creating a Sprite Class

Now you can go ahead and get started on your Sprite base class. What might you want to include in that class? Table 4-1 lists the members, and Table 4-2 lists the methods.

Table 4-1. Members of your Sprite class

Member

Type

Description

textureImage

Texture2D

Sprite or sprite sheet of image being drawn

position

Vector2

Position at which to draw sprite

frameSize

Point

Size of each individual frame in sprite sheet

collisionOffset

int

Offset used to modify frame-size rectangle for collision checks against this sprite

currentFrame

Point

Index of current frame in sprite sheet

sheetSize

Point

Number of columns/rows in sprite sheet

timeSinceLastFrame

int

Number of milliseconds since last frame was drawn

millisecondsPerFrame

int

Number of milliseconds to wait between frame changes

speed

Vector2

Speed at which sprite will move in both X and Y directions

Table 4-2. Methods of your Sprite class

Method

Return type

Description

Sprite(...) (multiple constructors)

Constructor

Sprite constructor method

Update(GameTime, Rectangle)

void

Handles all collision checks, movement, user input, and so on

Draw(GameTime, SpriteBatch)

void

Draws the sprite

This chapter will build upon the code that you created in Chapter 3. Open the code from that chapter and add a new class to your project by right-clicking on the project in Solution Explorer and selecting Add → Class. Name the new class file Sprite.cs.

Because you'll probably never find a reason to create an instance of the Sprite class, it makes sense to mark the class as abstract, forcing you to use one of the derived classes to instantiate objects. Make the base Sprite class abstract by adding the keyword abstract to the definition of the class:

abstract class Sprite

Add two XNA namespaces to your new class, which you'll need to be able to use XNA objects:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

Then, add the following variables, which correspond to the members of the class shown in Table 4-1. Make sure you mark the protected variables appropriately, or the subclasses you build later will not work properly:

Texture2D textureImage;
protected Vector2 position;
protected Point frameSize;
int collisionOffset;
Point currentFrame;
Point sheetSize;
int timeSinceLastFrame = 0;
int millisecondsPerFrame;
protected Vector2 speed;
const int defaultMillisecondsPerFrame = 16;

In addition to the variables listed in Table 4-1, you're defining a constant that will represent the default animation speed if no animation speed is specified.

Next, add the two constructors, as follows:

public Sprite(Texture2D textureImage, Vector2 position, Point frameSize,
    int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed)
    : this(textureImage, position, frameSize, collisionOffset, currentFrame,
    sheetSize, speed, defaultMillisecondsPerFrame)
{
}

public Sprite(Texture2D textureImage, Vector2 position, Point frameSize,
    int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed,
    int millisecondsPerFrame)
{
    this.textureImage = textureImage;
    this.position = position;
    this.frameSize = frameSize;
    this.collisionOffset = collisionOffset;
    this.currentFrame = currentFrame;
    this.sheetSize = sheetSize;
    this.speed = speed;
    this.millisecondsPerFrame = millisecondsPerFrame;
}

The only difference between the two constructors is that the second one requires a millisecondsPerFrame variable, used to calculate the animation speed. Hence, the first constructor will just call the second constructor (using the this keyword) and pass to that constructor all of its parameters, as well as the constant representing the default animation speed.

At a minimum, all animated sprites do two things: animate by moving a current frame index through the images on a sprite sheet, and draw the current image from the animation on the screen. Beyond that, you may want to add additional functionality to this class, or you may prefer to add it to a derived class to create a more specialized object. At the very least, though, you'll want to animate and draw in all animated sprites, so let's add the functionality to do that in the base Sprite class.

You've already written the code to animate and draw in previous chapters. All you need to do now is take that same code, which uses variables from the Game1 class, and apply it to the variables that you've defined in your Sprite base class. Once again, to perform the animation, all you do is move a current frame index through a sprite sheet, making sure to reset the index once it's passed through the entire sheet.

The following code, which should be familiar from previous chapters, does just that. Code the Update method of your Sprite base class as follows:

public virtual void Update(GameTime gameTime, Rectangle clientBounds)
{
    timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
    if (timeSinceLastFrame > millisecondsPerFrame)
    {
        timeSinceLastFrame = 0;
        ++currentFrame.X;
        if (currentFrame.X >= sheetSize.X)
        {
            currentFrame.X = 0;
            ++currentFrame.Y;
            if (currentFrame.Y >= sheetSize.Y)
                currentFrame.Y = 0;
        }
    }
}

You probably noticed the keyword virtual in the method declaration. This marks the method itself as virtual, which enables you to create overrides for this method in subclasses so you can modify its functionality in those classes if needed.

You may also have noticed the Rectangle parameter. This parameter represents the game window's client rectangle and will be used to detect when objects cross the edges of the game window.

Just as you've previously written code for animation, you've also already written code to draw a single frame of an animated sprite. Now you just need to take that code and plug it into your Sprite class, changing it to use the variables you've defined here.

The one difference between the drawing code from previous chapters and the code you need to add to your Sprite class is that in this case you have no access to a SpriteBatch object (which, as you hopefully remember, is required to draw Texture2D objects). To get around that, you need to accept a GameTime parameter and add a SpriteBatch parameter to the Draw method of your Sprite base class.

Your Sprite base class's Draw method should look something like this:

public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
    spriteBatch.Draw(textureImage,
        position,
        new Rectangle(currentFrame.X * frameSize.X,
            currentFrame.Y * frameSize.Y,
            frameSize.X, frameSize.Y),
        Color.White, 0, Vector2.Zero,
        1f, SpriteEffects.None, 0);
}

In addition to the Update and Draw methods, you're going to want to add a property to this class that represents the direction in which this sprite is moving. The direction will always be a Vector2, indicating movement in the X and Y directions, but it will also always be defined by the subclasses (i.e., automated sprites will move differently than user-controlled sprites). So, this property needs to exist in the base class, but it should be abstract, meaning that in the base class it has no body and it must be defined in all subclasses.

Add the abstract direction property to the base class as follows:

public abstract Vector2 direction
{
    get;
}

There's one more thing to add to your Sprite base class: a property that returns a rectangle that can be used for collision detection. Add the following property to your Sprite class:

public Rectangle collisionRect
{
    get
    {
        return new Rectangle(
            (int)position.X + collisionOffset,
            (int)position.Y + collisionOffset,
            frameSize.X - (collisionOffset * 2),
            frameSize.Y - (collisionOffset * 2));
    }
}

Your base class is pretty well ironed out at this point. The class will draw itself via the Draw method and will cycle through a sprite sheet via the Update method. So, let's turn our attention to the user-controlled sprite for a moment.

Get Learning XNA 3.0 now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.