Breakout game with brick collision, paddle physics, and level cycling. More...
#include <snes.h>Macros | |
| #define | ST_BLANK str_blank |
| #define | ST_GAMEOVER str_gameover |
| #define | ST_PAUSED str_paused |
| #define | ST_READY str_ready |
Functions | |
| static void | check_bricks (void) |
| Check collision with bricks. | |
| static void | check_paddle (void) |
| Check collision with paddle. | |
| static void | die (void) |
| Handle player losing a life. | |
| static void | draw_screen (void) |
| Update all sprite positions. | |
| static void | handle_pause (void) |
| Handle pause functionality. | |
| int | main (void) |
| Game initialization and main loop. | |
| static void | move_ball (void) |
| Update ball position and handle wall collision. | |
| static void | move_paddle (void) |
| Handle paddle movement from joypad input. | |
| static void | mycopy (u8 *dest, const u8 *src, u16 len) |
| Simple byte-by-byte memory copy. | |
| static void | new_level (void) |
| Initialize a new level. | |
| static void | remove_brick (void) |
| Remove a brick and update score. | |
| static void | run_frame (void) |
| Run one frame of gameplay. | |
| static void | writenum (u16 num, u8 len, u16 *tilemap, u16 pos, u16 offset) |
| Write a right-aligned number to a tilemap buffer. | |
| static void | writestring (const char *st, u16 *tilemap, u16 pos, u16 offset) |
| Write a null-terminated string to a tilemap buffer. | |
Variables | |
| static u16 | a |
| static u16 | b |
| u16 | backmap [] |
| BG3 tilemap RAM copy (0x400 entries = 2KB) at WRAM $1000. | |
| u8 | backpal [] |
| 7 level color sets for palette cycling (7 x 16 bytes = 112 bytes) | |
| u8 | backpal_end [] |
| u8 | bg1map [] |
| BG1 tilemap: playfield border frame and HUD layout (2KB) | |
| u8 | bg1map_end [] |
| u8 | bg2map [] |
| BG3 tilemap: all 4 background fill patterns concatenated (2KB) | |
| u8 | bg2map0 [] |
| Individual BG3 patterns (pointers into bg2map for per-level selection) | |
| u8 | bg2map1 [] |
| u8 | bg2map2 [] |
| u8 | bg2map3 [] |
| u8 | bg2map_end [] |
| static u16 | blockcount |
| u16 | blockmap [] |
| BG1 tilemap RAM copy (0x400 entries = 2KB) at WRAM $0800. | |
| u8 | blocks [] |
| Mutable brick state array (100 entries, one per grid cell). | |
| const u8 | brick_map [] |
| Default brick layout (10x10 grid, 100 bytes). | |
| static u16 | bx |
| static u16 | by |
| static u16 | c |
| static u16 | color |
| static u16 | hiscore |
| static u8 | i |
| static u8 | j |
| static u8 | k |
| static u16 | level |
| static u16 | level2 |
| static u16 | lives |
| static u16 | obx |
| static u16 | oby |
| static u16 | pad0 |
| u16 | pad_keys [] |
| Raw joypad state buffer written by the NMI handler every frame. | |
| u16 | pal [] |
| Palette RAM copy (0x100 entries = 512 bytes) at WRAM $1800. | |
| u8 | palette [] |
| Full 256-color palette for BG and sprite layers (512 bytes) | |
| u8 | palette_end [] |
| static s16 | pos_x |
| static s16 | pos_y |
| static u16 | px |
| static u16 | score |
| const char | str_blank [] |
| Blank string (spaces) for clearing message areas. | |
| const char | str_gameover [] |
| "GAME OVER" message string | |
| const char | str_paused [] |
| "PAUSED" message string | |
| const char | str_ready [] |
| "READY" message string (in data.asm for guaranteed bank $00 placement) | |
| u8 | tiles1 [] |
| BG tiles: border, bricks, and font glyphs (4bpp, ~3840 bytes) | |
| u8 | tiles1_end [] |
| u8 | tiles2 [] |
| Sprite tiles: ball, paddle, and shadow shapes (4bpp, ~592 bytes) | |
| u8 | tiles2_end [] |
| static s16 | vel_x |
| Ball X velocity (-2 to +2 pixels/frame). | |
| static s16 | vel_y |
Breakout game with brick collision, paddle physics, and level cycling.
A complete Breakout game demonstrating how multiple SNES subsystems work together in a real game. The playfield uses background tiles (not sprites) for bricks, enabling 100 on-screen bricks without hitting the 128-sprite hardware limit. The paddle and ball are hardware sprites with shadow sprites for depth effect.
Key technical patterns: RAM tilemap buffers for runtime brick destruction, overlapping BG1/BG3 tilemaps in VRAM to save memory (requiring atomic dual-DMA updates), palette cycling for per-level color variation, and direct oamMemory[] writes instead of oamSet() for performance (10 sprites per frame would cause visible slowdown with oamSet's 158-byte stack frame).
Ball bounce angle varies by paddle hit zone (4 zones), creating the classic Breakout aiming mechanic. Scoring uses level multipliers.
Based on SNES SDK game by Ulrich Hecht, PVSnesLib port by alekmaul.
| #define ST_BLANK str_blank |
Alias for the blank clearing string
| #define ST_GAMEOVER str_gameover |
Alias for the GAME OVER message
| #define ST_PAUSED str_paused |
Alias for the PAUSED message
| #define ST_READY str_ready |
Alias for the READY message
|
static |
Check collision with bricks.
GRID-BASED COLLISION: Convert ball pixel position to brick grid coordinates, then check if that grid cell contains a brick.
Grid layout: 10 columns x 10 rows Each brick is 16x8 pixels Grid offset from screen edge: 14 pixels
|
static |
Check collision with paddle.
BOUNCE PHYSICS: Ball bounce angle depends on where it hits the paddle: Left edge: Sharp left angle (vel_x = -2, vel_y = -1) Left-mid: Slight left (vel_x = -1, vel_y = -2) Right-mid: Slight right (vel_x = +1, vel_y = -2) Right edge: Sharp right angle (vel_x = +2, vel_y = -1)
This creates the classic breakout gameplay where paddle position matters for aiming the ball.
|
static |
Handle player losing a life.
< Alias for the GAME OVER message
< Alias for the READY message
< Alias for the blank clearing string
< Alias for the blank clearing string
|
static |
Update all sprite positions.
SPRITE ORGANIZATION: This game uses 10 hardware sprites for the ball and paddle. Sprite 0 is skipped due to a corruption issue (possibly WRAM mirroring).
Sprite Assignment: Sprite 1: Ball Sprites 2-5: Paddle (left cap, 2x middle mirrored, right cap) Sprite 6: Ball shadow (offset +3,+3 pixels, lower priority) Sprites 7-10: Paddle shadow (offset +4,+4 pixels, lower priority)
TILE NUMBERING: Sprite tiles are at VRAM $2000 (secondary name table). To access secondary table, set bit 8 of tile number: tile | 256
Tile Layout in tiles2: Tile 15: Paddle left cap Tile 16: Paddle middle (used twice, second time H-flipped) Tile 17: Paddle right cap Tile 18: Paddle shadow left Tile 19: Paddle shadow middle Tile 20: Ball Tile 21: Ball shadow
SHADOW TECHNIQUE: Shadows are separate sprites with:
|
static |
Handle pause functionality.
< Alias for the PAUSED message
< Alias for the blank clearing string
| int main | ( | void | ) |
Game initialization and main loop.
INITIALIZATION SEQUENCE:
< Alias for the READY message
< Alias for the blank clearing string
< Alias for the blank clearing string
|
static |
Update ball position and handle wall collision.
NOTE: Uses explicit temp variables instead of direct assignment to work around QBE compiler issues with compound operations.
|
static |
Handle paddle movement from joypad input.
Paddle moves at 2 pixels/frame normally, 4 with A held. Clamped to playfield boundaries (16-144).
Simple byte-by-byte memory copy.
Used instead of library memcpy to avoid bank addressing complexity. Works for Bank 0 addresses only (ROM $8000+ and RAM $0000-$1FFF).
|
static |
Initialize a new level.
This function handles level transitions:
CRITICAL DMA LESSON: BG1 tilemap (0x0000-0x07FF) and BG3 tilemap (0x0400-0x0BFF) OVERLAP in VRAM at addresses 0x0400-0x07FF. Both tilemaps must be uploaded in the SAME VBlank, or the intermediate frame shows corruption.
This was a hard-learned lesson: splitting "to be safe" actually caused a visible pink flash. PVSnesLib does 4.5KB+ in one VBlank.
< Alias for the READY message
< Alias for the blank clearing string
< Alias for the blank clearing string
|
static |
Remove a brick and update score.
When a brick is destroyed:
|
static |
Run one frame of gameplay.
Write a right-aligned number to a tilemap buffer.
| num | Number to display |
| len | Field width (pads with spaces on left) |
| tilemap | Pointer to tilemap buffer |
| pos | Starting position (leftmost digit position) |
| offset | Value added to digit (0-9) to get tile index |
Write a null-terminated string to a tilemap buffer.
Converts ASCII characters to tile indices by adding an offset. Supports newline (
) for multi-line text.
| st | Null-terminated string |
| tilemap | Pointer to tilemap buffer (u16 per entry) |
| pos | Starting position in tilemap (0-0x3FF for 32x32) |
| offset | Value added to ASCII code to get tile index |
Tilemap entry format (16-bit): Bits 0-9: Tile number (0-1023) Bits 10-12: Palette (0-7) Bit 13: Priority Bit 14: H-flip Bit 15: V-flip
|
static |
|
static |
Brick init temporaries (reused in new_level/main)
|
extern |
BG3 tilemap RAM copy (0x400 entries = 2KB) at WRAM $1000.
Modified for shadow effects under bricks (palette bit toggling). DMAs to VRAM $0400-$0BFF. Overlaps BG1 at VRAM $0400-$07FF, so both must be uploaded atomically in the same VBlank.
|
extern |
7 level color sets for palette cycling (7 x 16 bytes = 112 bytes)
| u8 backpal_end[] |
|
extern |
BG1 tilemap: playfield border frame and HUD layout (2KB)
| u8 bg1map_end[] |
|
extern |
BG3 tilemap: all 4 background fill patterns concatenated (2KB)
|
extern |
Individual BG3 patterns (pointers into bg2map for per-level selection)
| u8 bg2map1[] |
| u8 bg2map2[] |
| u8 bg2map3[] |
| u8 bg2map_end[] |
|
static |
Number of bricks remaining (0 triggers next level)
|
extern |
BG1 tilemap RAM copy (0x400 entries = 2KB) at WRAM $0800.
Modified at runtime when bricks are destroyed and HUD text updates. DMAs to VRAM $0000-$07FF each time visuals change.
|
extern |
Mutable brick state array (100 entries, one per grid cell).
Values 0-7 = active brick with that color, 8 = destroyed/empty. Initialized from the ROM brick_map[] template at each level start.
|
extern |
Default brick layout (10x10 grid, 100 bytes).
Each byte encodes the brick type at that grid position: values 0-7 select a colored brick (palette index), value 8 = empty cell. Copied to the mutable blocks[] array at level start so bricks can be destroyed at runtime without modifying the ROM template.
|
static |
|
static |
Ball position in brick grid coordinates (0-9 each)
|
static |
|
static |
Background palette set index (0-6, cycles per level)
|
static |
Current score and all-time high score
|
static |
|
static |
|
static |
Loop counters (global to reduce 65816 stack overhead)
|
static |
Internal level counter (0-based, used as score multiplier)
|
static |
Display level number (1-based, shown in HUD)
|
static |
Lives remaining (0 = game over on next death)
|
static |
|
static |
Previous frame's grid position (determines bounce axis)
|
static |
Current frame's joypad button bitmask
|
extern |
Raw joypad state buffer written by the NMI handler every frame.
pad_keys[0] = port 1 buttons, pad_keys[1] = port 2 buttons. Used directly (instead of padHeld()) for the game loop because Breakout was ported from PVSnesLib which reads pad_keys[] directly.
|
extern |
Palette RAM copy (0x100 entries = 512 bytes) at WRAM $1800.
|
extern |
Full 256-color palette for BG and sprite layers (512 bytes)
| u8 palette_end[] |
|
static |
Ball X pixel position
|
static |
Ball Y pixel position
|
static |
Paddle X position in pixels (clamped to 16-144)
|
static |
|
extern |
Blank string (spaces) for clearing message areas.
|
extern |
"GAME OVER" message string
|
extern |
"PAUSED" message string
|
extern |
"READY" message string (in data.asm for guaranteed bank $00 placement)
|
extern |
BG tiles: border, bricks, and font glyphs (4bpp, ~3840 bytes)
| u8 tiles1_end[] |
|
extern |
Sprite tiles: ball, paddle, and shadow shapes (4bpp, ~592 bytes)
| u8 tiles2_end[] |
|
static |
Ball X velocity (-2 to +2 pixels/frame).
Separate from a struct because the cc65816 compiler has quirks with compound operations on struct members (e.g., negation, += assignment).
|
static |
Ball Y velocity (-2 to +2 pixels/frame)