Loading...
Searching...
No Matches
Map and Objects

A scrolling platformer that combines the map engine (tile-based world scrolling) with the object engine (entity management with physics and collision). A player character (Mario) walks and jumps through a side-scrolling level populated with enemies (Goombas and Koopa Troopas) that patrol back and forth. This example demonstrates how real SNES games organize their world and entity logic.

Screenshot

What You'll Learn

  • How to use the map engine for tile-based level scrolling with collision data
  • How to use the object engine to manage multiple entities with init/update callbacks
  • How to implement enemy AI with patrol boundaries and animation
  • How to build a multi-file C project on the SNES (separate .c files per entity)
  • How the dynamic sprite engine streams sprite tiles into VRAM on demand

SNES Concepts

Map Engine and Tile Collision

The SNES PPU renders backgrounds from tilemaps in VRAM, but it has no built-in concept of "solid ground." The map engine bridges this gap by maintaining a collision attribute table (tilesetatt / .b16 file) that marks each tile as solid, passable, or hazardous. When objCollidMap() is called, it checks the object's position against this table and adjusts velocity accordingly – stopping downward movement when standing on a solid tile, for example. The tilemap sits at VRAM $6800 in a 64x32 layout (SC_64x32), giving a world 512 pixels wide – wider than the 256-pixel screen, which is what enables scrolling.

Object Engine

The object engine manages a pool of game entities. Each object type registers two callbacks: an init function and an update function. These callbacks are registered in assembly (data.asm) because the 65816 requires 24-bit addresses (16-bit pointer + 8-bit bank byte) and the C compiler only emits 16-bit pointers. Each frame, objUpdateAll() iterates over all active objects, loads their state into objWorkspace, and calls their update function.

Dynamic Sprites

Unlike static sprite allocation where tiles live at fixed VRAM addresses, the dynamic sprite engine (oamDynamicInit, oamDynamicDraw) uploads only the tiles that are visible each frame. This is essential when you have multiple animated characters, since SNES OBJ VRAM is limited to 32 KB. Sprite graphics are stored in ROM and streamed to VRAM as needed during VBlank — the NMI handler auto-flushes the VRAM tile queue, so the main loop just calls oamDynamicDraw(id) per sprite.

Controls

Button Action
D-Pad Left/Right Walk
A Jump (short hop)
Up + A High jump

How It Works

1. Initialize the Map and Sprite System

The tileset is loaded to VRAM $2000, and the tilemap pointer is set to $6800 with a 64x32 tile layout. The dynamic sprite engine is initialized with large sprites at VRAM $0000 and small sprites at $1000.

static const OamDynamicConfig dyn = {
.vramLarge = 0x0000,
.vramSmall = 0x1000,
.slotLargeInit = 0,
.slotSmallInit = 0,
.sizeMode = OBJ_SIZE8_L16,
};
(&tilesetend - &tileset), 16 * 2, BG_16COLORS, 0x2000);
bgSetMapPtr(0, 0x6800, SC_64x32);
void bgInitTileSet(u8 bgNumber, u8 *tileSource, u8 *tilePalette, u8 paletteEntry, u16 tileSize, u16 paletteSize, u16 colorMode, u16 vramAddr)
Initialize tileset with tiles and palette.
void bgSetMapPtr(u8 bg, u16 vramAddr, u8 mapSize)
Set background tilemap address and size.
static u16 bx
Definition main.c:159
u8 tilesetpal
BG1 tileset palette (BGR555, 16 colors)
u8 tileset
BG1 tileset tile data (4bpp) – start label.
u8 tilesetend
BG1 tileset tile data – end label (for size calculation)
#define BG_16COLORS
Definition background.h:47
#define SC_64x32
Definition background.h:37
void oamDynamicInit(const OamDynamicConfig *cfg)
Initialize the dynamic sprite engine from a config struct.
#define OBJ_SIZE8_L16
Sprite size indices (for oamInit, oamInitGfxSet)
Definition sprite.h:49
Configuration for the dynamic sprite engine.
Definition sprite.h:574
u16 vramLarge
Definition sprite.h:575

2. Register Object Types in Assembly

Each object type (Mario=0, Goomba=1, Koopa Troopa=2) has its init and update function pointers stored with correct bank bytes. This must be done in assembly because C cannot express 24-bit far pointers:

extern void objRegisterTypes(void);
void objRegisterTypes(void)
Register all object type callbacks (Mario, Goomba, Koopa) with correct bank bytes.

3. Load Objects from Map Data

Objects are defined in the level editor and stored in a .o16 file. The object engine parses this data, calling each type's init function to spawn entities at their map positions:

u8 tilesetatt[]
Tile attribute table (T_SOLID/T_EMPTY per tile index, b16 format)
u8 mapmario[]
Map data (tile indices + header with width/height)
u8 tilesetdef
Tile definition table (visual properties per tile index)
u8 objmario
Object layer data (spawn positions and type IDs for entities)
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:46
void mapLoad(u8 *layer1map, u8 *layertiles, u8 *tilesprop)
Load map data into the engine and flush to VRAM.
void objLoadObjects(u8 *sourceO)
Load objects from a data table.

4. Main Loop: Update, Draw, Sync

Every frame: update the map scroll state, update all objects (physics + AI), wait for VBlank, then let mapVblank() flush tilemap changes. The NMI handler auto-flushes the dynamic sprite engine — no oamInitDynamicSpriteEndFrame or oamVramQueueUpdate calls needed in the main loop.

while (1) {
}
void WaitForVBlank(void)
Wait for next VBlank period.
void mapVblank(void)
Transfer map updates to VRAM.
void mapUpdate(void)
Update map scroll buffers based on camera position.
void objUpdateAll(void)
Update all active objects.

5. Enemy AI (Goomba Example)

Each Goomba patrols between xmin and xmax boundaries. A frame counter triggers animation and direction changes every 10 ticks. Screen position is computed by subtracting the camera scroll (x_pos, y_pos):

s16 goombax
Definition goomba.c:19
u16 goombanum
Definition goomba.c:18
u16 x_pos
Current camera X position in pixels.
t_sprites oambuffer[128]
Dynamic sprite buffer (128 entries, 2048 bytes)
void oamDynamicDraw(u16 id)
Draw a dynamic sprite — engine picks the size routine.
s16 oamx
Definition sprite.h:189

Project Structure

mapandobjects/
├── main.c — Initialization, main loop, map/object engine setup
├── mario.c / .h — Player: input, physics, walk/jump/fall animation
├── goomba.c / .h — Goomba enemy: patrol AI, 2-frame animation
├── koopatroopa.c / .h — Koopa Troopa: 2-sprite composite, flip on turn
├── data.asm — ROM data + object type registration (ASM)
├── Makefile — Build config (4 C files, 8 library modules)
└── res/ — Tileset, sprite sheets, level data (.m16/.o16/.t16/.b16)

Build & Run

cd $OPENSNES_HOME
make -C examples/games/mapandobjects

Then open mapandobjects.sfc in your emulator (Mesen2 recommended).