Created
May 28, 2018 04:47
-
-
Save BrianMacIntosh/d84361e98d85dfa1c0ec44bc1023c805 to your computer and use it in GitHub Desktop.
These classes provide easy methods for displaying animated sprites in XNA.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
{ | |
/// <summary> | |
/// This class represents a visible object on the screen. | |
/// </summary> | |
class Sprite | |
{ | |
private SpriteSheet spritesheet; | |
/// <summary> | |
/// The screen position of this sprite. | |
/// </summary> | |
public Vector2 Position { get; set; } | |
/// <summary> | |
/// The offset of this sprite's graphic from its actual position. | |
/// </summary> | |
public Vector2 Offset { get; set; } | |
/// <summary> | |
/// The angle of this sprite (in radians) | |
/// </summary> | |
public float Angle { get; set; } | |
/// <summary> | |
/// Set this sprite's offset such that its image is centered. | |
/// </summary> | |
public void CenterOffset() | |
{ | |
Offset = new Vector2(Width, Height) / 2; | |
} | |
/// <summary> | |
/// The length of one frame in the sprite's animation. | |
/// </summary> | |
public TimeSpan FrameLength { get; set; } | |
private TimeSpan nextFrame; | |
private int currentFrame; | |
/// <summary> | |
/// The current frame of the animation. | |
/// </summary> | |
public int CurrentFrame { get { return currentFrame; } } | |
private int currentStartFrame; | |
private int currentEndFrame; | |
/// <summary> | |
/// Is this sprite looping? | |
/// </summary> | |
public bool IsLooping { get; protected set; } | |
/// <summary> | |
/// Is this sprite playing (can be true if sprite is paused)? | |
/// </summary> | |
public bool IsPlaying { get; protected set; } | |
/// <summary> | |
/// Is this sprite paused? | |
/// </summary> | |
public bool IsPaused { get; protected set; } | |
/// <summary> | |
/// The width of this sprite. | |
/// </summary> | |
public int Width { get { return spritesheet.FrameWidth; } } | |
/// <summary> | |
/// The height of this sprite. | |
/// </summary> | |
public int Height { get { return spritesheet.FrameHeight; } } | |
/// <summary> | |
/// Get the rectangle containing this sprite. | |
/// </summary> | |
public Rectangle CollisionBox { get { | |
return new Rectangle( | |
(int)(Position.X - Offset.X), | |
(int)(Position.Y - Offset.Y), | |
Width, | |
Height); } } | |
/// <summary> | |
/// Get the current frame of this sprite. | |
/// </summary> | |
/// <returns></returns> | |
public Texture2D GetCurrentFrame() | |
{ | |
return spritesheet.GetFrame(currentFrame); | |
} | |
/// <summary> | |
/// Create a new sprite | |
/// </summary> | |
/// <param name="spritesheet">The spritesheet to draw frames from</param> | |
public Sprite(SpriteSheet spritesheet) | |
{ | |
this.spritesheet = spritesheet; | |
currentStartFrame = 0; | |
currentEndFrame = spritesheet.FrameCount; | |
} | |
/// <summary> | |
/// Update this sprite's animation | |
/// </summary> | |
/// <param name="elapsedTime">Amount of time elapsed since last update</param> | |
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; | |
} | |
} | |
} | |
/// <summary> | |
/// Play this sprite's animation | |
/// </summary> | |
public void PlayAnimation() | |
{ | |
PlayAnimation(0, spritesheet.FrameCount - 1, false); | |
} | |
/// <summary> | |
/// Loop this sprite's animation | |
/// </summary> | |
public void LoopAnimation() | |
{ | |
PlayAnimation(0, spritesheet.FrameCount - 1, true); | |
} | |
/// <summary> | |
/// Loop the specified range of frames from the sprite's animation | |
/// </summary> | |
/// <param name="startFrame"></param> | |
/// <param name="endFrame"></param> | |
public void LoopAnimation(int startFrame, int endFrame) | |
{ | |
PlayAnimation(startFrame, endFrame, true); | |
} | |
/// <summary> | |
/// Play the specified range of frames from the sprite's animation | |
/// </summary> | |
/// <param name="startFrame"></param> | |
/// <param name="endFrame"></param> | |
/// <param name="loop">Should the animation loop?</param> | |
public void PlayAnimation(int startFrame, int endFrame, bool loop) | |
{ | |
nextFrame = FrameLength; | |
currentFrame = startFrame; | |
IsPaused = false; | |
IsLooping = loop; | |
currentStartFrame = startFrame; | |
currentEndFrame = endFrame; | |
IsPlaying = true; | |
} | |
/// <summary> | |
/// Pause this sprite's animation | |
/// </summary> | |
public void Pause() | |
{ | |
IsPaused = true; | |
} | |
/// <summary> | |
/// Resume this sprite's animation if it is paused | |
/// </summary> | |
public void Resume() | |
{ | |
IsPaused = false; | |
} | |
/// <summary> | |
/// Stop this sprite's animation | |
/// </summary> | |
public void StopAnimation() | |
{ | |
IsPaused = false; | |
IsPlaying = false; | |
IsLooping = false; | |
} | |
/// <summary> | |
/// Set the frame this sprite is showing | |
/// </summary> | |
/// <param name="frame"></param> | |
public void SetFrame(int frame) | |
{ | |
currentFrame = frame; | |
} | |
/// <summary> | |
/// Draw this sprite to the specified SpriteBatch using | |
/// its Position, Offset, and Angle fields | |
/// </summary> | |
/// <param name="batch"></param> | |
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); | |
} | |
/// <summary> | |
/// Does this sprite collide with the specified sprite using box collision? | |
/// (Does not support rotation) | |
/// </summary> | |
/// <param name="other"></param> | |
/// <returns></returns> | |
public bool RectangleHit(Sprite other) | |
{ | |
return other.CollisionBox.Intersects(CollisionBox); | |
} | |
/// <summary> | |
/// Does this sprite collide with any of the specified sprites using box collision? | |
/// (Does not support rotation) | |
/// </summary> | |
/// <param name="others"></param> | |
/// <returns>The other sprite collided with</returns> | |
public Sprite RectangleHitAny(ICollection<Sprite> others) | |
{ | |
foreach (Sprite s in others) | |
if (RectangleHit(s)) return s; | |
return null; | |
} | |
/// <summary> | |
/// Does this sprite collide with the specified sprite using circle collision? | |
/// </summary> | |
/// <param name="other"></param> | |
/// <returns></returns> | |
public bool CircleHit(Sprite other) | |
{ | |
return Vector2.Distance(Position, other.Position) <= | |
Math.Min(Width, Height) + Math.Min(other.Width, other.Height); | |
} | |
/// <summary> | |
/// Does this sprite collide with any of the specified sprites using circle collision? | |
/// </summary> | |
/// <param name="others"></param> | |
/// <returns>The other sprite collided with</returns> | |
public Sprite CircleHitAny(ICollection<Sprite> others) | |
{ | |
foreach (Sprite s in others) | |
if (CircleHit(s)) return s; | |
return null; | |
} | |
/// <summary> | |
/// Does this sprite collide with the specified sprite using per-pixel detection? | |
/// </summary> | |
/// <param name="other"></param> | |
/// <returns></returns> | |
/*public bool PerPixelHit(Sprite other) | |
{ | |
if (!RectangleHit(other)) return false; | |
Texture2D thisframe = GetCurrentFrame(); | |
Color[] thisdata = new Color[thisframe.Width * thisframe.Height]; | |
thisframe.GetData<Color>(thisdata); | |
Texture2D otherframe = other.GetCurrentFrame(); | |
Color[] otherdata = new Color[otherframe.Width * otherframe.Height]; | |
otherframe.GetData<Color>(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; | |
}*/ | |
} | |
/// <summary> | |
/// This class contains animation data for Sprites | |
/// </summary> | |
class SpriteSheet | |
{ | |
private Texture2D graphic; | |
/// <summary> | |
/// Get the specified frame from this spritesheet | |
/// </summary> | |
/// <param name="frame"></param> | |
/// <returns></returns> | |
public Texture2D GetFrame(int frame) | |
{ | |
Color[] data = new Color[framesize]; | |
graphic.GetData<Color>( | |
0, | |
GetRectangle(frame), | |
data, | |
0, | |
framesize); | |
Texture2D ret = new Texture2D(graphic.GraphicsDevice, FrameWidth, FrameHeight); | |
ret.SetData<Color>(data); | |
return ret; | |
} | |
/// <summary> | |
/// Get the full spritesheet image | |
/// </summary> | |
/// <returns></returns> | |
public Texture2D GetGraphic() | |
{ | |
return graphic; | |
} | |
/// <summary> | |
/// Get the rectangle on the texture that contains the specified frame | |
/// </summary> | |
/// <param name="frame"></param> | |
/// <returns></returns> | |
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; } } | |
/// <summary> | |
/// The width of one frame from this spritesheet | |
/// </summary> | |
public int FrameWidth { get { return graphic.Width / framesAcross; } } | |
/// <summary> | |
/// The height of one frame from this spritesheet | |
/// </summary> | |
public int FrameHeight { get { return graphic.Height / framesDown; } } | |
/// <summary> | |
/// The number of frames on this spritesheet | |
/// </summary> | |
public int FrameCount | |
{ | |
get | |
{ | |
if (lastFrame.HasValue) | |
return (int)lastFrame + 1; | |
else | |
return framesAcross * framesDown; | |
} | |
} | |
private int? lastFrame; | |
/// <summary> | |
/// Set the last frame number on this sheet (use if there are | |
/// empty frames at the end of the image used) | |
/// </summary> | |
/// <param name="frame"></param> | |
public void SetLastFrame(int frame) | |
{ | |
lastFrame = frame; | |
} | |
/// <summary> | |
/// Construct a new spritesheet with the given information | |
/// </summary> | |
/// <param name="graphic">Image containing sprite frames</param> | |
/// <param name="device"></param> | |
/// <param name="framesAcross"></param> | |
/// <param name="framesDown"></param> | |
public SpriteSheet(Texture2D graphic, int framesAcross, int framesDown) | |
{ | |
this.graphic = graphic; | |
this.framesAcross = framesAcross; | |
this.framesDown = framesDown; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment