using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; /* * XNA Lightweight Sprite Animation * by Brian MacIntosh for ThunderFish Entertainment/BoneFish Studios * * Version: 1.0 * * 12/28/2011: Version 1.0 * 11/21/2012: * - Distributed * * This library is provided free of charge for commercial and noncommercial use. * No warranty of fitness for any purpose, express or implied, is given. */ /* * These classes provide easy methods for displaying animated sprites. * * Simply create an instance of the ThunderFish.SpriteSheet class * using a Texture2D representing an image containing multiple frames. * Then, create instances of the ThunderFish.Sprite class. These are * the actual game objects that can be drawn and can play the animations * from the SpriteSheet. * * See method descriptions for more information. * * GOTCHAS: * - Sprite Update and Draw methods must be called by you */ namespace Thunderfish { /// /// This class represents a visible object on the screen. /// class Sprite { private SpriteSheet spritesheet; /// /// The screen position of this sprite. /// public Vector2 Position { get; set; } /// /// The offset of this sprite's graphic from its actual position. /// public Vector2 Offset { get; set; } /// /// The angle of this sprite (in radians) /// public float Angle { get; set; } /// /// Set this sprite's offset such that its image is centered. /// public void CenterOffset() { Offset = new Vector2(Width, Height) / 2; } /// /// The length of one frame in the sprite's animation. /// public TimeSpan FrameLength { get; set; } private TimeSpan nextFrame; private int currentFrame; /// /// The current frame of the animation. /// public int CurrentFrame { get { return currentFrame; } } private int currentStartFrame; private int currentEndFrame; /// /// Is this sprite looping? /// public bool IsLooping { get; protected set; } /// /// Is this sprite playing (can be true if sprite is paused)? /// public bool IsPlaying { get; protected set; } /// /// Is this sprite paused? /// public bool IsPaused { get; protected set; } /// /// The width of this sprite. /// public int Width { get { return spritesheet.FrameWidth; } } /// /// The height of this sprite. /// public int Height { get { return spritesheet.FrameHeight; } } /// /// Get the rectangle containing this sprite. /// public Rectangle CollisionBox { get { return new Rectangle( (int)(Position.X - Offset.X), (int)(Position.Y - Offset.Y), Width, Height); } } /// /// Get the current frame of this sprite. /// /// public Texture2D GetCurrentFrame() { return spritesheet.GetFrame(currentFrame); } /// /// Create a new sprite /// /// The spritesheet to draw frames from public Sprite(SpriteSheet spritesheet) { this.spritesheet = spritesheet; currentStartFrame = 0; currentEndFrame = spritesheet.FrameCount; } /// /// Update this sprite's animation /// /// Amount of time elapsed since last update public void Update(TimeSpan elapsedTime) { if (!IsPaused && IsPlaying) { nextFrame -= elapsedTime; if (nextFrame <= TimeSpan.Zero) { currentFrame++; if (currentFrame > currentEndFrame) { if (IsLooping) currentFrame = currentStartFrame; else { IsPlaying = false; currentFrame--; } } nextFrame = FrameLength; } } } /// /// Play this sprite's animation /// public void PlayAnimation() { PlayAnimation(0, spritesheet.FrameCount - 1, false); } /// /// Loop this sprite's animation /// public void LoopAnimation() { PlayAnimation(0, spritesheet.FrameCount - 1, true); } /// /// Loop the specified range of frames from the sprite's animation /// /// /// public void LoopAnimation(int startFrame, int endFrame) { PlayAnimation(startFrame, endFrame, true); } /// /// Play the specified range of frames from the sprite's animation /// /// /// /// Should the animation loop? public void PlayAnimation(int startFrame, int endFrame, bool loop) { nextFrame = FrameLength; currentFrame = startFrame; IsPaused = false; IsLooping = loop; currentStartFrame = startFrame; currentEndFrame = endFrame; IsPlaying = true; } /// /// Pause this sprite's animation /// public void Pause() { IsPaused = true; } /// /// Resume this sprite's animation if it is paused /// public void Resume() { IsPaused = false; } /// /// Stop this sprite's animation /// public void StopAnimation() { IsPaused = false; IsPlaying = false; IsLooping = false; } /// /// Set the frame this sprite is showing /// /// public void SetFrame(int frame) { currentFrame = frame; } /// /// Draw this sprite to the specified SpriteBatch using /// its Position, Offset, and Angle fields /// /// public void Draw(SpriteBatch batch) { batch.Draw( spritesheet.GetGraphic(), Position, spritesheet.GetRectangle(currentFrame), Color.White, Angle, Offset, 1.0f, SpriteEffects.None, 1.0f); } public void Draw(SpriteBatch batch, Rectangle destinationRectangle, Color color) { batch.Draw( spritesheet.GetFrame(currentFrame), destinationRectangle, color); } public void Draw(SpriteBatch batch, Vector2 position, Color color) { batch.Draw( spritesheet.GetFrame(currentFrame), position, color); } public void Draw(SpriteBatch batch, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color) { batch.Draw( spritesheet.GetFrame(currentFrame), destinationRectangle, sourceRectangle, color); } public void Draw(SpriteBatch batch, Vector2 position, Rectangle? sourceRectangle, Color color) { batch.Draw( spritesheet.GetFrame(currentFrame), position, sourceRectangle, color); } public void Draw(SpriteBatch batch, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth) { batch.Draw( spritesheet.GetFrame(currentFrame), destinationRectangle, sourceRectangle, color, rotation, origin, effects, layerDepth); } public void Draw(SpriteBatch batch, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth) { batch.Draw( spritesheet.GetFrame(currentFrame), position, sourceRectangle, color, rotation, origin, scale, effects, layerDepth); } public void Draw(SpriteBatch batch, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { batch.Draw( spritesheet.GetFrame(currentFrame), position, sourceRectangle, color, rotation, origin, scale, effects, layerDepth); } /// /// Does this sprite collide with the specified sprite using box collision? /// (Does not support rotation) /// /// /// public bool RectangleHit(Sprite other) { return other.CollisionBox.Intersects(CollisionBox); } /// /// Does this sprite collide with any of the specified sprites using box collision? /// (Does not support rotation) /// /// /// The other sprite collided with public Sprite RectangleHitAny(ICollection others) { foreach (Sprite s in others) if (RectangleHit(s)) return s; return null; } /// /// Does this sprite collide with the specified sprite using circle collision? /// /// /// public bool CircleHit(Sprite other) { return Vector2.Distance(Position, other.Position) <= Math.Min(Width, Height) + Math.Min(other.Width, other.Height); } /// /// Does this sprite collide with any of the specified sprites using circle collision? /// /// /// The other sprite collided with public Sprite CircleHitAny(ICollection others) { foreach (Sprite s in others) if (CircleHit(s)) return s; return null; } /// /// Does this sprite collide with the specified sprite using per-pixel detection? /// /// /// /*public bool PerPixelHit(Sprite other) { if (!RectangleHit(other)) return false; Texture2D thisframe = GetCurrentFrame(); Color[] thisdata = new Color[thisframe.Width * thisframe.Height]; thisframe.GetData(thisdata); Texture2D otherframe = other.GetCurrentFrame(); Color[] otherdata = new Color[otherframe.Width * otherframe.Height]; otherframe.GetData(thisdata); Vector2 otheroffset = (other.Position - other.Offset) - (Position - Offset); Rectangle check = Rectangle.Intersect(other.CollisionBox, CollisionBox); for (int x = check.Left; x < check.Right; x++) { for (int y = check.Top; y < check.Bottom; y++) { int lx = x - CollisionBox.Left; int ly = y - CollisionBox.Top; int o_x = x + (int)otheroffset.X; int o_y = y + (int)otheroffset.Y; int o_lx = o_x - other.CollisionBox.Left; int o_ly = o_y - other.CollisionBox.Top; int d = lx + ly * Width; int o_d = o_lx + o_ly * other.Width; if (thisdata[d].A > 0 && otherdata[o_d].A > 0) //Should be < 1? return true; } } return false; }*/ } /// /// This class contains animation data for Sprites /// class SpriteSheet { private Texture2D graphic; /// /// Get the specified frame from this spritesheet /// /// /// public Texture2D GetFrame(int frame) { Color[] data = new Color[framesize]; graphic.GetData( 0, GetRectangle(frame), data, 0, framesize); Texture2D ret = new Texture2D(graphic.GraphicsDevice, FrameWidth, FrameHeight); ret.SetData(data); return ret; } /// /// Get the full spritesheet image /// /// public Texture2D GetGraphic() { return graphic; } /// /// Get the rectangle on the texture that contains the specified frame /// /// /// public Rectangle GetRectangle(int frame) { int x = frame % framesAcross; int y = (int)Math.Floor(frame / (float)framesAcross); return new Rectangle( x * FrameWidth, y * FrameHeight, FrameWidth, FrameHeight); } private int framesAcross; private int framesDown; private int framesize { get { return FrameWidth * FrameHeight; } } /// /// The width of one frame from this spritesheet /// public int FrameWidth { get { return graphic.Width / framesAcross; } } /// /// The height of one frame from this spritesheet /// public int FrameHeight { get { return graphic.Height / framesDown; } } /// /// The number of frames on this spritesheet /// public int FrameCount { get { if (lastFrame.HasValue) return (int)lastFrame + 1; else return framesAcross * framesDown; } } private int? lastFrame; /// /// Set the last frame number on this sheet (use if there are /// empty frames at the end of the image used) /// /// public void SetLastFrame(int frame) { lastFrame = frame; } /// /// Construct a new spritesheet with the given information /// /// Image containing sprite frames /// /// /// public SpriteSheet(Texture2D graphic, int framesAcross, int framesDown) { this.graphic = graphic; this.framesAcross = framesAcross; this.framesDown = framesDown; } } }