architecture - jonathanperis/super-mango-editor GitHub Wiki
Architecture
Overview
Super Mango follows a classic init β loop β cleanup pattern. A single GameState struct is the owner of every resource in the game and is passed by pointer to every function that needs to read or modify it.
Startup Sequence
main()
βββ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)
βββ IMG_Init(IMG_INIT_PNG)
βββ TTF_Init()
βββ Mix_OpenAudio(44100, stereo, 2048 buffer)
β
βββ game_init(&gs)
β βββ SDL_CreateWindow β gs.window
β βββ SDL_CreateRenderer β gs.renderer
β βββ SDL_RenderSetLogicalSize(GAME_W, GAME_H)
β β
β β ββ Load all textures (engine resources) ββ
β βββ parallax_init(&gs.parallax, gs.renderer) (multi-layer background)
β βββ IMG_LoadTexture β gs.floor_tile (grass_tileset.png β fatal)
β βββ IMG_LoadTexture β gs.platform_tex (platform.png β fatal)
β βββ water_init(&gs.water, gs.renderer) (water.png)
β βββ IMG_LoadTexture β gs.spider_tex (spider.png β fatal)
β βββ IMG_LoadTexture β gs.jumping_spider_tex (jumping_spider.png β fatal)
β βββ IMG_LoadTexture β gs.bird_tex (bird.png β fatal)
β βββ IMG_LoadTexture β gs.faster_bird_tex (faster_bird.png β fatal)
β βββ IMG_LoadTexture β gs.fish_tex (fish.png β fatal)
β βββ IMG_LoadTexture β gs.coin_tex (coin.png β fatal)
β βββ IMG_LoadTexture β gs.bouncepad_medium_tex (bouncepad_medium.png β fatal)
β βββ IMG_LoadTexture β gs.vine_tex (vine.png β non-fatal)
β βββ IMG_LoadTexture β gs.ladder_tex (ladder.png β non-fatal)
β βββ IMG_LoadTexture β gs.rope_tex (rope.png β non-fatal)
β βββ IMG_LoadTexture β gs.bouncepad_small_tex (bouncepad_small.png β non-fatal)
β βββ IMG_LoadTexture β gs.bouncepad_high_tex (bouncepad_high.png β non-fatal)
β βββ IMG_LoadTexture β gs.rail_tex (rail.png β non-fatal)
β βββ IMG_LoadTexture β gs.spike_block_tex (spike_block.png β non-fatal)
β βββ IMG_LoadTexture β gs.float_platform_tex (float_platform.png β non-fatal)
β βββ IMG_LoadTexture β gs.bridge_tex (bridge.png β non-fatal)
β βββ IMG_LoadTexture β gs.yellow_star_tex (yellow_star.png β non-fatal)
β βββ IMG_LoadTexture β gs.axe_trap_tex (axe_trap.png β non-fatal)
β βββ IMG_LoadTexture β gs.circular_saw_tex (circular_saw.png β non-fatal)
β βββ IMG_LoadTexture β gs.blue_flame_tex (blue_flame.png β non-fatal)
β βββ IMG_LoadTexture β gs.faster_fish_tex (faster_fish.png β non-fatal)
β βββ IMG_LoadTexture β gs.spike_tex (spike.png β non-fatal)
β βββ IMG_LoadTexture β gs.spike_platform_tex (spike_platform.png β non-fatal)
β β
β β ββ Load all sound effects ββ
β βββ Mix_LoadWAV β gs.snd_spring (bouncepad.wav β non-fatal)
β βββ Mix_LoadWAV β gs.snd_axe (axe_trap.wav β non-fatal)
β βββ Mix_LoadWAV β gs.snd_flap (bird.wav β non-fatal)
β βββ Mix_LoadWAV β gs.snd_spider_attack (spider.wav β non-fatal)
β βββ Mix_LoadWAV β gs.snd_dive (fish.wav β non-fatal)
β βββ Mix_LoadWAV β gs.snd_jump (player_jump.wav β fatal)
β βββ Mix_LoadWAV β gs.snd_coin (coin.wav β non-fatal)
β βββ Mix_LoadWAV β gs.snd_hit (player_hit.wav β non-fatal)
β βββ Mix_LoadMUS β gs.music (game_music.wav β fatal)
β βββ Mix_PlayMusic(-1) (loop forever, 10% volume)
β β
β β ββ Initialise game objects ββ
β βββ player_init(&gs.player, gs.renderer)
β βββ fog_init(&gs.fog, gs.renderer) (fog_background_1.png, fog_background_2.png)
β βββ hud_init(&gs.hud, gs.renderer)
β βββ if (debug_mode) debug_init(&gs.debug)
β βββ level_load(&gs, level_path) (TOML level file β entity inits, sea gaps, backgrounds, music)
β βββ hearts/lives/score/score_life_next initialisation
β βββ SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) β lazy init, non-fatal
β βββ scan joysticks for first connected gamepad
β
βββ game_loop(&gs) β see Game Loop section below
β
βββ game_cleanup(&gs) β reverse init order
βββ SDL_GameControllerClose(gs->controller) β if non-NULL
βββ SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER)
βββ hud_cleanup
βββ fog_cleanup
βββ player_cleanup
βββ Mix_HaltMusic + Mix_FreeMusic
βββ Mix_FreeChunk (snd_jump)
βββ Mix_FreeChunk (snd_coin)
βββ Mix_FreeChunk (snd_hit)
βββ Mix_FreeChunk (snd_spring)
βββ Mix_FreeChunk (snd_axe)
βββ Mix_FreeChunk (snd_flap)
βββ Mix_FreeChunk (snd_spider_attack)
βββ Mix_FreeChunk (snd_dive)
βββ water_cleanup
βββ SDL_DestroyTexture (blue_flame_tex)
βββ SDL_DestroyTexture (axe_trap_tex)
βββ SDL_DestroyTexture (circular_saw_tex)
βββ SDL_DestroyTexture (spike_platform_tex)
βββ SDL_DestroyTexture (spike_tex)
βββ SDL_DestroyTexture (spike_block_tex)
βββ SDL_DestroyTexture (bridge_tex)
βββ SDL_DestroyTexture (float_platform_tex)
βββ SDL_DestroyTexture (rail_tex)
βββ SDL_DestroyTexture (bouncepad_high_tex)
βββ SDL_DestroyTexture (bouncepad_medium_tex)
βββ SDL_DestroyTexture (bouncepad_small_tex)
βββ SDL_DestroyTexture (rope_tex)
βββ SDL_DestroyTexture (ladder_tex)
βββ SDL_DestroyTexture (vine_tex)
βββ last_star_cleanup
βββ SDL_DestroyTexture (yellow_star_tex)
βββ SDL_DestroyTexture (coin_tex)
βββ SDL_DestroyTexture (faster_fish_tex)
βββ SDL_DestroyTexture (fish_tex)
βββ SDL_DestroyTexture (faster_bird_tex)
βββ SDL_DestroyTexture (bird_tex)
βββ SDL_DestroyTexture (jumping_spider_tex)
βββ SDL_DestroyTexture (spider_tex)
βββ SDL_DestroyTexture (platform_tex)
βββ SDL_DestroyTexture (floor_tile)
βββ parallax_cleanup
βββ SDL_DestroyRenderer
βββ SDL_DestroyWindow
β
βββ Mix_CloseAudio
βββ TTF_Quit
βββ IMG_Quit
βββ SDL_Quit
Game Loop
The loop runs at 60 FPS, capped via VSync + a manual SDL_Delay fallback. Each frame has four distinct phases:
while (gs.running) {
1. Delta Time β measure ms since last frame β dt (seconds)
2. Events β SDL_PollEvent (quit / ESC key)
SDL_CONTROLLERDEVICEADDED β opens a newly plugged-in controller
SDL_CONTROLLERDEVICEREMOVED β closes and NULLs gs->controller when unplugged
SDL_CONTROLLERBUTTONDOWN (START) β sets gs->running = 0 to quit
3. Update β player_handle_input β player_update (incl. bouncepad, float-platform, bridge landing)
β bouncepad response (animation + spring sound)
β spiders_update β jumping_spiders_update β birds_update β faster_birds_update
β fish_update β faster_fish_update β spike_blocks_update β spikes_update
β spike_platforms_update β circular_saws_update β axe_traps_update
β blue_flames_update β float_platforms_update β bridges_update
β spider collision β jumping_spider collision β bird collision β faster_bird collision
β fish collision β faster_fish collision β spike_block collision (+ push impulse)
β spike collision β spike_platform collision β circular_saw collision
β axe_trap collision β blue_flame collision
β sea gap fall detection (instant death)
β coinβplayer collision β yellow_starβplayer collision β last_starβplayer collision
β heart/lives/score_life_next logic
β water_update β fog_update β bouncepads_update (small, medium, high)
β debug_update (if --debug)
4. Render β clear β parallax background β platforms β floor tiles
β float platforms β spike rows β spike platforms β bridges
β bouncepads (medium, small, high) β rails
β vines β ladders β ropes β coins β yellow stars β last star
β blue flames β fish β faster fish β water
β spike blocks β axe traps β circular saws
β spiders β jumping spiders β birds β faster birds
β player β fog β hud
β debug overlay (if --debug) β present
}
Delta Time
Uint64 now = SDL_GetTicks64();
float dt = (float)(now - prev) / 1000.0f;
prev = now;
All velocities are expressed in pixels per second. Multiplying by dt (seconds) gives the correct displacement per frame regardless of the actual frame rate.
Render Order (back to front)
| Layer | What | How |
|---|---|---|
| 1 | Background | 7 layers from assets/ (parallax_sky.png, parallax_clouds_bg.png, parallax_glacial_mountains.png, parallax_clouds_mg_1/2/3.png, parallax_cloud_lonely.png) tiled horizontally, each scrolling at a different speed fraction of cam_x |
| 2 | Platforms | platform.png 9-slice tiled pillar stacks (drawn before floor so pillars sink into ground) |
| 3 | Floor | grass_tileset.png 9-slice tiled across world width at FLOOR_Y, with sea-gap openings |
| 4 | Float platforms | float_platform.png 3-slice hovering surfaces (static, crumble, rail modes) |
| 5 | Spike rows | spike.png ground-level spike strips on the floor surface |
| 6 | Spike platforms | spike_platform.png elevated spike hazard surfaces |
| 7 | Bridges | bridge.png tiled crumble walkways |
| 8 | Bouncepads (medium) | bouncepad_medium.png standard-launch spring pads |
| 9 | Bouncepads (small) | bouncepad_small.png low-launch spring pads |
| 10 | Bouncepads (high) | bouncepad_high.png high-launch spring pads |
| 11 | Rails | rail.png bitmask tile tracks for spike blocks and float platforms |
| 12 | Vines | vine.png climbable plant decorations hanging from platforms |
| 13 | Ladders | ladder.png climbable ladder structures |
| 14 | Ropes | rope.png climbable rope segments |
| 15 | Coins | coin.png collectible sprites drawn on top of platforms |
| 16 | Yellow stars | yellow_star.png collectible star pickups |
| 17 | Last star | end-of-level star collectible (uses HUD star sprite) |
| 18 | Blue flames | blue_flame.png animated flame hazards erupting from sea gaps |
| 19 | Fish | fish.png animated jumping enemies, drawn before water for submerged look |
| 20 | Faster fish | faster_fish.png fast aggressive jumping fish enemies |
| 21 | Water | water.png animated scrolling strip at the bottom |
| 22 | Spike blocks | spike_block.png rotating rail-riding hazards |
| 23 | Axe traps | axe_trap.png swinging axe hazards |
| 24 | Circular saws | circular_saw.png spinning blade hazards |
| 25 | Spiders | spider.png animated ground patrol enemies |
| 26 | Jumping spiders | jumping_spider.png animated jumping patrol enemies |
| 27 | Birds | bird.png slow sine-wave sky patrol enemies |
| 28 | Faster birds | faster_bird.png fast aggressive sky patrol enemies |
| 29 | Player | Animated sprite sheet, drawn on top of environment |
| 30 | Fog | fog_background_1.png / fog_background_2.png semi-transparent sliding overlay |
| 31 | HUD | hud_render: hearts, lives, score -- always drawn on top |
| 32 | Debug | debug_render: FPS counter, collision boxes, event log β when --debug active |
Note: Additional foreground layers (fog, water overlays) can be added per-level via
[foreground_layers](/jonathanperis/super-mango-editor/wiki/foreground_layers)in the TOML file, adding up to 8 extra layers above the HUD. The base 32 layers are always present.
Coordinate System
SDL's Y-axis increases downward. The origin (0, 0) is at the top-left of the logical canvas.
(0,0) βββββββββββββββββββΊ x (GAME_W = 400)
β
β LOGICAL CANVAS (400 Γ 300)
β
βΌ
y
(GAME_H = 300)
ββββββββββββββββββββββββββββββββββββββββββββ
β βββββββββ GAME_W (400 px) ββββββββββββββΊ β
FLOOR_Y βββΊβββββββββββββββββββββββββββββββββββββββββββ
(300-48=252)β Grass Tileset (48px tall) β
ββββββββββββββββββββββββββββββββββββββββββββ
SDL_RenderSetLogicalSize(renderer, 400, 300) makes SDL scale this canvas 2x to fill the 800x600 OS window automatically, giving the chunky pixel-art look with no changes to game logic.
GameState Struct
Defined in game.h. The single container for every runtime resource.
typedef struct {
SDL_Window *window; // OS window handle
SDL_Renderer *renderer; // GPU drawing context
SDL_GameController *controller; // first connected gamepad; NULL = none
ParallaxSystem parallax; // multi-layer scrolling background
SDL_Texture *floor_tile; // grass_tileset.png (GPU)
SDL_Texture *platform_tex; // Shared tile for platform pillars (GPU)
SDL_Texture *spider_tex; // Shared texture for all spiders (GPU)
Spider spiders[MAX_SPIDERS];
int spider_count;
SDL_Texture *jumping_spider_tex; // Shared texture for jumping spiders (GPU)
JumpingSpider jumping_spiders[MAX_JUMPING_SPIDERS];
int jumping_spider_count;
SDL_Texture *bird_tex; // Shared texture for Bird enemies (GPU)
Bird birds[MAX_BIRDS];
int bird_count;
SDL_Texture *faster_bird_tex; // Shared texture for FasterBird (GPU)
FasterBird faster_birds[MAX_FASTER_BIRDS];
int faster_bird_count;
SDL_Texture *fish_tex; // Shared texture for all fish enemies (GPU)
Fish fish[MAX_FISH];
int fish_count;
SDL_Texture *faster_fish_tex; // Shared texture for faster fish enemies (GPU)
FasterFish faster_fish[MAX_FASTER_FISH];
int faster_fish_count;
SDL_Texture *coin_tex; // Shared texture for all coin collectibles (GPU)
Coin coins[MAX_COINS];
int coin_count;
SDL_Texture *yellow_star_tex; // Shared texture for yellow star pickups (GPU)
YellowStar yellow_stars[MAX_YELLOW_STARS];
int yellow_star_count;
LastStar last_star; // Special end-of-level star collectible
SDL_Texture *vine_tex; // Shared texture for vine decorations (GPU)
VineDecor vines[MAX_VINES];
int vine_count;
SDL_Texture *ladder_tex; // Shared texture for ladders (GPU)
LadderDecor ladders[MAX_LADDERS];
int ladder_count;
SDL_Texture *rope_tex; // Shared texture for ropes (GPU)
RopeDecor ropes[MAX_ROPES];
int rope_count;
SDL_Texture *bouncepad_small_tex; // Shared texture for small bouncepads (GPU)
Bouncepad bouncepads_small[MAX_BOUNCEPADS_SMALL];
int bouncepad_small_count;
SDL_Texture *bouncepad_medium_tex; // Shared texture for medium bouncepads (GPU)
Bouncepad bouncepads_medium[MAX_BOUNCEPADS_MEDIUM];
int bouncepad_medium_count;
SDL_Texture *bouncepad_high_tex; // Shared texture for high bouncepads (GPU)
Bouncepad bouncepads_high[MAX_BOUNCEPADS_HIGH];
int bouncepad_high_count;
SDL_Texture *rail_tex; // Shared texture for all rail tiles (GPU)
Rail rails[MAX_RAILS];
int rail_count;
SDL_Texture *spike_block_tex; // Shared texture for spike blocks (GPU)
SpikeBlock spike_blocks[MAX_SPIKE_BLOCKS];
int spike_block_count;
SDL_Texture *spike_tex; // Shared texture for ground spikes (GPU)
SpikeRow spike_rows[MAX_SPIKE_ROWS];
int spike_row_count;
SDL_Texture *spike_platform_tex; // Shared texture for spike platforms (GPU)
SpikePlatform spike_platforms[MAX_SPIKE_PLATFORMS];
int spike_platform_count;
SDL_Texture *circular_saw_tex; // Shared texture for circular saws (GPU)
CircularSaw circular_saws[MAX_CIRCULAR_SAWS];
int circular_saw_count;
SDL_Texture *axe_trap_tex; // Shared texture for axe traps (GPU)
AxeTrap axe_traps[MAX_AXE_TRAPS];
int axe_trap_count;
SDL_Texture *blue_flame_tex; // Shared texture for blue flames (GPU)
BlueFlame blue_flames[MAX_BLUE_FLAMES];
int blue_flame_count;
SDL_Texture *float_platform_tex; // float_platform.png 3-slice (GPU)
FloatPlatform float_platforms[MAX_FLOAT_PLATFORMS];
int float_platform_count;
SDL_Texture *bridge_tex; // bridge.png tile (GPU)
Bridge bridges[MAX_BRIDGES];
int bridge_count;
Mix_Chunk *snd_jump; // Player jump sound effect (WAV)
Mix_Chunk *snd_coin; // Coin collect sound effect (WAV)
Mix_Chunk *snd_hit; // Player hurt sound effect (WAV)
Mix_Chunk *snd_spring; // Bouncepad spring sound effect (WAV)
Mix_Chunk *snd_axe; // Axe trap swing sound effect (WAV)
Mix_Chunk *snd_flap; // Bird flap sound effect (WAV)
Mix_Chunk *snd_spider_attack;// Spider attack sound effect (WAV)
Mix_Chunk *snd_dive; // Fish dive sound effect (WAV)
Mix_Music *music; // Background music stream (WAV)
Player player; // Player data β stored by value
Platform platforms[MAX_PLATFORMS];
int platform_count;
Water water; // Animated water strip at the bottom
FogSystem fog; // Atmospheric fog overlay β topmost layer
int sea_gaps[MAX_SEA_GAPS];
int sea_gap_count;
Hud hud; // HUD display: hearts, lives, score
int hearts; // Current hit points (0-MAX_HEARTS)
int lives; // Remaining lives; <0 triggers game over
int score; // Cumulative score from collecting coins/stars
int score_life_next; // Score threshold for next bonus life
Camera camera; // Viewport scroll position; updated every frame
int running; // Loop flag: 1 = keep going, 0 = quit
int paused; // 1 = window lost focus; physics/music frozen
int debug_mode; // 1 = debug overlays active (--debug flag)
DebugOverlay debug; // FPS counter, collision vis, event log
// ---- Loop state (persists across frames for emscripten callback) ----
Uint64 loop_prev_ticks; // timestamp of previous frame
int fp_prev_riding; // float platform player stood on last frame
} GameState;
Key design decisions:
Playeris embedded by value, not a pointer. This avoids a heap allocation and keeps the struct self-contained. The same applies toPlatform,Water,FogSystem, and all entity arrays.- Every pointer is set to
NULLafter freeing, making accidental double-frees safe. - Initialised with
GameState gs = {0}so every field starts as0/NULL.
Error Handling Strategy
| Situation | Action |
|---|---|
SDL subsystem init failure (in main) |
fprintf(stderr, ...) β clean up already-inited subsystems β return EXIT_FAILURE |
Resource load failure (in game_init) |
fprintf(stderr, ...) β destroy already-created resources β exit(EXIT_FAILURE) |
| Sound load failure (non-fatal pattern) | fprintf(stderr, ...) then continue -- play is guarded by if (snd_jump) |
| Optional texture load failure (non-fatal) | fprintf(stderr, ...) then continue -- render is guarded by if (texture) |
All SDL error strings are retrieved with SDL_GetError(), IMG_GetError(), or Mix_GetError() and printed to stderr.