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 |
---|---|---|
|
|
Sprite or sprite sheet of image being drawn |
|
|
Position at which to draw sprite |
|
|
Size of each individual frame in sprite sheet |
|
|
Offset used to modify frame-size rectangle for collision checks against this sprite |
|
|
Index of current frame in sprite sheet |
|
|
Number of columns/rows in sprite sheet |
|
|
Number of milliseconds since last frame was drawn |
|
|
Number of milliseconds to wait between frame changes |
|
|
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 constructor method |
|
|
Handles all collision checks, movement, user input, and so on |
|
|
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.