Loading...
Searching...
No Matches
Sprites & Animation Tutorial

This tutorial covers SNES sprites (OBJ layer) including OAM management and animation.

SNES Sprite Basics

  • Up to 128 sprites on screen
  • Sizes: 8x8, 16x16, 32x32, 64x64 (two sizes per mode)
  • 4bpp (16 colors per palette)
  • 8 palettes available (palettes 8-15 in CGRAM)
  • Stored in OAM (Object Attribute Memory) - 544 bytes

OAM Structure

Each sprite uses 4 bytes in main OAM + 2 bits in high table:

Main OAM (4 bytes per sprite):

Byte Content
0 X position (low 8 bits)
1 Y position
2 Tile number (low 8 bits)
3 Attributes: vhoopppN

Attributes:

  • v = Vertical flip
  • h = Horizontal flip
  • oo = Priority (0-3)
  • ppp = Palette (0-7, maps to CGRAM 128-255)
  • N = Tile number bit 8

Using OpenSNES Sprite Functions

Initialize OAM

#include <snes.h>
int main(void) {
// Initialize OAM (hides all sprites)
// Enable sprites on main screen
// ... game loop
}
int main(void)
Entry point — initialize audio, display controls, run transport loop.
Definition main.c:37
void consoleInit(void)
Initialize SNES hardware.
void setScreenOn(void)
Enable screen display.
#define REG_TM
Main screen designation (W)
Definition registers.h:181
#define TM_OBJ
Definition registers.h:443
OpenSNES Master Header.
void oamInit(u16 size, u16 tile_base)
Initialize the sprite (OAM) system.

Setting a Sprite

// oamSet(id, x, y, priority, hflip, vflip, palette)
oamSet(0, 100, 80, 0, 0, 0, 0); // Sprite 0 at (100, 80)
oamSet(1, 120, 80, 0, 0, 0, 1); // Sprite 1 with palette 1
void oamSet(u16 id, u16 x, u16 y, u16 tile, u16 palette, u16 priority, u16 flags)
Set sprite properties.

Updating OAM

while (1) {
// Update sprite positions
oamSet(0, player_x, player_y, 0, 0, 0, 0);
// Transfer OAM buffer to hardware
}
static s16 player_y
Player Y position in screen coordinates.
Definition main.c:57
static s16 player_x
Player X position in screen coordinates.
Definition main.c:55
void WaitForVBlank(void)
Wait for next VBlank period.
void oamUpdate(void)
Copy OAM buffer to hardware.

Hiding Sprites

// Hide a specific sprite (moves Y off-screen)
// Hide all sprites
void oamHide(u8 id)
Hide sprite.

Loading Sprite Tiles

Sprite tiles go in VRAM (location set by REG_OBJSEL):

const u8 sprite_tile[32] = {
// 8x8 tile, 4bpp (32 bytes)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
void load_sprite_tiles(void) {
u16 i;
// Configure sprite settings
REG_OBJSEL = 0x00; // 8x8/16x16 sprites, tiles at $0000
// Set VRAM address
REG_VMAIN = 0x80;
REG_VMADDL = 0x00;
REG_VMADDH = 0x00;
// Upload tiles
for (i = 0; i < 32; i += 2) {
}
}
static u8 i
Definition main.c:156
static u16 bx
Definition main.c:159
#define REG_VMADDH
VRAM address high (W)
Definition registers.h:118
#define REG_VMAIN
VRAM address increment mode (W)
Definition registers.h:112
#define REG_VMADDL
VRAM address low (W)
Definition registers.h:115
#define REG_VMDATAL
VRAM data write low (W)
Definition registers.h:121
#define REG_OBJSEL
Object (sprite) size and base (W)
Definition registers.h:52
#define REG_VMDATAH
VRAM data write high (W)
Definition registers.h:124
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:52
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:46
static const u8 sprite_tile[32]
Inline 8x8 solid square sprite tile in SNES 4bpp format (32 bytes).
Definition main.c:43

Sprite Palettes

Sprite palettes use CGRAM addresses 128-255:

void load_sprite_palette(void) {
// Palette 0 for sprites (CGRAM 128)
REG_CGADD = 128;
// Color 0: Transparent
REG_CGDATA = 0x00; REG_CGDATA = 0x00;
// Color 1: Black
REG_CGDATA = 0x00; REG_CGDATA = 0x00;
// Color 2: White
REG_CGDATA = 0xFF; REG_CGDATA = 0x7F;
// Color 3: Red
REG_CGDATA = 0x1F; REG_CGDATA = 0x00;
// ... colors 4-15
}
#define REG_CGADD
CGRAM address (W)
Definition registers.h:148
#define REG_CGDATA
CGRAM data write (W)
Definition registers.h:151

Animation

Frame-based Animation

u8 anim_frame = 0;
const u8 ANIM_SPEED = 8; // Frames per animation step
void update_animation(void) {
anim_frame++;
if (anim_frame >= 4) { // 4 frames of animation
anim_frame = 0;
}
}
}
// In main loop:
// Set tile based on frame (assuming tiles 0-3 are animation frames)
oamSetTile(0, anim_frame);
void oamSetTile(u8 id, u16 tile)
Set sprite tile.

Movement with Animation

u16 player_x = 128;
u16 player_y = 112;
if (pad & KEY_LEFT) {
if (player_x > 0) player_x--;
}
if (pad & KEY_RIGHT) {
if (player_x < 248) player_x++;
}
// Update sprite with horizontal flip based on direction
}
#define KEY_RIGHT
Definition input.h:78
#define KEY_LEFT
Definition input.h:77

Sprite Sizes

Configure sprite sizes with REG_OBJSEL:

// REG_OBJSEL: sssnnbbb
// sss = size mode (see table)
// nn = name select (gap between tables)
// bbb = base address (/8192)
// Size modes:
// 0: 8x8 and 16x16
// 1: 8x8 and 32x32
// 2: 8x8 and 64x64
// 3: 16x16 and 32x32
// 4: 16x16 and 64x64
// 5: 32x32 and 64x64
REG_OBJSEL = 0x00; // 8x8/16x16, tiles at $0000
REG_OBJSEL = 0x20; // 8x8/32x32, tiles at $0000
REG_OBJSEL = 0x60; // 16x16/32x32, tiles at $0000

Example: Two Players

See examples/input/two_players/ for a complete example with two independently controlled sprites.

Performance Tips

  1. Minimize oamUpdate() calls - Only call once per frame
  2. Use sprite pooling - Reuse sprite slots instead of creating/destroying
  3. Check sprite limits - Max 32 sprites per scanline, 128 total
  4. Batch similar sprites - Group sprites using same tiles/palettes

Next Steps