OpenSNES
Modern Open-Source SNES Development SDK
Loading...
Searching...
No Matches
main.c File Reference

World-space sprites — a camera, culling, a depth sort and OAM in one call per batch. More...

#include <snes.h>
#include <snes/tile.h>

Macros

#define CAM_XMAX   (WORLD_W - 256)
 The camera may not show past the world's edge.
#define CAM_YMAX   (WORLD_H - 224)
#define NCOINS   17
 Coins in the world (the 16 x 16 batch).
#define NWALK   6
 Walkers (the 32 x 32 batch, sorted by depth).
#define SPR_VRAM   0x4000
 Sprite tile VRAM word address.
#define WALK_ID   NCOINS
 The walkers' OAM ids follow the coins'.
#define WORLD_H   448
#define WORLD_W   512
 World size in pixels.

Functions

static void load_shape (const u8 *rows, u16 name, u8 flipx, u8 flipy)
 Encode one 8 x 8 shape (8 rows of bits, colour index 1) to VRAM name name, mirrored left-right (flipx) and / or top-bottom (flipy): a big sprite has ONE attribute byte for all its tiles, so each corner and edge is its own tile.
int main (void)
static void sort_walkers (void)
 Sort the walkers by y, the lowest on screen first: an insertion sort of six bytes.

Variables

u16 cam_x
 Probe oracles: the camera, the coins placed this frame, the walker in the front slot.
u16 cam_y
static OamWorldBatch coins
 The two batches: the coins (16 x 16) and the walkers (32 x 32, sorted).
static u8 px [64]
 Scratch: an 8 x 8 tile as pixel indices, and its 4bpp form.
static u8 tilebuf [32]
static u8 walk_order [6]
 The walkers' drawing order: slot i of the batch shows walker order[i].
static OamWorldBatch walkers
static u8 wattr [17+6]
u16 world_front
u16 world_visible
static u8 wseen [17+6]
static u8 wtile [17+6]
static s8 wvx [17+6]
static s8 wvy [17+6]
static s16 wx [17+6]
static s16 wy [17+6]

Detailed Description

World-space sprites — a camera, culling, a depth sort and OAM in one call per batch.

A scrolling game keeps its actors in WORLD coordinates (a 512 x 448 field here) and shows a 256 x 224 window of it. Every frame it has to subtract the camera from each actor, decide whether the result is on screen, write the OAM entry or hide it, and set the ninth x bit for a sprite half out on the left. In compiled C that loop cost a real game about 6,000 master cycles a sprite (issue #165); oamPlaceWorld() does it in assembly for a whole batch, about 1,700 a sprite on the library bench (docs/PERF.md, row world).

The game keeps one array per property (structure of arrays: what the 65816 indexes cheaply) and fills an OamWorldBatch with their addresses once. Two batches, because a batch has one size: 17 coins of 16 x 16 (the SMALL size of OBJ_SIZE16_L32) and six walkers of 32 x 32 (the LARGE size, set once with oamSetSize(): the call never touches the size bit).

The walkers show order. Seen from above, the one lower on the screen is nearer and must be drawn in front — and OAM order is drawing order, the lower slot in front. So the walkers are sorted by y every frame (an insertion sort of six bytes) and the batch's order says which walker each slot shows: the arrays stay indexed by the walker, only the six bytes of order move. This is what a real game does with twenty players and robots on a pitch.

Hold the D-pad to move the camera. visible[] comes back from the call: the example counts the coins in view with it (world_visible); a game reads it to stream the animation frames of the sprites that are shown and skip the others — a sprite off screen should cost no VRAM DMA.

ROM mode: LoROM (project default).

SNES Concepts
  • World coordinates minus a camera = screen coordinates; a sprite partly off the left edge needs the ninth x bit (x in 256..511), which the PPU reads as negative
  • OAM culling: a sprite off screen is hidden (x = 257, y = 240), not left wherever it was
  • OAM order is drawing order: a depth sort permutes slots, not data
  • Structure of arrays for per-sprite data, and the size bit set once
What to Observe
Six square walkers pace up and down near the centre and pass in front of and behind one another as their y changes; gold coins drift over the field; the D-pad scrolls the camera and stops at the world's edge. world_visible (probe oracle) follows how many coins are in view, world_front which walker is in the front slot, cam_x / cam_y the camera.
Modules Used
console, dma, background, sprite, input, tile
See also
lib/include/snes/sprite.h — OamWorldBatch, oamPlaceWorld, oamSetSize
examples/sprites/sprite_swarm — the same sprites moved by direct OAM writes, no camera

Macro Definition Documentation

◆ CAM_XMAX

#define CAM_XMAX   (WORLD_W - 256)

The camera may not show past the world's edge.

◆ CAM_YMAX

#define CAM_YMAX   (WORLD_H - 224)

◆ NCOINS

#define NCOINS   17

Coins in the world (the 16 x 16 batch).

◆ NWALK

#define NWALK   6

Walkers (the 32 x 32 batch, sorted by depth).

◆ SPR_VRAM

#define SPR_VRAM   0x4000

Sprite tile VRAM word address.

◆ WALK_ID

#define WALK_ID   NCOINS

The walkers' OAM ids follow the coins'.

◆ WORLD_H

#define WORLD_H   448

◆ WORLD_W

#define WORLD_W   512

World size in pixels.

Function Documentation

◆ load_shape()

void load_shape ( const u8 * rows,
u16 name,
u8 flipx,
u8 flipy )
static

Encode one 8 x 8 shape (8 rows of bits, colour index 1) to VRAM name name, mirrored left-right (flipx) and / or top-bottom (flipy): a big sprite has ONE attribute byte for all its tiles, so each corner and edge is its own tile.

◆ main()

int main ( void )

◆ sort_walkers()

void sort_walkers ( void )
static

Sort the walkers by y, the lowest on screen first: an insertion sort of six bytes.

Variable Documentation

◆ cam_x

u16 cam_x

Probe oracles: the camera, the coins placed this frame, the walker in the front slot.

◆ cam_y

u16 cam_y

◆ coins

OamWorldBatch coins
static

The two batches: the coins (16 x 16) and the walkers (32 x 32, sorted).

◆ px

u8 px[64]
static

Scratch: an 8 x 8 tile as pixel indices, and its 4bpp form.

◆ tilebuf

u8 tilebuf[32]
static

◆ walk_order

u8 walk_order[6]
static

The walkers' drawing order: slot i of the batch shows walker order[i].

◆ walkers

OamWorldBatch walkers
static

◆ wattr

u8 wattr[17+6]
static

vhoopppc, as OAM_ATTR() builds it

◆ world_front

u16 world_front

◆ world_visible

u16 world_visible

◆ wseen

u8 wseen[17+6]
static

out: 1 placed, 0 hidden

◆ wtile

u8 wtile[17+6]
static

tile name, low byte

◆ wvx

s8 wvx[17+6]
static

drift

◆ wvy

s8 wvy[17+6]
static

◆ wx

s16 wx[17+6]
static

world x of the top-left corner

◆ wy

s16 wy[17+6]
static

world y