/*
* 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);
}
}
}