This example demonstrates platformer physics with slope collision detection. Mario walks, jumps, and slides along diagonal terrain using the objCollidMapWithSlopes() function from the OpenSNES object engine. It combines the map engine (scrolling tiled backgrounds), the object engine (entity management with callbacks), and the dynamic sprite engine (animated character rendering) into a complete platformer framework. No audio – the focus is on collision mechanics.
Screenshot
What You'll Learn
- How to use
objCollidMapWithSlopes() for diagonal terrain collision
- How the OpenSNES object engine manages entities with init/update callbacks
- How map scrolling follows a player object via
mapUpdateCamera()
- How dynamic sprites handle animation frames with state machines
- How to register object type callbacks from assembly (for correct bank bytes)
SNES Concepts
The Map Engine
The OpenSNES map engine handles large scrolling worlds that exceed the 32x32 or 64x64 tile hardware limits. It loads map data from .m16 files (tilemap), .t16 files (tile type definitions), and .b16 files (tile attribute/behavior data). The map is initialized with mapLoad() and updated each frame with mapUpdate(), which streams new tile columns/rows into VRAM as the camera scrolls. The mapVblank() function performs the actual VRAM transfers during VBlank.
Object Engine and Slope Collision
Each object type has an init callback and an update callback, registered via objRegisterTypes(). The engine calls these automatically during objUpdateAll(). The key function objCollidMapWithSlopes() reads tile attribute data (from .b16) to determine which tiles are solid, which are slopes (types T_SLOPEU1 through T_SLOPEUD2), and adjusts the object's Y position to follow diagonal surfaces. The object's tilestand field indicates whether the character is on ground – essential for jump detection.
Dynamic Sprite Engine
The dynamic sprite engine (oamDynamicInit, oamDynamicDraw) manages OAM entries and VRAM tile uploads automatically. Each oambuffer[] entry tracks position, current frame ID, and a refresh flag. When oamrefresh is set to 1, the engine re-uploads that sprite's tile data to VRAM during the next VBlank — the NMI handler auto-flushes the queue, so the application code never calls oamVramQueueUpdate or oamInitDynamicSpriteEndFrame from the main loop. The engine is configured at startup with an OamDynamicConfig struct (vramLarge=0x0000, vramSmall=0x1000, sizeMode=OBJ_SIZE8_L16).
Assembly Bank Byte Registration
The compiler produces 16-bit function pointers without bank bytes, but C functions may be placed in bank $01+ by the linker. The objRegisterTypes() assembly function uses WLA-DX's :label syntax to resolve the correct bank byte at link time, storing the full 24-bit address in the object callback tables (objfctinit, objfctupd).
Controls
| Button | Action |
| D-PAD Left/Right | Walk left/right (with acceleration up to MARIO_MAXACCEL) |
| A | Jump (hold Up + A for a higher jump) |
How It Works
1. Setup – The tileset is loaded to VRAM, the map engine is initialized with slope attribute data, and Mario's object type is registered:
.vramSmall = 0x1000,
.slotLargeInit = 0,
.slotSmallInit = 0,
};
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 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)
void objRegisterTypes(void)
Register all object type callbacks (Mario, Goomba, Koopa) with correct bank bytes.
u8 tileset
BG1 tileset tile data (4bpp) – start label.
u8 objmario
Object layer data (spawn positions and type IDs for entities)
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
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.
u8 tilepal
BG1 tileset palette (BGR555, 16 colors)
void objInitEngine(void)
Initialize the object engine.
void objLoadObjects(u8 *sourceO)
Load objects from a data table.
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. Mario init callback – When the object is created, it sets collision dimensions (width 14, height 16, xofs 1) and loads the sprite graphics and palette:
}
void dmaCopyCGram(u8 *source, u16 startColor, u16 size)
Copy palette data to CGRAM (PVSnesLib compatible)
void marioinit(u16 xp, u16 yp, u16 type, u16 minx, u16 maxx)
Definition mario.c:56
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:52
#define ACT_STAND
Definition map.h:93
t_objs objWorkspace
Object workspace (Bank $00)
#define OAM_SET_GFX(id, gfx)
Set sprite graphics address (bank $00 only)
Definition sprite.h:225
t_sprites oambuffer[128]
Dynamic sprite buffer (128 entries, 2048 bytes)
u16 height
Definition object.h:93
u16 width
Definition object.h:92
u16 xofs
Definition object.h:90
u16 action
Definition object.h:102
u16 oamframeid
Definition sprite.h:191
3. Mario update callback – Each frame: read input, apply acceleration, run slope collision, advance the animation state machine, and update the camera:
}
static u16 pad0
Definition main.c:167
u16 mariox
Definition mario.c:21
void marioupdate(u16 idx)
Definition mario.c:79
u16 marioy
Definition mario.c:21
void mapUpdateCamera(u16 xpos, u16 ypos)
Update camera position to follow an object.
u16 x_pos
Current camera X position in pixels.
u16 y_pos
Current camera Y position in pixels.
void objCollidMapWithSlopes(u16 objhandle)
Check object collision with map tiles including slopes.
void objUpdateXY(u16 objindex)
Update object position from velocity.
void oamDynamicDraw(u16 id)
Draw a dynamic sprite — engine picks the size routine.
s16 oamx
Definition sprite.h:189
s16 oamy
Definition sprite.h:190
4. Main loop – Updates the map and all objects; the NMI handler auto-flushes the dynamic sprite engine each VBlank, and mapVblank() handles tilemap DMA:
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.
Project Structure
slopemario/
├── main.c — Console init, map/object setup, main loop
├── mario.c — Mario init/update callbacks, physics, animation
├── mario.h — Mario callback prototypes
├── data.asm — ROM data: tileset, palette, map, object types, bank byte registration
├── Makefile — Build configuration
└── res/
├── tiles.png — Tileset with slope tiles (4bpp, Mode 1)
├── mario_sprite.png — Mario animation frames (16x16, 4bpp)
├── BG1.m16 — Map data (tile layout)
├── map_1_1.o16 — Object placement data (Mario spawn point)
├── map_1_1.t16 — Tile type definitions (solid, slope, empty)
└── map_1_1.b16 — Tile behavior/attribute data
Build & Run
cd $OPENSNES_HOME
make -C examples/maps/slopemario
Then open slopemario.sfc in your emulator (Mesen2 recommended).