AABB and tile-based collision detection with visual feedback. More...
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) | |
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.
| #define ENEMY_SIZE 8 |
Enemy sprite width/height in pixels (8x8)
| #define MAP_HEIGHT 14 |
Collision map height in 8x8 tiles
| #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 |
Collision map width in 8x8 tiles
| #define NUM_ENEMIES 4 |
Number of static enemy sprites in the arena
| #define PLAYER_SIZE 8 |
Player sprite width/height in pixels (8x8)
| #define PLAYER_SPEED 2 |
Player movement speed in pixels per frame
|
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)
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.
| new_x | Proposed player X position in screen coordinates |
| new_y | Proposed player Y position in screen coordinates |
< 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
|
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
|
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).
| 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.
< 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
|
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
|
static |
BG palette (2 BGR555 colors): black background + gray walls.
|
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.
|
static |
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.
|
static |
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.
|
static |
Empty background tile (8x8, 2bpp = 16 bytes, all zeros = transparent)
|
static |
Bounding boxes for each enemy, rebuilt every frame for collideRect()
|
static |
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.
|
static |
X positions of the four static enemy sprites.
|
static |
Y positions of the four static enemy sprites.
|
static |
Player bounding box for AABB collision tests via collideRect()
|
static |
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].
|
static |
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).
|
static |
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.