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