SpriteData.cs
namespace Sandbox;
/// <summary>
/// A single coloured pixel within a sprite frame.
/// Stores its position relative to the sprite origin (bottom-left) and its colour.
/// </summary>
public struct PixelData
{
public PixelPoint Position;
public Color32 Color;
public PixelData( PixelPoint position, Color32 color )
{
Position = position;
Color = color;
}
}
/// <summary>
/// One frame of a sprite animation.
/// Contains the pixel list and the duration (in seconds) before advancing to the next frame.
/// </summary>
public class FrameData
{
// Sparse pixel list — only non-transparent pixels are stored.
// Fully transparent pixels are stripped at load time (see SpriteManager.ParseFramePixels)
// so Draw loops only touch pixels that will actually write something.
public List<PixelData> Pixels { get; set; }
public float AnimTime { get; set; }
/// <summary>
/// Optional per-frame hitbox override. When null, the animation-level
/// <see cref="AnimationData.Hitbox"/> is used instead.
/// </summary>
public PixelRect? Hitbox { get; set; }
// Lazily-computed flipped variants. Pixel data is immutable after load,
// so these are computed once and reused on every subsequent Draw call.
private List<PixelData> _cachedFlipX;
private List<PixelData> _cachedFlipY;
private List<PixelData> _cachedFlipXY;
public FrameData() { }
public FrameData( List<PixelData> pixels, float animTime = 0f, PixelRect? hitbox = null )
{
Pixels = pixels;
AnimTime = animTime;
Hitbox = hitbox;
}
/// <summary>
/// Return a copy of the pixel list with X coordinates mirrored within the given width.
/// Result is cached after the first call.
/// </summary>
public List<PixelData> GetPixelsFlippedX( int width )
{
if ( _cachedFlipX is not null )
return _cachedFlipX;
_cachedFlipX = new List<PixelData>( Pixels.Count );
foreach ( PixelData p in Pixels )
_cachedFlipX.Add( new PixelData( new PixelPoint( width - 1 - p.Position.X, p.Position.Y ), p.Color ) );
return _cachedFlipX;
}
/// <summary>
/// Return a copy of the pixel list with Y coordinates mirrored within the given height.
/// Result is cached after the first call.
/// </summary>
public List<PixelData> GetPixelsFlippedY( int height )
{
if ( _cachedFlipY is not null )
return _cachedFlipY;
_cachedFlipY = new List<PixelData>( Pixels.Count );
foreach ( PixelData p in Pixels )
_cachedFlipY.Add( new PixelData( new PixelPoint( p.Position.X, height - 1 - p.Position.Y ), p.Color ) );
return _cachedFlipY;
}
/// <summary>
/// Return a copy of the pixel list mirrored on both axes.
/// Result is cached after the first call.
/// </summary>
public List<PixelData> GetPixelsFlippedXY( int width, int height )
{
if ( _cachedFlipXY is not null )
return _cachedFlipXY;
_cachedFlipXY = new List<PixelData>( Pixels.Count );
foreach ( PixelData p in Pixels )
_cachedFlipXY.Add( new PixelData( new PixelPoint( width - 1 - p.Position.X, height - 1 - p.Position.Y ), p.Color ) );
return _cachedFlipXY;
}
}
/// <summary>
/// Defines a single named animation for a sprite, including all frames,
/// the bounding size, optional hitbox, and loop mode.
/// </summary>
public class AnimationData
{
public string Name { get; set; }
public List<FrameData> Frames { get; set; }
public PixelPoint AnimSize { get; set; }
public PixelRect Hitbox { get; set; }
public LoopMode LoopMode { get; set; }
public AnimationData() { }
public AnimationData( string name, List<FrameData> frames, PixelPoint animSize, PixelRect hitbox, LoopMode loopMode )
{
Name = name;
Frames = frames;
AnimSize = animSize;
Hitbox = hitbox;
LoopMode = loopMode;
}
}
/// <summary>
/// How an animation plays back after reaching the last frame.
/// </summary>
public enum LoopMode
{
/// <summary>Restart from frame 0 after the last frame.</summary>
Loops,
/// <summary>Play once then stop on the last frame.</summary>
PlayOnce,
/// <summary>Alternate forward and backward through the frames.</summary>
PingPong,
/// <summary>Jump to a random frame each cycle.</summary>
RandomFrame
}