Loading...
Searching...
No Matches
main.c File Reference

AABB and tile-based collision detection with visual feedback. More...

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

Macros

#define ENEMY_SIZE   8
 
#define MAP_HEIGHT   14
 
#define MAP_OFFSET_X   64
 Screen X offset where the collision map starts (centers the 128px map on 256px screen)
 
#define MAP_OFFSET_Y   56
 Screen Y offset where the collision map starts (centers the 112px map on 224px screen)
 
#define MAP_WIDTH   16
 
#define NUM_ENEMIES   4
 
#define PLAYER_SIZE   8
 
#define PLAYER_SPEED   2
 

Functions

static void check_collisions (void)
 Test player-vs-enemy AABB overlap and update collision_flags.
 
static u8 check_wall_collision (s16 new_x, s16 new_y)
 Test whether a position would overlap a solid wall tile.
 
static void draw_tilemap (void)
 Render the collision map as BG tiles in VRAM.
 
static void load_graphics (void)
 Load all tile and palette data into VRAM/CGRAM via DMA.
 
int main (void)
 Entry point – initialize arena, run game loop with collision tests.
 
static void update_sprites (void)
 Write all sprite positions and attributes into the OAM buffer.
 

Variables

static const u8 bg_palette []
 BG palette (2 BGR555 colors): black background + gray walls.
 
static u8 collision_flags
 Bitmask tracking which enemies the player is currently overlapping.
 
static u8 collision_map [16 *14]
 Tile-based collision map (16x14 = 224 bytes, 128x112 pixel area)
 
static const u8 collision_palette []
 Sprite palette 1 – collision highlight (CGRAM offset 144)
 
static const u8 empty_tile []
 Empty background tile (8x8, 2bpp = 16 bytes, all zeros = transparent)
 
static Rect enemy_box [4]
 Bounding boxes for each enemy, rebuilt every frame for collideRect()
 
static const u8 enemy_tile []
 Enemy sprite tile (8x8, 4bpp = 32 bytes) — TRON-style wireframe.
 
static s16 enemy_x [4]
 X positions of the four static enemy sprites.
 
static s16 enemy_y [4]
 Y positions of the four static enemy sprites.
 
static Rect player_box
 Player bounding box for AABB collision tests via collideRect()
 
static const u8 player_tile []
 Player sprite tile (8x8, 4bpp = 32 bytes) — TRON-style wireframe.
 
static s16 player_x
 Player X position in screen coordinates.
 
static s16 player_y
 Player Y position in screen coordinates.
 
static const u8 sprite_palette []
 Sprite palette 0 – normal state (CGRAM offset 128, 16 colors)
 
static const u8 wall_tile []
 Wall background tile (8x8, 2bpp = 16 bytes)
 

Detailed Description

AABB and tile-based collision detection with visual feedback.

Demonstrates the OpenSNES collision module with two complementary techniques: axis-aligned bounding box (AABB) tests between sprites, and tile-based collision against a static map. A white player sprite moves through a walled arena while four red enemy sprites sit at fixed positions. Collisions are detected every frame and indicated by color changes (white -> green). Wall collisions use corner-point checks against a 16x14 collision map, with per-axis rejection so the player can slide along walls.

The example also illustrates the direct oamMemory[] write pattern instead of calling oamSet() per sprite, which avoids the 158-byte stack frame overhead of oamSet() and keeps the main loop fast enough for 5 sprites at 60 fps.

SNES Concepts
  • AABB collision via collideRect() for sprite-vs-sprite overlap testing
  • Tile collision via collideTile() for sprite-vs-map wall rejection
  • Direct OAM buffer writes (oamMemory[]) with oam_update_flag for NMI DMA
  • OAM high table management (bytes 512+) for X high bit and sprite size
  • Separate sprite palette banks for normal and collision-highlight colors
  • Mode 0 BG with hand-coded wall/empty tiles for the collision arena
What to Observe
  • Move the white square with D-pad through the gray-walled arena
  • The player cannot pass through walls; it slides along them
  • When the player overlaps a red enemy, both turn green
  • Multiple simultaneous collisions are tracked independently
Modules Used
console, input, sprite, dma, collision, background
See also
collision.h, sprite.h, dma.h, input.h

Macro Definition Documentation

◆ ENEMY_SIZE

#define ENEMY_SIZE   8

Enemy sprite width/height in pixels (8x8)

◆ MAP_HEIGHT

#define MAP_HEIGHT   14

Collision map height in 8x8 tiles

◆ MAP_OFFSET_X

#define MAP_OFFSET_X   64

Screen X offset where the collision map starts (centers the 128px map on 256px screen)

◆ MAP_OFFSET_Y

#define MAP_OFFSET_Y   56

Screen Y offset where the collision map starts (centers the 112px map on 224px screen)

◆ MAP_WIDTH

#define MAP_WIDTH   16

Collision map width in 8x8 tiles

◆ NUM_ENEMIES

#define NUM_ENEMIES   4

Number of static enemy sprites in the arena

◆ PLAYER_SIZE

#define PLAYER_SIZE   8

Player sprite width/height in pixels (8x8)

◆ PLAYER_SPEED

#define PLAYER_SPEED   2

Player movement speed in pixels per frame

Function Documentation

◆ check_collisions()

static void check_collisions ( void  )
static

Test player-vs-enemy AABB overlap and update collision_flags.

Rebuilds the player and all enemy bounding boxes from their current positions, then calls collideRect() for each player-enemy pair. The result is stored in collision_flags as a bitmask (bit N = enemy N is overlapping). This bitmask drives the palette swap in update_sprites().

< Player sprite width/height in pixels (8x8)

< Player sprite width/height in pixels (8x8)

< Number of static enemy sprites in the arena

< Enemy sprite width/height in pixels (8x8)

< Enemy sprite width/height in pixels (8x8)

◆ check_wall_collision()

static u8 check_wall_collision ( s16  new_x,
s16  new_y 
)
static

Test whether a position would overlap a solid wall tile.

Converts screen coordinates to collision map coordinates (subtracting the map offset), then probes all four corners of the player's 8x8 bounding box against the collision_map[] via collideTile(). If any corner lands on a solid tile (value 1), the function returns 1 to reject the movement.

The caller uses per-axis rejection: if the combined (new_x, new_y) collides, it tries each axis independently so the player can slide along walls instead of stopping dead.

Parameters
new_xProposed player X position in screen coordinates
new_yProposed player Y position in screen coordinates
Returns
1 if the position overlaps a wall, 0 if clear

< Collision map width in 8x8 tiles

< Player sprite width/height in pixels (8x8)

< Collision map width in 8x8 tiles

< Player sprite width/height in pixels (8x8)

< Collision map width in 8x8 tiles

< Player sprite width/height in pixels (8x8)

< Player sprite width/height in pixels (8x8)

< Collision map width in 8x8 tiles

◆ draw_tilemap()

static void draw_tilemap ( void  )
static

Render the collision map as BG tiles in VRAM.

First clears the entire 32x32 BG1 tilemap at VRAM $0400 with tile 0 (empty), then writes the 16x14 collision map into the tilemap at an offset that centers it on screen. Each collision_map[] entry of 1 becomes BG tile 1 (wall outline), and 0 stays as tile 0 (empty).

VRAM writes are done via direct PPU register access (REG_VMDATAL/H), which only works during forced blank or VBlank.

< Collision map height in 8x8 tiles

< Collision map width in 8x8 tiles

< Collision map width in 8x8 tiles

◆ load_graphics()

static void load_graphics ( void  )
static

Load all tile and palette data into VRAM/CGRAM via DMA.

Called once during initialization with screen off (force blank). Loads BG tiles (empty + wall) at VRAM $0000, sprite tiles (player + enemy) at VRAM $4000, and sets up both BG and sprite color palettes. Sprite palettes are loaded at CGRAM offsets 128 and 144 (banks 0 and 1).

◆ main()

int main ( void  )

Entry point – initialize arena, run game loop with collision tests.

Sets up Mode 0 with BG1 for the wall tilemap and sprites for the player and enemies. The main loop reads the D-pad every frame, proposes a new position, validates it against the wall collision map (with per-axis sliding), checks AABB overlaps with enemies, and updates all sprite positions in the OAM buffer. The NMI handler handles the actual OAM DMA transfer during VBlank.

Returns
Never returns (infinite loop).

< Current joypad button state (bitmask)

< Proposed player position before wall check

< Player movement speed in pixels per frame

< Player movement speed in pixels per frame

< Player movement speed in pixels per frame

< Player movement speed in pixels per frame

◆ update_sprites()

static void update_sprites ( void  )
static

Write all sprite positions and attributes into the OAM buffer.

Uses direct oamMemory[] writes instead of oamSet() to avoid the 158-byte stack frame overhead per call. Each sprite occupies 4 bytes: [X_low, Y, tile_number, attribute]. The attribute byte encodes priority (bits 5-4) and palette bank (bits 3-1). When a collision is active, the palette bank switches from 0 (normal colors) to 1 (green highlight). Setting oam_update_flag = 1 tells the NMI handler to DMA the buffer to OAM during the next VBlank.

< Number of static enemy sprites in the arena

Variable Documentation

◆ bg_palette

const u8 bg_palette[]
static
Initial value:
= {
0x00, 0x00,
0x94, 0x52,
}

BG palette (2 BGR555 colors): black background + gray walls.

◆ collision_flags

u8 collision_flags
static

Bitmask tracking which enemies the player is currently overlapping.

Bit N is set when the player's AABB overlaps enemy N. Used to switch both the player and the colliding enemy to the green "collision" palette.

◆ collision_map

u8 collision_map[16 * 14]
static
Initial value:
= {
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,1,1,0,0,0,0,0,0,1,1,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,1,1,1,1,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,1,1,0,0,0,0,0,0,1,1,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
}

Tile-based collision map (16x14 = 224 bytes, 128x112 pixel area)

A flat 2D array where 1 = solid wall and 0 = passable. The map is stored in row-major order. collideTile() converts a pixel coordinate to a tile index (px/8) and looks up this array to determine if the tile is solid. The border is all walls with internal platforms at symmetric positions.

Note
This is mutable (not const) because collideTile() takes a non-const pointer. It lives in WRAM, not ROM.

◆ collision_palette

const u8 collision_palette[]
static
Initial value:
= {
0x00, 0x00,
0xE0, 0x03,
0xE0, 0x03,
0xFF, 0x7F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}

Sprite palette 1 – collision highlight (CGRAM offset 144)

When a collision is detected, the sprite's OAM attribute byte is switched to palette bank 1, turning both the player and enemy green as visual feedback. Colors 1 and 2 are both green so both the player tile (color 1) and enemy tile (color 2) appear highlighted.

◆ empty_tile

const u8 empty_tile[]
static
Initial value:
= {
0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,
0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,
}

Empty background tile (8x8, 2bpp = 16 bytes, all zeros = transparent)

◆ enemy_box

Rect enemy_box[4]
static

Bounding boxes for each enemy, rebuilt every frame for collideRect()

◆ enemy_tile

const u8 enemy_tile[]
static
Initial value:
= {
0x00,0xFF, 0x00,0x81, 0x00,0x81, 0x00,0x81,
0x00,0x81, 0x00,0x81, 0x00,0x81, 0x00,0xFF,
0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,
0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,
}

Enemy sprite tile (8x8, 4bpp = 32 bytes) — TRON-style wireframe.

A 1-pixel outlined square in palette color 2 (red). Same outline pattern as the player tile but on bitplane 1 instead of bitplane 0.

◆ enemy_x

s16 enemy_x[4]
static

X positions of the four static enemy sprites.

◆ enemy_y

s16 enemy_y[4]
static

Y positions of the four static enemy sprites.

◆ player_box

Rect player_box
static

Player bounding box for AABB collision tests via collideRect()

◆ player_tile

const u8 player_tile[]
static
Initial value:
= {
0xFF,0x00, 0x81,0x00, 0x81,0x00, 0x81,0x00,
0x81,0x00, 0x81,0x00, 0x81,0x00, 0xFF,0x00,
0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,
0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,
}

Player sprite tile (8x8, 4bpp = 32 bytes) — TRON-style wireframe.

A 1-pixel outlined square in palette color 1 (white). Top/bottom rows fully lit (0xFF), middle rows light only the left/right edges (0x81 = b10000001). 4bpp format: 8 rows of [bp0 bp1], then 8 rows of [bp2 bp3].

◆ player_x

s16 player_x
static

Player X position in screen coordinates.

◆ player_y

s16 player_y
static

Player Y position in screen coordinates.

◆ sprite_palette

const u8 sprite_palette[]
static
Initial value:
= {
0x00, 0x00,
0xFF, 0x7F,
0x1F, 0x00,
0xE0, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}

Sprite palette 0 – normal state (CGRAM offset 128, 16 colors)

Loaded at CGRAM byte offset 128 (sprite palette bank 0). Color 0 is transparent (ignored by PPU for sprites), color 1 = white (player), color 2 = red (enemy), color 3 = green (unused in normal state).

◆ wall_tile

const u8 wall_tile[]
static
Initial value:
= {
0xFF,0x00, 0x81,0x00, 0x81,0x00, 0x81,0x00,
0x81,0x00, 0x81,0x00, 0x81,0x00, 0xFF,0x00,
}

Wall background tile (8x8, 2bpp = 16 bytes)

An outlined square: border pixels use color 1 (gray), interior is color 0 (black/transparent). This provides visual feedback for the collision map walls while the collision logic uses the separate collision_map[] array.