/* * Animation.cs * Authors: August Zinsser * * Copyright Matthew Belmonte 2007 */ using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; namespace tAC_Engine { /// /// Used to store and manage the frames of multiple animations. /// All frames must be the same size and are assumed to go left to right, top to bottom. /// Objects that use this class can get the appropriate texture2D at any time. They also must update this class regularly. /// public class Animation { /// /// Stores all information needed for one specific animation /// private class Clip { public Texture2D SpriteSheet; // Stores a sprite sheet containing all the frames of this animation. Sheets are read left to right, top to bottom like a book. There must also be no holes. public int Frames; // Number of frames of this animation. public int FrameOffset = 0; // Which frame to start on public float Delay = 0; // Delay in seconds between each frame. public int CurrentFrame; // Frame currently being displayed. /// /// Increments the current frame and automatically wraps around to 0 as necessary /// public void IncrementFrame() { CurrentFrame++; if (CurrentFrame >= Frames) CurrentFrame = FrameOffset; } } private Dictionary mClips = new Dictionary(); // Stores all particular animations, keyed by their names private Clip mCurrentClip; // The clip currently being displayed private Clip mLastClip; // The last clip that was displayed private int mLastFrame; // The last frame of the last clip that was displayed private Texture2D mDisplayTexture; // Holds an image of the last texture displayed (used to speed up get_Texture, but it's still really slow!) private static Texture2D mNoTexture = // Holds an "empty" picture which get_Texture sometimes returns new Texture2D(GenericBaseApplication.GameManager.GraphicsDevice, 1, 1, 1, ResourceUsage.None, SurfaceFormat.Color); private int mValidWidth; // Holds the validated width of each frame (stored as an int, not in a Vector2 of type float) private int mValidHeight; // '' (height) private Vector2 mSize; // Size of every frame (in source image pixels) private bool mPaused = false; // Stops the cycling of animations private float mFrameUpdateCounter; // Used to keep track of when to update a frame public bool Paused { set { mPaused = value; } get { return mPaused; } } /// /// Returns a new Texture2D with the current frame's image. Use carefully, as this function is slow for clips that are more than one frame /// public Texture2D CurrentFrameCopy { get { if (mCurrentClip != null && (int)mSize.X > 1) { // See if it is necessary to update the display texture if (!(mCurrentClip == mLastClip && mCurrentClip.CurrentFrame == mLastFrame)) { // Check if the clip has multiple frames if (mCurrentClip.Frames == 1) { // This is a still clip, so just return the whole sheet return mCurrentClip.SpriteSheet; } else { // Initialize the Display Texture, if necessary if (mDisplayTexture == null) mDisplayTexture = new Texture2D(GenericBaseApplication.GameManager.GraphicsDevice, mValidWidth, mValidHeight, 1, ResourceUsage.None, SurfaceFormat.Color); // Copy the color data from the proper area of the current sprite sheet into the display texture Color[] colorData = new Color[(int)mSize.X * (int)mSize.Y]; ((Texture2D)mCurrentClip.SpriteSheet).GetData(0, GetSourceRectangle(), colorData, 0, colorData.Length); mDisplayTexture.SetData(0, null, colorData, 0, colorData.Length, SetDataOptions.None); } mLastClip = mCurrentClip; mLastFrame = mCurrentClip.CurrentFrame; } return mDisplayTexture; } else return mNoTexture; } } /// /// Returns a reference to the sprite sheet of the current frame. Unlike CurrentFrameCopy, this call is fast. /// public Texture2D CurrentSpriteSheet { get { if (mCurrentClip != null) return mCurrentClip.SpriteSheet; else return null; } } /// /// Returns the width of each frame /// public float Width { get { return mSize.X; } } /// /// Returns the height of each frame /// public float Height { get { return mSize.Y; } } /// /// Default Constructor /// private Animation() { } /// /// Initialize the animation and set the size of each frame /// /// Size of each frame in source pixels public Animation(Vector2 frameSize) : this() { mSize = frameSize; // Validate its dimensions mValidWidth = (int)MathHelper.Max((int)mSize.X, 1); mValidHeight = (int)MathHelper.Max((int)mSize.Y, 1); } /// /// Initialize the animation and set the size of each frame /// /// Height of each frame /// Width of each frame public Animation(float frameWidth, float frameHeight) : this (new Vector2(frameWidth, frameHeight)) { } /// /// Creates a new clip that can be used for animation /// /// Identifier of the clip /// Spritesheet containing the frames of the clip /// Number of frames for this clip /// Seconds to wait before advancing to the next frame public void AddClip(string name, Texture2D spriteSheet, int frames, float frameDelay) { AddClip(name, spriteSheet, frames, 0, frameDelay); } /// /// Creates a new clip that can be used for animation /// /// Identifier of the clip /// Spritesheet containing the frames of the clip /// Number of frames for this clip /// First frame of this clip (0-indexed) /// Seconds to wait before advancing to the next frame public void AddClip(string name, Texture2D spriteSheet, int frames, int frameOffset, float frameDelay) { Clip newClip = new Clip(); newClip.Frames = frames; newClip.Delay = frameDelay; newClip.SpriteSheet = spriteSheet; newClip.FrameOffset = frameOffset; newClip.CurrentFrame = frameOffset; mClips.Add(name, newClip); } /// /// Changes the selected animation. Does not affect the Paused state of this animation. /// /// public void Play(string clipName) { if (mClips.ContainsKey(clipName)) mCurrentClip = mClips[clipName]; } /// /// Cycles through frames as appropriate /// public void Update() { if (mCurrentClip != null && !mPaused && mCurrentClip.Delay != 0) { // See if it is time to transition to the next frame mFrameUpdateCounter -= GenericBaseApplication.GameManager.ElapsedSeconds; while (mFrameUpdateCounter <= 0) { // Update to the next frame mCurrentClip.IncrementFrame(); mFrameUpdateCounter += mCurrentClip.Delay; } } } /// /// Draws the current frame to the given spritebatch using alphablending /// /// public void Draw(SpriteBatch spriteBatch, Rectangle destination, Color tint, float rotation, SpriteEffects spriteEffects, float layerDepth) { if (mCurrentClip.SpriteSheet == null) return; Rectangle slice = GetSourceRectangle(); spriteBatch.Draw( mCurrentClip.SpriteSheet, destination, slice, tint, rotation, new Vector2(slice.Width / 2, slice.Height / 2), spriteEffects, layerDepth ); } /// /// Generates a rectangle with the location and size of the current frame /// /// private Rectangle GetSourceRectangle() { int col = mCurrentClip.CurrentFrame; int row = 0; // See if the frame index has wrapped past the first row while (col * mSize.X >= mCurrentClip.SpriteSheet.Width) { col -= (int)(mCurrentClip.SpriteSheet.Width / mSize.X); row++; } return new Rectangle(col * (int)mSize.X, row * (int)mSize.Y, (int)mSize.X, (int)mSize.Y); } } }