OpenSNES
Modern Open-Source SNES Development SDK
Loading...
Searching...
No Matches
anim.h File Reference

Declarative animation player — data-driven frame sequencing. More...

#include <snes/types.h>

Go to the source code of this file.

Classes

struct  AnimClip
 An animation clip — ROM-resident, 12 bytes. More...
struct  AnimPlayer
 A playback head — RAM, 8 bytes, array-friendly. More...

Macros

#define ANIM_F_FINISHED   0x01
 AnimPlayer.flags bit: an ANIM_ONCE clip reached its last frame.
#define ANIM_LOOP   0
 Loop mode: wrap to frame 0 after the last frame.
#define ANIM_NONE   0xFFFF
 animTick()/animFrame() result when the player has no clip.
#define ANIM_ONCE   1
 Loop mode: hold the last frame and raise ANIM_F_FINISHED.
#define ANIM_PLAYER_INIT   {0, 0, 0, 0, 0}
 Static initializer: AnimPlayer p = ANIM_PLAYER_INIT;.
#define animDone(_p)
 Nonzero once an ANIM_ONCE clip holds its last frame.
#define animFrame(_p)
 Current frame value WITHOUT advancing (ANIM_NONE if stopped).
#define animStop(_p)
 Stop: detach the clip, clear flags. Next animPlay starts fresh.
#define animTickMeta(_p, _tbl)
 Tick + resolve against a gfx4snes metasprite pointer table.
#define animTickOam(_p, _id)
 Tick + apply to the dynamic sprite engine (the 90% one-liner).
#define DECLARE_ANIM_CLIP(name, mode_, speed_, ...)
 Declare a static const AnimClip with uniform frame duration.

Functions

void animPlay (AnimPlayer *p, const AnimClip *clip)
 Start (or keep) a clip — continue-if-same semantics.
void animRestart (AnimPlayer *p)
 Force-restart the current clip from frame 0, even mid-flight.
u16 animTick (AnimPlayer *p)
 Advance one tick; return the current frame value.

Detailed Description

Declarative animation player — data-driven frame sequencing.

Replaces the hand-rolled tempo counters every game rewrites (a tick variable, a modulo gate, manual oamframeid/oamrefresh pokes) with two small structs and one explicit per-frame call.

A frame value is an opaque u16 — the player sequences it without interpreting it. Choose what it means per clip:

Zero global state, zero init call, zero link dependencies: all state lives in caller-owned AnimPlayer structs, and nothing runs unless you tick. Pausing an animation = not ticking it (costs nothing).

Layout contract (tooling-facing)
AnimClip (12 bytes: two 4-byte far pointers + 4 u8 fields) and AnimPlayer (8 bytes: one far pointer + 4 u8 fields) layouts below are a public contract: editors (Cooper) emit AnimClip as generated C data. Field order, sizes and semantics are stable; reserved fields must be 0.
Cost (per PHILOSOPHY.md P5)
animTick() on the non-advancing path is a NULL check, a finished check, a decrement/compare and one indexed u16 load — on the order of 60–90 cycles per player per frame including call overhead. RAM cost is 8 bytes per player; ROM cost 12 bytes per clip + 2 bytes per frame (+1/frame if per-frame durations are used). Nothing runs in NMI; VRAM upload only happens when a frame actually changes (the dynamic engine's existing budget).
Warning
Clips declared with DECLARE_ANIM_CLIP are ROM const data and count against the bank $00 budget (see KNOWN_LIMITATIONS.md): if bank $00 is full they spill to bank $01+ and are read as garbage. The build hard-fails when that happens (symmap ratchet). Mitigation for a nearly-full ROM: declare the clip and its frames array as initialized non-const statics — RAM-backed clips have the identical layout and API (see examples/games/likemario).

Requires 'anim' in LIB_MODULES.

License: CC0 (Public Domain)

Macro Definition Documentation

◆ ANIM_F_FINISHED

#define ANIM_F_FINISHED   0x01

AnimPlayer.flags bit: an ANIM_ONCE clip reached its last frame.

◆ ANIM_LOOP

#define ANIM_LOOP   0

Loop mode: wrap to frame 0 after the last frame.

◆ ANIM_NONE

#define ANIM_NONE   0xFFFF

animTick()/animFrame() result when the player has no clip.

◆ ANIM_ONCE

#define ANIM_ONCE   1

Loop mode: hold the last frame and raise ANIM_F_FINISHED.

◆ ANIM_PLAYER_INIT

#define ANIM_PLAYER_INIT   {0, 0, 0, 0, 0}

Static initializer: AnimPlayer p = ANIM_PLAYER_INIT;.

◆ animDone

#define animDone ( _p)
Value:
((_p)->flags & ANIM_F_FINISHED)
#define ANIM_F_FINISHED
AnimPlayer.flags bit: an ANIM_ONCE clip reached its last frame.
Definition anim.h:60

Nonzero once an ANIM_ONCE clip holds its last frame.

◆ animFrame

#define animFrame ( _p)
Value:
((_p)->clip ? (_p)->clip->frames[(_p)->frame] : ANIM_NONE)
#define ANIM_NONE
animTick()/animFrame() result when the player has no clip.
Definition anim.h:63

Current frame value WITHOUT advancing (ANIM_NONE if stopped).

◆ animStop

#define animStop ( _p)
Value:
do { (_p)->clip = 0; (_p)->flags = 0; } while (0)

Stop: detach the clip, clear flags. Next animPlay starts fresh.

◆ animTickMeta

#define animTickMeta ( _p,
_tbl )
Value:
((_tbl)[animTick(_p)])
u16 animTick(AnimPlayer *p)
Advance one tick; return the current frame value.

Tick + resolve against a gfx4snes metasprite pointer table.

Feeds oamDrawMeta()/oamMetaDrawDyn() directly:

oamDrawMeta(0, x, y, animTickMeta(&hero, hero_metasprites), 0, 0, sz);
#define animTickMeta(_p, _tbl)
Tick + resolve against a gfx4snes metasprite pointer table.
Definition anim.h:176
u8 oamDrawMeta(u8 startId, s16 x, s16 y, const MetaspriteItem *meta, u16 baseTile, u8 basePalette, u8 size)
Draw a metasprite (PVSnesLib compatible).
Warning
The player must be running (frame values index _tbl); a stopped player would index with ANIM_NONE. Guard with animFrame(p) != ANIM_NONE if the clip can be absent — the hot path stays branch-free by contract.

◆ animTickOam

#define animTickOam ( _p,
_id )
Value:
do { \
u16 _af = animTick(_p); \
if (_af != ANIM_NONE && oambuffer[_id].oamframeid != _af) { \
oambuffer[_id].oamframeid = _af; \
oambuffer[_id].oamrefresh = 1; \
} \
} while (0)
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:53
t_sprites oambuffer[128]
Dynamic sprite buffer (128 entries, 2048 bytes).

Tick + apply to the dynamic sprite engine (the 90% one-liner).

Writes oambuffer[_id].oamframeid and sets oamrefresh = 1 ONLY when the frame actually changed — the VRAM re-upload is the expensive part, so a 1-frame clip (stand pose) costs nothing after the first apply.

Macro (not a function) so the anim module carries no link dependency on the dynamic engine: the oambuffer reference lands in YOUR translation unit, which already links sprite/sprite_dynamic.

◆ DECLARE_ANIM_CLIP

#define DECLARE_ANIM_CLIP ( name,
mode_,
speed_,
... )
Value:
static const u16 name##_frames[] = { __VA_ARGS__ }; \
static const AnimClip name = { \
name##_frames, \
0, \
(u8)(sizeof(name##_frames) / sizeof(u16)), \
(speed_), \
(mode_), \
0, \
}
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:47
An animation clip — ROM-resident, 12 bytes.
Definition anim.h:77

Declare a static const AnimClip with uniform frame duration.

animPlay(&mario_anim, &mario_walk);
#define DECLARE_ANIM_CLIP(name, mode_, speed_,...)
Declare a static const AnimClip with uniform frame duration.
Definition anim.h:189
void animPlay(AnimPlayer *p, const AnimClip *clip)
Start (or keep) a clip — continue-if-same semantics.
#define ANIM_LOOP
Loop mode: wrap to frame 0 after the last frame.
Definition anim.h:55
static AnimPlayer mario_anim
Definition main.c:178
#define FRAME_WALK0
Dynamic sprite frame index: walk animation frame 0.
Definition main.c:127
#define FRAME_WALK1
Dynamic sprite frame index: walk animation frame 1.
Definition main.c:129

Per-frame durations can't be expressed variadically — declare those clips as raw structs (see the AnimClip example above).

Function Documentation

◆ animPlay()

void animPlay ( AnimPlayer * p,
const AnimClip * clip )

Start (or keep) a clip — continue-if-same semantics.

Safe to call unconditionally every frame from a state machine:

  • same clip, still playing: no-op (the animation continues);
  • same clip, finished (ANIM_ONCE): restarts from frame 0 (re-trigger);
  • different clip: switches immediately — frame 0, ticks reloaded, flags cleared;
  • NULL clip (or len == 0): equivalent to animStop().
Parameters
pPlayer (caller-owned)
clipROM clip to play

◆ animRestart()

void animRestart ( AnimPlayer * p)

Force-restart the current clip from frame 0, even mid-flight.

◆ animTick()

u16 animTick ( AnimPlayer * p)

Advance one tick; return the current frame value.

Call once per game-loop iteration per active player (after WaitForVBlank(), like the rest of the frame logic).

Returns
The current frame value; ANIM_NONE if the player is stopped. A finished ANIM_ONCE player keeps returning its last frame.