Clips & Frames¶
Namespace: Brine2D.Animation
An AnimationClip is an ordered sequence of SpriteFrames that a SpriteAnimator steps through over time. Both types are plain C# objects — no ECS required to construct them.
SpriteFrame¶
A SpriteFrame describes one displayable moment of an animation.
var frame = new SpriteFrame(new Rectangle(0, 0, 32, 32)); // default duration 0.1 s
var frame = new SpriteFrame(new Rectangle(0, 0, 32, 32), 0.08f); // explicit duration
Properties¶
| Property | Type | Default | Purpose |
|---|---|---|---|
SourceRect |
Rectangle |
— | Pixel region in the sprite sheet |
Duration |
float |
0.1 |
Seconds to display this frame (min 0.001) |
Origin |
Vector2 |
(0.5, 0.5) |
Normalised pivot point written to SpriteComponent.Origin |
DrawOffset |
Vector2 |
(0, 0) |
Canvas-space pixel offset; used by Aseprite trim to correct trimmed sprite positions |
Tint |
Color? |
null |
Per-frame tint override (requires AnimationLayerMask.Tint) |
FlipX |
bool? |
null |
Per-frame horizontal flip (requires AnimationLayerMask.FlipX) |
FlipY |
bool? |
null |
Per-frame vertical flip (requires AnimationLayerMask.FlipY) |
Texture |
ITexture? |
null |
Per-frame texture override (takes priority over clip-level Texture) |
TexturePath |
string? |
null |
Per-frame texture path override |
HitBox |
Rectangle? |
null |
Primary hitbox shorthand (maps to the "hitbox" named box) |
UserData |
object? |
null |
Arbitrary payload; not consumed by the animation system |
Named Hit Boxes¶
Frames support multiple named hit boxes for attack, hurt, detection zones, etc.
frame.SetHitBox("hurtbox", new Rectangle(4, 2, 24, 28));
frame.SetHitBox("head", new Rectangle(8, 0, 16, 10));
Rectangle? hurtbox = frame.TryGetHitBox("hurtbox"); // null if absent
Rectangle head = frame.GetHitBox("head"); // throws if absent
frame.RemoveHitBox("head");
SpriteFrame.HitBox is a shorthand for the box stored under AsepriteClipLoader.HitBoxSliceName ("hitbox" by default). All named boxes from Aseprite slice data are loaded automatically — see Aseprite Integration.
Frame Lifecycle Events¶
frame.OnEnter += () => { /* frame became active */ };
frame.OnExit += () => { /* frame was replaced */ };
AnimationClip¶
An AnimationClip holds the frames plus metadata controlling how they play.
var clip = new AnimationClip("walk");
Core Properties¶
| Property | Type | Default | Purpose |
|---|---|---|---|
Name |
string |
— | Identifies the clip; used as state name in the state machine |
PlaybackMode |
PlaybackMode |
Loop |
How the clip loops — see below |
Loop |
bool |
true |
Shorthand: false maps current mode to its non-looping equivalent |
RepeatCount |
int |
0 |
Loop/PingPong only: number of passes before OnAnimationComplete. 0 = infinite |
TotalDuration |
float |
computed | Sum of all frame durations; cached and auto-invalidated |
Frames |
IReadOnlyList<SpriteFrame> |
— | Read-only view; mutate via the methods below |
Events |
IReadOnlyList<ClipEvent> |
— | Read-only view of time-offset callbacks |
Optional Clip-Level Overrides¶
| Property | Type | Purpose |
|---|---|---|
Texture |
ITexture? |
Applied to SpriteComponent.Texture every frame (per-frame overrides win) |
TexturePath |
string? |
Applied to SpriteComponent.TexturePath every frame |
ClipTint |
Color? |
Applied to SpriteComponent.Tint every frame (per-frame overrides win) |
UserData |
object? |
Arbitrary payload; not consumed by the animation system |
Clip Lifecycle Events¶
clip.OnEnter += () => { /* clip became active */ };
clip.OnExit += () => { /* clip was replaced */ };
clip.OnUpdate += (elapsed) => { /* fires every tick while active */ };
Adding and Removing Frames¶
All mutation methods return this for fluent chaining:
var clip = new AnimationClip("run")
.AddFrame(new SpriteFrame(new Rectangle(0, 0, 32, 32), 0.08f))
.AddFrame(new SpriteFrame(new Rectangle(32, 0, 32, 32), 0.08f))
.AddFrame(new SpriteFrame(new Rectangle(64, 0, 32, 32), 0.08f))
.AddFrame(new SpriteFrame(new Rectangle(96, 0, 32, 32), 0.08f));
| Method | Purpose |
|---|---|
AddFrame(frame) |
Appends a frame |
InsertFrame(index, frame) |
Inserts a frame at the given index |
RemoveFrame(frame) |
Removes a specific frame instance |
ClearFrames() |
Removes all frames |
Shared Frames
The same SpriteFrame instance can belong to multiple clips. The frame's Duration setter
automatically invalidates the TotalDuration cache of every owning clip via weak references.
PlaybackMode¶
PlaybackMode controls what happens when a clip reaches the end of its frames.
| Value | Behaviour |
|---|---|
Loop |
Loops indefinitely; jumps back to frame 0 after the last frame |
PingPong |
Loops forward then backward indefinitely |
OnceHoldLast |
Plays once, freezes on the last frame (or first when Reversed) |
OnceHoldFirst |
Plays once, freezes on the first frame (or last when Reversed) |
OnceStop |
Plays once, then clears CurrentFrame (null); useful for VFX that should disappear |
PingPongOnce |
One forward + one backward pass, then stops on the first frame of the final pass |
var jumpClip = new AnimationClip("jump")
{
PlaybackMode = PlaybackMode.OnceHoldLast
};
var deathClip = new AnimationClip("death")
{
PlaybackMode = PlaybackMode.OnceStop
};
var breatheClip = new AnimationClip("breathe")
{
PlaybackMode = PlaybackMode.PingPong
};
RepeatCount on Loop and PingPong clips makes them fire OnAnimationComplete after N full passes instead of looping forever.
Building Clips Manually¶
Uniform grid (same-size frames in a row)¶
const int fw = 32, fh = 32;
var walk = new AnimationClip("walk") { PlaybackMode = PlaybackMode.Loop };
for (int i = 0; i < 8; i++)
walk.AddFrame(new SpriteFrame(new Rectangle(i * fw, 0, fw, fh), 0.1f));
Variable frame durations¶
var attack = new AnimationClip("attack") { PlaybackMode = PlaybackMode.OnceHoldLast };
attack.AddFrame(new SpriteFrame(new Rectangle(0, 0, 48, 48), 0.05f)); // wind-up
attack.AddFrame(new SpriteFrame(new Rectangle(48, 0, 48, 48), 0.03f)); // strike
attack.AddFrame(new SpriteFrame(new Rectangle(96, 0, 48, 48), 0.12f)); // recovery
Multi-row sprite sheet¶
const int fw = 32, fh = 32, cols = 8;
var run = new AnimationClip("run") { PlaybackMode = PlaybackMode.Loop };
int startFrame = 16; // frame 16 in the sheet
for (int i = startFrame; i < startFrame + 8; i++)
{
int col = i % cols;
int row = i / cols;
run.AddFrame(new SpriteFrame(new Rectangle(col * fw, row * fh, fw, fh), 0.07f));
}
Registering and Playing Clips¶
Once built, register clips on a SpriteAnimator (directly or via AnimatorComponent):
// Direct SpriteAnimator usage
var animator = new SpriteAnimator();
animator.AddAnimation(walk);
animator.AddAnimation(run);
animator.Play("walk");
// Via AnimatorComponent (preferred in ECS)
var comp = entity.GetComponent<AnimatorComponent>()!;
comp.Animator.AddAnimation(walk);
comp.Animator.AddAnimation(run);
comp.Animator.Play("walk");
See Animator Component for the full playback API and ECS setup.