Skip to content

Instantly share code, notes, and snippets.

@BrianMacIntosh
Created May 28, 2018 04:47
Show Gist options
  • Save BrianMacIntosh/d84361e98d85dfa1c0ec44bc1023c805 to your computer and use it in GitHub Desktop.
Save BrianMacIntosh/d84361e98d85dfa1c0ec44bc1023c805 to your computer and use it in GitHub Desktop.
These classes provide easy methods for displaying animated sprites in XNA.
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