This tutorial covers collision detection on the SNES, from basic bounding box checks to tile-based world collision used in platformers.
Why AABB?
The SNES has no hardware collision detection. All collision must be done in software by the CPU. Axis-aligned bounding box (AABB) testing is the standard approach because it requires only four comparisons per pair of objects – fast enough for the 65816 at 3.58 MHz.
The Rect Type
OpenSNES provides a Rect structure representing an axis-aligned bounding box:
typedef struct {
SNES Collision Detection.
signed short s16
16-bit signed integer (-32768 to 32767)
Definition types.h:49
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:52
Axis-aligned bounding box (rectangle)
Definition collision.h:54
Initialize it with rectInit() or by direct assignment:
static Rect player_box
Player bounding box for AABB collision tests via collideRect()
Definition main.c:59
static s16 player_y
Player Y position in screen coordinates.
Definition main.c:57
static s16 player_x
Player X position in screen coordinates.
Definition main.c:55
void rectInit(Rect *r, s16 x, s16 y, u16 w, u16 h)
Initialize a rectangle.
Sprite-to-Sprite Collision
Basic Overlap Test
Use collideRect() to test whether two rectangles overlap:
player_hit();
}
}
static s16 enemy_y[4]
Y positions of the four static enemy sprites.
Definition main.c:64
static void check_collisions(void)
Test player-vs-enemy AABB overlap and update collision_flags.
Definition main.c:330
static Rect enemy_box[4]
Bounding boxes for each enemy, rebuilt every frame for collideRect()
Definition main.c:66
static s16 enemy_x[4]
X positions of the four static enemy sprites.
Definition main.c:62
u8 collideRect(Rect *a, Rect *b)
Check rectangle vs rectangle collision (AABB)
u16 height
Definition collision.h:58
s16 y
Definition collision.h:56
u16 width
Definition collision.h:57
s16 x
Definition collision.h:55
Testing Multiple Enemies
The collision_demo example checks the player against four enemies each frame using a bitmask to track which enemies are currently colliding:
#define NUM_ENEMIES 4
}
}
}
static u8 collision_flags
Bitmask tracking which enemies the player is currently overlapping.
Definition main.c:74
#define PLAYER_SIZE
Definition main.c:49
#define NUM_ENEMIES
Definition main.c:51
#define ENEMY_SIZE
Definition main.c:50
static u8 i
Definition main.c:156
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:46
Point-vs-Rectangle
Use collidePoint() when checking a single coordinate (bullet, cursor) against a target:
target_hit();
}
u8 collidePoint(s16 x, s16 y, Rect *r)
Check point vs rectangle collision.
Sprite-to-Background Collision
Reading Tilemap Data
For platformers and top-down games, the world is made of tiles. A separate collision map (one byte per tile, 0 = empty, nonzero = solid) lets you check whether a pixel position is blocked:
#define MAP_WIDTH 16
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,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
};
static u8 collision_map[16 *14]
Tile-based collision map (16x14 = 224 bytes, 128x112 pixel area)
Definition main.c:94
#define MAP_HEIGHT
Definition main.c:81
#define MAP_WIDTH
Definition main.c:80
Use collideTile() to look up the tile at a pixel position:
on_ground = 1;
}
u8 collideTile(s16 px, s16 py, u8 *tilemap, u16 mapWidth)
Check collision with tile at pixel coordinates.
Checking All Four Corners
A single point check misses edges. The collision_demo example checks all four corners of the player bounding box to prevent clipping through walls:
return 0;
}
#define MAP_OFFSET_Y
Screen Y offset where the collision map starts (centers the 112px map on 224px screen)
Definition main.c:114
#define MAP_OFFSET_X
Screen X offset where the collision map starts (centers the 128px map on 256px screen)
Definition main.c:112
static u8 check_wall_collision(s16 new_x, s16 new_y)
Test whether a position would overlap a solid wall tile.
Definition main.c:371
You can also use collideRectTile() which does this internally:
}
u8 collideRectTile(Rect *r, u8 *tilemap, u16 mapWidth)
Check collision between rectangle and tilemap.
Custom Tile Sizes
For maps with 16x16 tiles, use collideTileEx():
on_ground = 1;
}
u8 collideTileEx(s16 px, s16 py, u8 *tilemap, u16 mapWidth, u8 tileSize)
Check collision with tile using custom tile size.
Tile Properties from a Tilemap (Platformer Pattern)
The likemario example uses 16-bit tile properties looked up from the visual tilemap, rather than a separate collision map. Each tile index maps to a property value (T_SOLID or T_EMPTY):
#define T_EMPTY 0x0000
#define T_SOLID 0xFF00
}
static u16 px
Definition main.c:166
static u16 map_height
Definition main.c:139
static u16 map_get_tile_prop(s16 px, s16 py)
Look up the collision property of the tile at pixel coordinates.
Definition main.c:199
static u16 * tile_props
Definition main.c:141
static u16 map_width
Definition main.c:138
static u16 * map_row_ptrs[32]
Precomputed row pointers into map_data for fast tile lookup.
Definition main.c:150
#define T_EMPTY
Empty tile (object will fall through)
Definition map.h:54
#define T_SOLID
Solid tile (blocks movement)
Definition map.h:57
This avoids storing a duplicate collision map – the visual tilemap doubles as the collision source.
Collision Response Patterns
Detecting a collision is only half the problem. What happens next matters more.
Stopping Movement (Slide Along Walls)
When the player tries to move diagonally into a wall, test each axis independently so they can slide along the wall instead of getting stuck:
} else {
}
}
}
#define PLAYER_SPEED
Definition main.c:52
Pushing Out of Overlap
Use collideRectEx() to get the overlap amount and push objects apart. This is useful for objects that can drift into each other (e.g., physics objects):
}
u8 collideRectEx(Rect *a, Rect *b, s16 *overlapX, s16 *overlapY)
Check if two rectangles overlap and return overlap amount.
Bouncing (Breakout Pattern)
The breakout example reverses velocity on collision with walls and paddle. Negation uses a temporary variable to work around compiler constraints:
}
switch (zone) {
}
}
}
static s16 vel_y
Definition main.c:176
static s16 pos_x
Definition main.c:177
static s16 pos_y
Definition main.c:178
static s16 vel_x
Ball X velocity (-2 to +2 pixels/frame).
Definition main.c:175
Snapping to Tile Grid (Platformer Landing)
When a falling character hits the ground, snap their Y position to the tile boundary to prevent sinking into the floor:
on_ground = 1;
}
static s16 mario_yvel
Definition main.c:160
static s16 mario_x
Definition main.c:155
static s16 mario_y
Definition main.c:156
The mask 0xFFF8 rounds down to the nearest 8-pixel tile boundary. Subtracting the sprite height (16) places the sprite so its feet rest exactly on top of the solid tile.
Grid-Based Collision (Breakout Bricks)
The breakout example converts the ball's pixel position to a grid coordinate and checks a brick array directly:
}
}
static void remove_brick(void)
Remove a brick and update score.
Definition main.c:642
static u16 bx
Definition main.c:159
static u16 by
Definition main.c:159
u8 blocks[]
Mutable brick state array (100 entries, one per grid cell).
Performance Tips
- Check collision only when objects are close. Skip
collideRect() for enemies off screen.
- Use bitmasks for groups. Track collision state with bit flags (
collision_flags |= (1 << i)) instead of boolean arrays.
- Separate axes. Test X and Y independently – this is cheaper than diagonal resolution and gives better sliding behavior.
- Power-of-2 tile sizes. Use 8 or 16 pixel tiles so division becomes a bit shift (
>> 3 or >> 4).
- Avoid
collideRect() in tight loops with many objects. For N-vs-N collision, consider spatial partitioning (grid cells) to reduce the number of pairs tested.
Complete Examples
examples/basics/collision_demo/ – AABB sprite collision with tile-based wall collision and visual feedback
examples/games/breakout/ – Ball vs walls, ball vs paddle (angle control), ball vs brick grid
examples/games/likemario/ – Platformer tile collision with gravity, ground snapping, and wall sliding
Next Steps