Introduction
The basics of animation are well known; by rapidly switching among a group of still images, we can create the illusion that something is moving. Simply animating something isn't a challenge. In working on a game recently, I found the need to come up with a better way of managing my animation state. Often times there may be multiple objects displayed on the screen that are drawn from the same sprite sheet. But each one of these objects could be in a different state in its animation. There's also the matter of getting the sprite coordinates into the program. While you could type these coordinates into the program's code, that's not the most desired solution; if something in the images change, it would be necessary to make changes to the code. The purpose of the code attached to this article is to address both of those problems: managing animation state and managing the information as content.
The day after concluding that I needed a better solution, I was on a car ride for a few hours heading to a family reunion. What I document below is the solution I thought out the night before the drive and typed during the drive. This is a proof of concept and not final code, but it's an idea that I think others can easily extend to suit their own needs.
Prerequisites
To take advantage of the information in this article, you will need to already have familiarity with XNA and handling sprites. Being a .NET technology, this also means that you need to have familiarity with C# and Visual Studio. For software, you need to have a PC running Windows Vista or higher, and you need to have the Windows Phone Developer Tools installed (even if you don't plan on targeting Windows Phone). While the download is labeled as being for Windows Phone, it is actually a package that contains the dev tools for Windows Phone, Xbox, and PC.
PC, Xbox, and Windows Phone. The code in this article can be compiled to run on all three.
Running the Project
I know there are some that want to see the project running without needing to download and setup the environment. For the benefit of the people in that group, I've uploaded a vide to YouTube in which I briefly talk through the project and then run it.
Sprite Animation Basics
When rendering a sprite, you will normally have a source that has one or more images on it. You specify the coordinates of the bounding rectangle that contains the image you want to display along with the coordinates on the screen where you want it displayed. If you want a sprite to be animated, then you need to have an image for each frame of the animation to reference. You'll need to switch among the set of source images in sequence. XNA can render to the screen up to 60 frames per second (possibly faster, but let's ignore that). But you probably do not have a different version of your image for each one of those 60 frames. You will need to appropriately time when it is time to switch to the next image.
To better understand, it's helpful if you first download a sprite sheet to look at. There are an uncountable number of sprite sheets out there. I decided to Bing a sprite sheet for one of the Mario Brothers games and found no shortage of them. I found this little gem. This wasn't actually created by Nintendo but was created by fans of Nintendo games. Best of all, the sprites on this sheet are nicely organized and labeled. There's one of Mario running that I'm going to use. So I used Paint.Net to cop the image and erase the white background so that the image would be transparent.
Sprite sheet for Mario running
As your eyes follow each image in the sprite sheet, you can see how the animation will look. Ideally, the images in the sprite would be spaced evenly apart. For images that come from the days when available memory was measured in kilobytes, you may find this to not be the case since available memory had more impact on decisions than convenience. Who ever made this sprite seemed to only try to minimize space between the images so they are not evenly spaced. But I think the animation is cool and it brings back memories, so I am sticking with it. I also grabbed an image of a plane with a rotating propeller from another CodeProject.com article and found one of a bird flapping its wings. With these three pre-existing images, I found that a different amount of effort was needed to get them animated (which also gives me ideas on how I can improve this code in the future). I'll start with the simplest of the images (the planes from the CodeProject.com article) and then move to the more complex.
Many thanks to Jonas Follesø for his article on the game 1945 for PocketPC and publishing it under CPOL. I took the plane images from there.
Animating the Plane
One of the files in the article is named BigPlanes.bmp. I converted it from a bitmap to a PNG so that I could make it transparent. The image contains sprites for three planes. Each one of the planes has an animated propeller. These images are evenly spaced. The first plane starts at coordinate (1,1) and is 64 pixels wide and high. After the image of the first plane is a gray 1 pixel border, and then the second plane starts at position (66,1). Because of the 1 pixel border and 64 pixel width of the images, you can get the starting point of the nth plane by taking (n*65)+1. This makes it much easier to convert this sprite sheet into an animation. I don't have to look up the starting point of each image.
BigPlanes sprite sheet
I'm just going to animate the first two planes in the sheet. I'll have them as animations for different states of the same object. I got the starting X and Y positions of the three images {(1,1), (66, 1), (131, 1)} and the second three images {(196,1), (261, 1), (326, 1)} and put them in my code. For now, assume that the screen is being updated 30 times per second but I only want this animation to occur at 10 frames per second. At 30fps, the player will be able to smoothly move the plane around on the screen. So I will need to track how much time has passed before I advance the frame. In the following code, I animate the plane and provide a way of switching from one plane animation to another. I'm leaving out some of the typical pieces of code that you may expect to find in an XNA program so that I can highlight the parts that are specific to this task.
Rectangle[] _planeSourceSpriteLocation_1 = new Rectangle[]
{
new Rectangle( 1, 1, 64, 64),
new Rectangle( 66, 1, 64, 64),
new Rectangle(131, 1, 64, 64),
};
Rectangle[] _planeSourceSpriteLocation_2 = new Rectangle[]
{
new Rectangle(196, 1, 64, 64),
new Rectangle(261, 1, 64, 64),
new Rectangle(326, 1, 64, 64),
};
Rectangle[] _planeSourceSpriteLocation_current;
int _frameNumber;
TimeSpan _frameLength;
Vector2 _position = new Vector2(100,100);
Texture2D _planeTexture;
void SetPlaneSource(int i)
{
_planeSourceSpriteLocation_current= (i==1)?
_planeSourceSpriteLocation_2 :
_planeSourceSpriteLocation_1;
}
protected override void LoadContent()
{
SetPlaneSource(1);
}
protected override void Update(GameTime gameTime)
{
_frameLength += gameTime.ElapsedGameTime;
if(_frameLength.TotalSeconds > 0.1d)
{
_frameLenth -= TimeSpan.FromSeconds(0.1);
++_frameNumber;
if(_frameNumber >
One way of displaying the sprites.
You can see that's a lot of information to track and handle for a single sprite. This isn't the way you would want to do this for multiple sprites; the code would get messy fast. I'll show one way of organizing this information before this article is over.
Animating the Bird
For another sprite sheet I found of a bird, the images were not evenly spaced, but this didn't make for much of an obstacle in using it. The bird is flapping its wings but its head is stationary. So when I defined my bounding rectangles, I ensured that the bird's head was in the same place in each one of the rectangles. If this isn't done, the bird would appear to shake when it is animated. Once I have my binding rectangles, the rest of the task would be the same as it was with the plane.
Animated bird image.
The code from the previous example would work for animating this bird, though my array of rectangles would have 4 rectangles instead of 3.
Animating Mario Running
In trying to animate Mario, the first real obstacle was encountered. The images in that sprite were not evenly spaced. So I used the same technique that I did on the bird; I chose a stationary part of the image and got my coordinates relative to that stationary position. When I did this, I ran into a second problem; the images were not the same width. If I use a bounding rectangle that matches the minimum width of one of the images, then in some of the frames, parts of the animation get clipped. If I use a rectangle that matches the maximum width, then I will unintentionally end up with parts of the neighboring images inappropriately showing up in some frames. I could use rectangles of various widths so that each rectangle appropriately bound its intended frame without spilling over into the other frames, but doing that would obligate the Draw
code to be modified to accommodate the rectangles needing to be drawn slightly different. I thought about making a solution to address this but decided against it. Accommodating images like this makes the code considerably more complex. After much thought, I decided instead to make it a requirement that all sprites associated with an animation be of the same length. I don't enforce this within my code, but I don't support it either.
Representing a Sprite Animation
I've run through a few simple sprite animation scenarios and have a good feel for what I need for the code to do. What follows is one way of organizing the information needed for animating sprites into classes.
Specifying Sprite Coordinates
A sprite sheet is essentially just an image that has a lot of smaller images on it. Normally, when you are drawing an image from a sprite sheet, you will have to specify two sets of coordinates. Let's take a look at a typical call to draw a sprite onto the screen. I've put the data types in comments.
spriteBatch.Draw(
sourceTexture,
destinationRectangle,
sourceRectangle,
tint
);
The destination rectangle is going to depend on exactly where on the screen your object needs to be drawn, which will be dependent on where within your game world the object is currently positioned. But the source rectangle will have less variance. Every time you draw the same object, the source coordinates for the texture are going to be decided at compile time. So an attribute of the solution being sought is that it will need to be able to keep track of the source coordinates from the sprite sheet. Each source rectangle coordinates will also be associated with some length of time for which the sprite needs to be displayed. If I had an animation that was playing 15 frames per second, then the length of time associated with each frame would be 1/15 of a second.
public class SpriteSource
{
public TimeSpan FrameLength { get; set; }
public double FrameLengthSeconds
{
get { return FrameLength.TotalSeconds; }
set { FrameLength = TimeSpan.FromSeconds(value); }
}
public Rectangle SourceRectangle { get; set; }
}
The FrameLengthSeconds
field looks redundant, but since this data will be saved to a file, it must be serializable. The TimeSpan
member doesn't serialize, so I have added an alias field (FrameLengthSeconds
) of type double
that will serialize.
Grouping Coordinates into Frames
An animation is a collection of frames, so I need to be able to organize my frames into a list. A generic list class meets this need. But any given animated object could be associated with more than one animation. For example, you may have different animations for some one walking vs. some one running. You may have a different animation for when a player is in normal mode vs. powered up. Or you may have an animation for each direction in which a character could walk. So my animation will have a collection of frames, but each object could also have a collection of animations. I prefer to reference to animations by name in my class for storing the frames that belong to an animation. I also need to be able to identify which frame should be displayed at a specific time index. Since the animation shown is going to be dependent on an object's states, I'm borrowing from a XAML concept and will call the collection of frames that represent an object in a specific state, the VisualState
.
public class VisualState
{
public VisualState()
{
SpriteSourceList = new List<SpriteSource>();
}
public SpriteSource this[TimeSpan index]
{
get {
if(TotalLength.Ticks>0)
while (index > TotalLength)
index -= TotalLength;
int i = 0;
while(index>SpriteSourceList[i].FrameLength)
{
++i;
index -= SpriteSourceList[i].FrameLength;
}
return SpriteSourceList[i];
}
}
public TimeSpan TotalLength
{
get { return TimeSpan.FromTicks(
SpriteSourceList.Sum((ss) => ss.FrameLength.Ticks)); }
}
public string Name { get; set; }
public List<SpriteSource> SpriteSourceList { get; set; }
}
Grouping Animations for a Single Object
Since any object can have more than one animation, a list works out fine for grouping those animations into a single entity. Just for the sake of it, I also gave the collection of animations a name (there's no functional reason for doing this, but when debugging, it helps to be able to associate the collection with a name) and also added a member to reference the Texture2D
from which the animation will be pulling its images.
public class AnimatedSprite
{
public string Name { get; set; }
public string PreferredTextureName { get; set; }
[XmlIgnore]
internal Texture2D CurrentTexture { get; set; }
public void SetTexture(Texture2D sourceTexture)
{
CurrentTexture = sourceTexture;
}
public List<VisualState> VisualStateList { get; set; }
public AnimatedSprite()
{
VisualStateList = new List<VisualState>();
}
public AnimatedSpriteInstance CreateInstance()
{
return new AnimatedSpriteInstance(this);
}
}
There are two things in the above code that I haven't talked about. The CreateInstance()
method hasn't been explained nor has the reason that CurrentTexture
is marked as internal
been explained. I'll come back to the internal
marking on CurrentTexture
in a moment. Let's talk about CreateInstance()
.
Tracking Instances of Animations
There are likely to be multiple items on the screen that are using the same animation. It would be overkill to keep an exact duplicate of each animation for each object. But there is also data that must be specific to each instance. So I've separated the common data from the instance data. The AnimatedSprite
class contains the common data. Each object that has its own animation will need to have instance data which can be acquired from the CreateInstance()
class. This method instantiates a new AnimatedSpriteInstance()
. I've not made the constructor for this class public so the only way to create one is to first have an AnimatedSprite
and then call the CreateInstance()
method.
The most vital pieces of information that this class contains are a reference to the animated spite from which the instance was made (in the Parent
property), the time progress of the animation (in the _timeOffset
field), and the state, which determines which animation set is being used (in the PresentState
property, but manipulated through the StateName
property). If for some reason I decide that a specific instance of an animation should pull from a different texture, I've exposed a property called TextureOverride
that defaults to null
. Setting this to another texture will cause the animation instance to use that new texture. Setting it back to null
will cause it to revert back to its original texture. There is also a Position
member that you can use to set where the sprite will appear on the screen.
The animation is advanced through AnimationInstance.Update(GameTime)
. In general, you would call this early within the Draw
method. But if an animation needs to be synced with some timed event, then it would be better to call this in your game's Update()
method. The Draw()
method may not get called every cycle if something slows a game down, but Update
will always be called. All animations will loop once the last frame is reached. So the AnimatedSpriteInstance.Update()
method will reset the _timeOffset
whenever it reaches the end of the animation.
The Draw
method will take care of drawing the appropriate frame of the sprite. You only need to give it the SpriteBatch
instance to use. You might recall that the SpriteBatch
method has a number of overloaded Draw
methods. I've only used one in this example code, but you may want to change this to use one of the overloaded methods that provides more options. You could either expose these options by adding new members onto the AnimatedSpriteInstance.Draw()
method, or you could add new properties to hold values that would be passed to the additional parameters. For example, I've added a Color Tint { get; set; }
property that will get passed as the Tine
parameter for the animation.
public class AnimatedSpriteInstance
{
private TimeSpan _timeOffset;
public Vector2 Position { get; set; }
public AnimatedSprite Parent { get; internal set; }
public SpriteBatch TargetSpriteBatch { get; set; }
public Texture2D TextureOverride { get; set; }
public VisualState PresentState { get; protected set; }
public Color Tint { get; set; }
private string _stateName;
public string StateName
{
get { return _stateName; }
set
{
VisualState newState =
(from VisualState s in Parent.VisualStateList where
s.Name.Equals(value) select s).FirstOrDefault();
if(newState==null)
throw new IndexOutOfRangeException(String.Format(
"There is no state named {0} in this sprite", value));
_stateName = value;
PresentState = newState;
}
}
public AnimatedSpriteInstance()
{
Tint = Color.White;
TextureOverride = null;
Reset();
}
public AnimatedSpriteInstance(AnimatedSprite parent):this()
{
Parent = parent;
Reset();
}
public void Reset()
{
_timeOffset = TimeSpan.Zero;
if (Parent != null)
{
PresentState = Parent.VisualStateList[0];
_stateName = PresentState.Name;
}
}
public void Update(GameTime gameTime)
{
_timeOffset += gameTime.ElapsedGameTime;
while (_timeOffset >= PresentState.TotalLength)
_timeOffset -= PresentState.TotalLength;
}
public void Draw(SpriteBatch targetBatch)
{
var spriteSource = PresentState[_timeOffset].SourceRectangle;
targetBatch.Draw( (TextureOverride ?? Parent.CurrentTexture),
Position, spriteSource, Color.White);
}
public void Draw()
{
Draw(TargetSpriteBatch);
}
}
Class Diagram
Class diagram.
Making Support for an Animation to be Content
Part of the reason that I wrote this code is I wanted to start removing the specifics of an animation from my code and put it into another resource that some one without a programming skill set can modify and create the animations. My first step in this is to allow the information on the sprite locations to be specified in an XML file. While I don't expect most artists to be familiar with XML files, it is an incremental step closer to a human (artist) friendly solution. The next step would be to create a tool so that some one could specify the locations of the sprites graphically. I won't be tackling that part in this article. So I only want to talk about creating the XML file here and consuming it in an XNA project. I'll talk on creating a graphical tool for manipulating this information after I've used this code for several more scenarios. I don't know what changes future needs will bring to the code, and don't want to yet have a graphical editor that would possibly require being updated as I adjust the animation code. Once the animation code further matures and handles a wider range of scenarios, then I will look at making a graphical tool.
XNA has a facility called the content pipeline made to handle the processing of contents in a project. If you are new to XNA, you've probably been happily using the content pipeline without having to consider its inner workings. The content pipeline already has what is needed to handle some common file types (MP3, PNG, 3D Model Formats, and so on) and I wanted to extend it to also be able to handle my animations. Once done, I would be able to use the same technique for games made for the PC, the Xbox, and the Windows Phone.
Content Pipeline Basics
Assets that go through the content pipeline are processed in four different objects: an importer, a processor, a writer, and a reader. The importer reads the asset and passes it off to the processor. The processor looks at the data that was read from the asset and interprets it, building some other object that represents the form that you want the data to be in when the content is loaded. The writer serializes the object to a stream. And the reader will be used by the game at runtime to load the saved content. XNA defines generic base classes for implementing each one of these types of objects.
Of those four types of objects, three of them are used at design time (importer, processor, writer) and one is used at runtime (reader). The three design-time classes can be part of one project. The runtime class must be deployed with your project and a version of the runtime class must be created for each platform on which your game will run. In my case, the runtime projects will just be projects that share the same files since there will be no difference in the source code from Windows Phone to PC to Xbox. Let's look at how I implemented each one of these classes in more detail. I started off by creating a new project. In the new project dialog, there is an option for a Content Extension project under the XNA group. After I made the project, I started adding the following to it.
Content Importer
XNA defines the generic base class ContentImporter<T>
for implementing a content importer. This class only loads the data into something that can be passed to the processor, but otherwise does not do any processing on the file. The content importer must declare the extension for the types of files it processes and define the processor to be used along with a friendly display name. All of this information is passed through a ContentImporterAttribute
on the class.
[ContentImporter(".animatedSprite",
DefaultProcessor = "AnimatedSpriteProcessor",
DisplayName = "Animated Sprite Importer")]
public class AnimatedSpriteImporter: ContentImporter<AnimatedSpriteDescription>
{
public override AnimatedSpriteDescription Import(
string filename, ContentImporterContext context)
{
string spriteXml = File.ReadAllText(filename);
return new AnimatedSpriteDescription(
Path.GetFileNameWithoutExtension(filename), spriteXml);
}
}
AnimatedSpriteDescription
is a class I've defined for holding the contents of the file. It only contains two fields, AnimatedSpriteXml
and AnimationName
, both of which are strings.
public class AnimatedSpriteDescription
{
public string AnimatedSpriteXml { get; set; }
public string AnimationName { get; set; }
public AnimatedSpriteDescription(string name, string xml)
{
AnimationName = name;
AnimatedSpriteXml = xml;
}
}
Content Processor
The content processor converts the data it receives from the ContentImporter
to an object. The generic base class for implementing a ContentProcessor
, as you may have guessed, is ContentProcessor<InputType, OutputType>
. InputType
needs the type of your importer and OutputType
should be set to the type that the processor produces. I just introduced you to the importer. The output of my processor is an AnimatedSprite
; the same type that I described in the beginning of this article. The only method that needs to be overridden in the derived class is the Process
method.
[ContentProcessor(DisplayName = "Animated Sprite Processor")]
public class AnimatedSpriteProcessor :
ContentProcessor<AnimatedSpriteDescription, AnimatedSprite>
{
public override AnimatedSprite Process(AnimatedSpriteDescription input,
ContentProcessorContext context)
{
XmlSerializer xs = new XmlSerializer(typeof(AnimatedSprite));
StringReader sr = new StringReader(input.AnimatedSpriteXml);
var entry = (AnimatedSprite)xs.Deserialize(sr);
return entry;
}
}
Since I went with an XML format, you can see that I didn't have to do much processing.
Content Writer
The last design time component through which your asset will pass is a class derived from ContentTypeWriter<ContentType>
. It has a few methods that need to be overridden. As you would expect, there is a Write
method that must serialize your content. As arguments, it receives a ContentWriter
and the object produced by the ContentProcessor
that needs to be written. There are two other methods that need to be overridden too: GetRuntimeType
and GetRuntimeReader
. The method GetRuntimeType
should return a string that identifies the assembly and the type within the assembly that must be instantiated for holding your content. GetRuntimeType
returns a string that identifies the assembly and the class within the assembly that contains the ContentReader
.
class AnimatedSpriteWriter: ContentTypeWriter<AnimatedSprite>
{
protected override void Write(ContentWriter output, AnimatedSprite value)
{
XmlSerializer xs = new XmlSerializer(typeof(AnimatedSprite));
System.IO.StringWriter sw = new StringWriter();
xs.Serialize(sw, value);
output.Write(value.Name);
output.Write(sw.ToString());
}
public override string GetRuntimeType(
Microsoft.Xna.Framework.Content.Pipeline.TargetPlatform targetPlatform)
{
return "J2i.Net.AnimatedSriteLibrary, AnimatedSprite, " +
"Version=1.0.0.0, Culture=neutral";
}
public override string GetRuntimeReader(
Microsoft.Xna.Framework.Content.Pipeline.TargetPlatform targetPlatform)
{
return "J2i.Net.AnimatedSriteLibrary, AnimatedSpriteReader, " +
"Version=1.0.0.0, Culture=neutral";
}
}
ContentTypeReader
The other three content classes that I've talked about all execute on your PC while you are building a game. None of them get pushed out to the machine or device that will run the game. The ContentTypeReader
differs from these in that it must be distributed with the game. Since your XNA game could be running on one of three platforms (Windows Phone, Xbox, and PC), you will need to have three versions of your content reader. The source code for all three can be identical. You can even use the same file to compile all three versions. But each one of these platforms has its own binary format for executables so you won't have the exact same binary for all three platforms. For now, I am going to stick with targeting PC only. Adding the support needed for Windows Phone and Xbox is a trivial effort so I will save that task for later.
Since the ContentTypeReader
will be used by the same code that uses the AnimatedSprite
that I built earlier, I find it appropriate to make it part of the same project. I added a new class to that project and had it inherit from ContentTypeReader<AnimatedSprite>
and overrode the Read
method. As arguments, this method receives a reference to a ContentReader
and an instantiated object that the contents could be written to. Since I used XML serialization to write the asset, I can use the Deserialize
method on the XmlSerializer
class once I read the data from the ContentTypeReader
.
public class AnimatedSpriteReader : ContentTypeReader<AnimatedSprite>
{
protected override AnimatedSprite Read(ContentReader input,
AnimatedSprite existingInstance)
{
var xs = new System.Xml.Serialization.XmlSerializer(typeof(AnimatedSprite));
string name = input.ReadString();
string info = input.ReadString();
System.IO.StringReader sr = new System.IO.StringReader(info);
var animatedSprite = (AnimatedSprite)xs.Deserialize(sr);
return animatedSprite;
}
}
Creating the Animation
Creating an animation is just a matter of typing an XML file with the right coordinates and giving it the .animatedSprite extension. I've typed the code for the plane animation in the following. You might notice that each frame has its own FrameLengthSeconds
setting. This is because not every frame has to be the same length. I've also given the animation two states; one called Main
that will be the animation's default state and another called Green
.
="1.0"="utf-8"
<AnimatedSprite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Plane Map</Name>
<VisualStateList>
<VisualState>
<Name>Main</Name>
<SpriteSourceList>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>1</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>67</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>133</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
</SpriteSourceList>
</VisualState>
<VisualState>
<Name>Green</Name>
<SpriteSourceList>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>199</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>265</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>331</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
</SpriteSourceList>
</VisualState>
</VisualStateList>
</AnimatedSprite>
To use the sprite, I had to do a few things to the content project in my XNA game:
- Add a reference to the animation design time project
- Add the .animatedSprite file(s) to the content project
- Add the image resources to the content project
Content project with animated sprite assets added.
In the Game project, there are also a few things that need to be done. I need to add a reference to the project that contains the runtime information. I put both the ContentTypeReader
and the class declarations for my sprites in the same project, though this isn't mandatory. Once that is done, I can load the sprite animations, images, and start displaying them. Recall that you must create an instance of AnimatedSpriteInstance
to display something on screen. Once the sprite instance is created, the only methods I need to call to keep it animating are Update
and Draw
. The class will keep track of everything else on its own.
private AnimatedSprite _animatedSprite;
private Texture2D _spriteTexture;
private AnimatedSpriteInstance _spriteInstance;
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
_spriteTexture = Content.Load<Texture2D>("allies");
_animatedSprite = Content.Load<AnimatedSprite>("MyAnimatedSprite");
SpriteFont sf = Content.Load<SpriteFont>("DebugFont");
_animatedSprite.SetTexture(_spriteTexture);
_spriteInstance = _animatedSprite.CreateInstance();
}
protected override void Update(GameTime gameTime)
{
_spriteInstance.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
_spriteInstance.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
When an animation has multiple visual states, I can switch between the states by setting the StateName
value on the instance.
_spriteInstance.StateName="NewState";
Running on Other Platforms
I mentioned earlier that you would need to make a version of the run time code for each platform on which you intend it to run. When I started off, I was only running this code on a PC. Making it run on a Windows Phone or Xbox requires little effort. First, let's take a look at the project layout.
Project layout.
I have the game as a Windows XNA project, the game's content project, a game class library (which contains the runtime classes), and the content extension project (which contains the design time components). Of these projects, only the Windows XNA game project and the class library need to be converted. The others will remain untouched. Right-click on the game project and select either the option to copy the project for Xbox 360 or Windows Phone.
Both the game and the class library on which it depends will be copied into a Windows Phone or Xbox project. Perhaps I should use the word "copy" because the same source files are being referenced by the two projects. If you make a change to a file in one project, since the file is shared, you will see the change in the other also. When you try to compile the project, it will fail, stating that it cannot find the class definition for System.Xml.Serialization.XmlSerialization
. On Windows Phone and Xbox, this class exists in its own assembly, while on the PC, it exists in the System.Xml.dll assembly. So you will need to add a reference to the System.Xml.Serialization.dll assembly in both the runtime project and the game project.
Future Improvements
I've written this as a proof of concept. There are some other features I want it to support but rather than trying to get it to do everything I wanted to do at once, I decided it was best to start small. Part of my goal was to get this working during the car ride and that limits how much I can get done. Though I don't view that time limit as a bad thing; the goal for this project was to get something that is initially satisficing. In using what I have so far, I had some realizations such as needing support for "debugging" sprites. I implemented a quick solution to fit my needs so that I could see the frame index being used at any point in time. I also will need support for drawing sprites at something other than their natural size and support for rotation in the future. For now, I am just going to keep track of needs for the future and modify this code enough so that it meets the needs for the game I am working on now (I don't want to get side tracked with scope creep). I've also added a Scale
property for enlarging the image, though the implementation in the code isn't the implementation I want to have in the final code.