How to animate a character sprite that walks in 4 directions with 3 animation frames each. Demonstrates sprite sheet layout, direction state machines, and horizontal flip to reuse art.
Read sprites/simple_sprite first (OAM basics, tile loading).
| Button | Action |
|---|---|
| D-PAD Up | Walk up |
| D-PAD Down | Walk down |
| D-PAD Left | Walk left (flipped) |
| D-PAD Right | Walk right |
Then open animated_sprite.sfc in your emulator (Mesen2 recommended).
The sprite sheet is a PNG strip containing 9 frames of 16x16 sprites, loaded to VRAM at startup via oamInitGfxSet(). Each 16x16 sprite occupies 2 tile columns in VRAM. The frame index maps to tile numbers:
OBJ_FLIPXLeft and right share the same animation row – the PPU's horizontal flip flag (OBJ_FLIPX) mirrors the sprite at zero CPU cost. This is a standard SNES technique that halves the sprite art needed for left/right movement.
Animation only advances when a direction button is held. ANIM_DELAY (6 frames) controls walking speed – at 60 fps, this means about 10 animation steps per second.
The tile number is computed from the state and frame:
Frame 2 of the right-walk is at tile 32 (next row in VRAM) because the sheet wraps at 16 tiles per row.
The PPU can flip any sprite horizontally by setting a single bit in the OAM attribute byte. This costs nothing – no extra tiles in VRAM, no CPU work. Many SNES games use this to halve the sprite art needed for left/right movement.
All animation frames are loaded into VRAM at once via oamInitGfxSet(). The active frame is selected by changing the tile number in oamSet(). This means switching frames is instant – just change a number, no DMA needed.
In OAM, a 16x16 sprite uses 4 hardware 8x8 tiles arranged in a 2x2 grid. The tile number refers to the top-left tile; the PPU fills in the rest automatically using the SNES tile numbering convention (right tile = tile+1, bottom tiles follow the 16-tile-per-row VRAM layout).
| File | Purpose |
|---|---|
main.c | Input handling, state machine, animation logic |
data.asm | Sprite tile data and palette via .INCBIN |
res/sprites.png | Source sprite sheet (9 frames, 16x16 each) |
Makefile | LIB_MODULES := console sprite dma input |
sprites/dynamic_sprite – Stream frames instead of pre-loading all of themgames/breakout – Multiple sprites interacting with game logic