#region File Description //----------------------------------------------------------------------------- // AnimatingSprite.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; #endregion namespace RolePlayingGameData { /// /// A sprite sheet with flipbook-style animations. /// public class AnimatingSprite : ContentObject #if WINDOWS , ICloneable #endif { #region Graphics Data /// /// The content path and name of the texture for this spell animation. /// private string textureName; /// /// The content path and name of the texture for this spell animation. /// public string TextureName { get { return textureName; } set { textureName = value; } } /// /// The texture for this spell animation. /// private Texture2D texture; /// /// The texture for this spell animation. /// [ContentSerializerIgnore] public Texture2D Texture { get { return texture; } set { texture = value; } } #endregion #region Frame Data /// /// The dimensions of a single frame of animation. /// private Point frameDimensions; /// /// The width of a single frame of animation. /// public Point FrameDimensions { get { return frameDimensions; } set { frameDimensions = value; frameOrigin.X = frameDimensions.X / 2; frameOrigin.Y = frameDimensions.Y / 2; } } /// /// The origin of the sprite, within a frame. /// private Point frameOrigin; /// /// The number of frames in a row in this sprite. /// private int framesPerRow; /// /// The number of frames in a row in this sprite. /// public int FramesPerRow { get { return framesPerRow; } set { framesPerRow = value; } } /// /// The offset of this sprite from the position it's drawn at. /// private Vector2 sourceOffset; /// /// The offset of this sprite from the position it's drawn at. /// [ContentSerializer(Optional=true)] public Vector2 SourceOffset { get { return sourceOffset; } set { sourceOffset = value; } } #endregion #region Animation Data /// /// The animations defined for this sprite. /// private List animations = new List(); /// /// The animations defined for this sprite. /// public List Animations { get { return animations; } set { animations = value; } } /// /// Enumerate the animations on this animated sprite. /// /// The name of the animation. /// The animation if found; null otherwise. public Animation this[string animationName] { get { if (String.IsNullOrEmpty(animationName)) { return null; } foreach (Animation animation in animations) { if (String.Compare(animation.Name, animationName, StringComparison.OrdinalIgnoreCase) == 0) { return animation; } } return null; } } /// /// Add the animation to the list, checking for name collisions. /// /// True if the animation was added to the list. public bool AddAnimation(Animation animation) { if ((animation != null) && (this[animation.Name] == null)) { animations.Add(animation); return true; } return false; } #endregion #region Playback /// /// The animation currently playing back on this sprite. /// private Animation currentAnimation = null; /// /// The current frame in the current animation. /// private int currentFrame; /// /// The elapsed time since the last frame switch. /// private float elapsedTime; /// /// The source rectangle of the current frame of animation. /// private Rectangle sourceRectangle; /// /// The source rectangle of the current frame of animation. /// public Rectangle SourceRectangle { get { return sourceRectangle; } } /// /// Play the given animation on the sprite. /// /// The given animation may be null, to clear any animation. public void PlayAnimation(Animation animation) { // start the new animation, ignoring redundant Plays if (animation != currentAnimation) { currentAnimation = animation; ResetAnimation(); } } /// /// Play an animation given by index. /// public void PlayAnimation(int index) { // check the parameter if ((index < 0) || (index >= animations.Count)) { throw new ArgumentOutOfRangeException("index"); } PlayAnimation(this.animations[index]); } /// /// Play an animation given by name. /// public void PlayAnimation(string name) { // check the parameter if (String.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); } PlayAnimation(this[name]); } /// /// Play a given animation name, with the given direction suffix. /// /// /// For example, passing "Walk" and Direction.South will play the animation /// named "WalkSouth". /// public void PlayAnimation(string name, Direction direction) { // check the parameter if (String.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); } PlayAnimation(name + direction.ToString()); } /// /// Reset the animation back to its starting position. /// public void ResetAnimation() { elapsedTime = 0f; if (currentAnimation != null) { currentFrame = currentAnimation.StartingFrame; // calculate the source rectangle by updating the animation UpdateAnimation(0f); } } /// /// Advance the current animation to the final sprite. /// public void AdvanceToEnd() { if (currentAnimation != null) { currentFrame = currentAnimation.EndingFrame; // calculate the source rectangle by updating the animation UpdateAnimation(0f); } } /// /// Stop any animation playing on the sprite. /// public void StopAnimation() { currentAnimation = null; } /// /// Returns true if playback on the current animation is complete, or if /// there is no animation at all. /// public bool IsPlaybackComplete { get { return ((currentAnimation == null) || (!currentAnimation.IsLoop && (currentFrame > currentAnimation.EndingFrame))); } } #endregion #region Updating /// /// Update the current animation. /// public void UpdateAnimation(float elapsedSeconds) { if (IsPlaybackComplete) { return; } // loop the animation if needed if (currentAnimation.IsLoop && (currentFrame > currentAnimation.EndingFrame)) { currentFrame = currentAnimation.StartingFrame; } // update the source rectangle int column = (currentFrame - 1) / framesPerRow; sourceRectangle = new Rectangle( (currentFrame - 1 - (column * framesPerRow)) * frameDimensions.X, column * frameDimensions.Y, frameDimensions.X, frameDimensions.Y); // update the elapsed time elapsedTime += elapsedSeconds; // advance to the next frame if ready while (elapsedTime * 1000f > (float)currentAnimation.Interval) { currentFrame++; elapsedTime -= (float)currentAnimation.Interval / 1000f; } } #endregion #region Drawing /// /// Draw the sprite at the given position. /// /// The SpriteBatch object used to draw. /// The position of the sprite on-screen. /// The depth at which the sprite is drawn. public void Draw(SpriteBatch spriteBatch, Vector2 position, float layerDepth) { Draw(spriteBatch, position, layerDepth, SpriteEffects.None); } /// /// Draw the sprite at the given position. /// /// The SpriteBatch object used to draw. /// The position of the sprite on-screen. /// The depth at which the sprite is drawn. /// The sprite-effect applied. public void Draw(SpriteBatch spriteBatch, Vector2 position, float layerDepth, SpriteEffects spriteEffect) { // check the parameters if (spriteBatch == null) { throw new ArgumentNullException("spriteBatch"); } if (texture != null) { spriteBatch.Draw(texture, position, sourceRectangle, Color.White, 0f, sourceOffset, 1f, spriteEffect, MathHelper.Clamp(layerDepth, 0f, 1f)); } } #endregion #region Content Type Reader /// /// Read an AnimatingSprite object from the content pipeline. /// public class AnimatingSpriteReader : ContentTypeReader { /// /// Read an AnimatingSprite object from the content pipeline. /// protected override AnimatingSprite Read(ContentReader input, AnimatingSprite existingInstance) { AnimatingSprite animatingSprite = existingInstance; if (animatingSprite == null) { animatingSprite = new AnimatingSprite(); } animatingSprite.AssetName = input.AssetName; animatingSprite.TextureName = input.ReadString(); animatingSprite.Texture = input.ContentManager.Load( System.IO.Path.Combine(@"Textures", animatingSprite.TextureName)); animatingSprite.FrameDimensions = input.ReadObject(); animatingSprite.FramesPerRow = input.ReadInt32(); animatingSprite.SourceOffset = input.ReadObject(); animatingSprite.Animations.AddRange( input.ReadObject>()); return animatingSprite; } } #endregion #region ICloneable Members /// /// Creates a clone of this object. /// public object Clone() { AnimatingSprite animatingSprite = new AnimatingSprite(); animatingSprite.animations.AddRange(animations); animatingSprite.currentAnimation = currentAnimation; animatingSprite.currentFrame = currentFrame; animatingSprite.elapsedTime = elapsedTime; animatingSprite.frameDimensions = frameDimensions; animatingSprite.frameOrigin = frameOrigin; animatingSprite.framesPerRow = framesPerRow; animatingSprite.sourceOffset = sourceOffset; animatingSprite.sourceRectangle = sourceRectangle; animatingSprite.texture = texture; animatingSprite.textureName = textureName; return animatingSprite; } #endregion } }