/*
* Animation.cs
* Authors: August Zinsser, Adam Nabinger
* Copyright (c) 2007-2008 Cornell University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MaritimeDefender
{
///
/// 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
{
#region Fields
///
/// 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 Texture2D SpriteSheet;
///
/// Number of frames of this animation.
///
public int Frames;
///
/// Which frame to start on
///
public int FrameOffset;
///
/// Delay in seconds between each frame.
///
public float Delay;
///
/// Frame currently being displayed.
///
public int CurrentFrame;
#endregion
#region IncrementFrame
///
/// Increments the current frame and automatically wraps around to 0 as necessary
///
public void IncrementFrame()
{
if (++CurrentFrame >= Frames)
{
CurrentFrame = FrameOffset;
}
}
#endregion
}
#region Fields
// Stores all particular animations, keyed by their names
private readonly Dictionary mClips = new Dictionary();
// The clip currently being displayed
private Clip mCurrentClip;
// Size of every frame (in source image pixels)
private Vector2 mSize;
// Stops the cycling of animations
private bool mPaused;
// Used to keep track of when to update a frame
private float mFrameUpdateCounter;
#endregion
#region Properties
///
/// Whether or not the animation is currently paused
///
public bool Paused { set { mPaused = value; } get { return mPaused; } }
///
/// Returns a new Texture2D with the current frame's image if the current clip is only 1 frame, else null.
///
public Texture2D CurrentFrameCopy
{
get
{
if (mCurrentClip != null /*&& (int)mSize.X > 1*/)
{
// 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;
}
}
return null;
}
}
///
/// Returns a reference to the sprite sheet of the current frame.
///
public Texture2D CurrentSpriteSheet { get { return mCurrentClip != null ? mCurrentClip.SpriteSheet : 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; } }
#endregion
#region Creation
///
/// 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;
}
///
/// 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))
{ }
#endregion
#region Update
///
/// Cycles through frames as appropriate
///
/// Time that has passed in game
public void Update(GameTime gameTime)
{
if (mCurrentClip == null || mPaused || mCurrentClip.Delay == 0)
{
return;
}
// See if it is time to transition to the next frame
mFrameUpdateCounter -= (float)gameTime.ElapsedGameTime.TotalSeconds;
while (mFrameUpdateCounter <= 0)
{
// Update to the next frame
mCurrentClip.IncrementFrame();
mFrameUpdateCounter += mCurrentClip.Delay;
}
}
#endregion
#region Render
///
/// Draws the current frame to the given sprite batch using alpha blending
///
/// The sprite batch used for rendering textures
/// The position and dimensions of the texture to draw
/// The color to render the texture in
/// The degree of rotation to apply to the texture
/// What effects, if any, to use when rendering the texture
/// Its layer of depth for z-ordering
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 >> 1, slice.Height >> 1),
spriteEffects,
layerDepth
);
}
#endregion
#region AddClip
///
/// 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);
}
#endregion
#region Play
///
/// Changes the selected animation. Does not affect the Paused state of this animation.
///
/// Path name for the clip file
public void Play(string clipName)
{
if (mClips.ContainsKey(clipName))
{
mCurrentClip = mClips[clipName];
}
}
#endregion
#region Getters
///
/// Generates a rectangle with the location and size of the current frame
///
/// 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);
}
#endregion
}
}