29 KiB
Arquitectura de Coffee Crisis Arcade Edition
Guía de orientación para un desarrollador nuevo en el proyecto.
Cada afirmación está anclada a código real: se cita el fichero (y, cuando ayuda, la función o el número de línea) que la respalda. Donde no he encontrado algo, lo digo explícitamente en lugar de inventarlo.
Coffee Crisis Arcade Edition es un shooter arcade cooperativo de 2 jugadores escrito en C++20 sobre SDL3: los jugadores defienden el café de globos gigantes. Apunta a Windows, Linux, macOS (Intel/Apple Silicon), Raspberry Pi, Anbernic y web (Emscripten). Los comentarios del código están en español/valenciano; este documento está en castellano.
Índice
- Visión general
- Punto de entrada y bucle principal
- Secciones y flujo de la aplicación
- Renderizado: de la lógica al píxel
- Entrada
- Lógica del juego: la clase
Game - Entidades y managers de gameplay
- Modo demo y attract mode
- Recursos
- Audio
- Configuración, parámetros y constantes
- Localización
- Convenciones y patrones recurrentes
- Guía de navegación: "si quieres tocar X, mira Y"
1. Visión general
El árbol source/ separa motor y juego:
source/core/— motor genérico:system(arranque, secciones, demo, eventos globales),rendering(+sdl3gpu,sprite),input,resources,audio,locale.source/game/— el juego concreto:scenes(las secciones),gameplay(managers),entities(jugador, globos, balas, ítems…),ui, yoptions.source/utils/—color,param,utils,defines.source/external/— vendorizado:nlohmann/json,fkyaml,stb_image,stb_vorbis.
Los #include son absolutos respecto a source/ (p.ej.
#include "core/audio/audio.hpp"); CMake añade un único -I.../source
(ver CLAUDE.md). ~150 ficheros C++, ~32.000 líneas.
Cuatro ideas-fuerza que conviene interiorizar:
- El flujo de la aplicación se conduce con una variable global
(
Section::name), no con objetos de transición. ElDirectorreacciona a sus cambios (§3). - El render usa texturas GPU vía
SDL_Rendererdibujadas sobre una render-target texture, con post-procesado opcional vía un backend SDL3 GPU. No es un blitter de software (§4). - El gameplay se organiza con managers/pools (
BalloonManager,BulletManager,StageManager) coordinados por una claseGamemuy grande (§6, §7). - Sí hay modo demo (attract mode): es reproducción de input grabado, no IA (§8).
graph TD
SDL[SDL3 callbacks · main.cpp] --> DIR[Director]
DIR -->|lee Section::name| ST{handleSectionTransition}
ST --> SEC["Section activa (Logo/Intro/Title/Game/…)"]
DIR --> GE[GlobalEvents] --> INP[Input] & SVC[ServiceMenu]
SEC --> GAME[Game]
GAME --> BM[BalloonManager] & BLM[BulletManager] & STG[StageManager]
GAME --> PL[Players] -->|Input::Action| INP
GAME --> DEMO["Demo (playback de input grabado)"] -.->|setInput| PL
GAME -->|SDL_RenderTexture| CV["game_canvas_ (render target)"]
CV -->|RenderReadPixels → uploadPixels| SB[ShaderBackend SDL3 GPU] --> WIN[Ventana]
RES["Resource / Asset"] -.-> GAME & SEC
2. Punto de entrada y bucle principal
2.1. SDL conduce el bucle (callbacks)
source/main.cpp define SDL_MAIN_USE_CALLBACKS: no hay while propio. SDL
llama a cuatro funciones, todas delegando en el Director:
SDL_AppInit → *appstate = new Director(argc, argv); // main.cpp:15
SDL_AppIterate→ Director::iterate(); // un frame
SDL_AppEvent → Director::handleEvent(event); // un evento
SDL_AppQuit → delete Director;
2.2. El Director
source/core/system/director.{hpp,cpp} es el orquestador. Mantiene un
unique_ptr por cada sección (preload_, logo_, intro_, title_,
game_, …) de los que solo uno está vivo a la vez (director.hpp:60).
Constructor (director.cpp:82): fija la semilla aleatoria, crea la carpeta
de sistema (jailgames/coffee_crisis_arcade_edition), decide la sección
inicial según el build (RECORDING → GAME; _DEBUG → lee debug.yaml; Release
→ LOGO) y llama a init().
init() (director.cpp:131) inicializa en orden: Asset (índice de
ficheros), sistema de recursos (resources.pack, con/ sin fallback a disco
según build), Input, Options (config.yaml, controllers.json, presets de
shaders), parámetros y scores, Lang, Screen, Audio y Resource.
2.3. Arranque NO bloqueante (PRELOAD incremental)
Un detalle importante: si el modo de carga es PRELOAD, no se cargan todos
los recursos de golpe. init() redirige el arranque a la sección PRELOAD
guardando el destino real en Section::post_preload (director.cpp:191):
Section::post_preload = Section::name; // destino real
Section::name = Section::Name::PRELOAD; // mientras tanto, barra de progreso
Resource::get()->beginLoad();
Cada frame, Director::iterate() (director.cpp:489) llama a
Resource::loadStep(50 /*ms*/): carga recursos hasta agotar un presupuesto de
50 ms por frame, manteniendo la ventana y el bucle vivos. Cuando termina,
finishBoot() inicializa lo que depende de los recursos (ServiceMenu,
Notifier, singletons de Screen) y salta a post_preload.
2.4. Orden del frame y gestión del tiempo
Director::iterate() (director.cpp:489):
sequenceDiagram
participant SDL
participant Dir as Director::iterate
participant Sec as Section activa
SDL->>Dir: SDL_AppIterate
alt boot_loading_
Dir->>Dir: Resource::loadStep(50ms) → finishBoot() al terminar
end
Dir->>Dir: handleSectionTransition() (destruye/crea sección)
Dir->>Sec: iterate() (la sección hace su propio update+render)
El tiempo es time-based: cada sección calcula su propio delta_time. En
Game es calculateDeltaTime() (game.hpp:210), usado en todos los update.
Los eventos llegan por separado vía Director::handleEvent →
GlobalEvents::handle + reenvío a la sección activa (director.cpp:539).
2.5. Reinicio en caliente
Igual que su proyecto hermano, soporta reinicio real vía execv
(Director::relaunch(), director.cpp:254): la sección RESET (p.ej. tras
F10 o cambio de idioma) reemplaza el proceso por sí mismo; si no se puede
(Emscripten, argv inválido), cae a un reset() interno que recarga recursos y
vuelve a LOGO.
3. Secciones y flujo de la aplicación
3.1. Variables globales de estado
source/core/system/section.hpp define el estado del flujo como variables
globales inline en el namespace Section:
inline Name name = Name::RESET; // sección a la que ir
inline Name post_preload = Name::LOGO; // destino tras PRELOAD
inline Options options = Options::NONE; // 1P/2P/BOTH, timeouts, etc.
inline AttractMode attract_mode = AttractMode::TITLE_TO_DEMO;
Name enumera: RESET, PRELOAD, LOGO, INTRO, TITLE, GAME, HI_SCORE_TABLE, GAME_DEMO, INSTRUCTIONS, CREDITS, QUIT. Cualquier parte del código cambia el
flujo simplemente asignando Section::name = ....
3.2. La transición de secciones
Director::handleSectionTransition() (director.cpp:390) se ejecuta cada
frame:
- Si
Section::name == RESET: intentarelaunch(); si vuelve, hace el reset interno. - Si
Section::name == last_built_section_name_: no hace nada (ya está viva). - Si cambió:
resetActiveSection()(libera todos losunique_ptr) y unswitchconstruye la nueva sección. ParaGAMEtraduceSection::optionsaPlayer::Id(1P/2P/BOTH); paraGAME_DEMOconstruyeGame(..., DEMO_ON)con jugador aleatorio (director.cpp:448).
Cada sección (source/game/scenes/) es autónoma y expone iterate() +
handleEvent(). Director::iterate despacha al puntero vivo con una cadena de
if/else if (director.cpp:517).
graph LR
RESET --> PRELOAD --> LOGO --> INTRO --> TITLE
TITLE -->|jugar| GAME --> HI_SCORE_TABLE --> TITLE
TITLE -->|timeout / attract| GAME_DEMO --> TITLE
TITLE -->|attract alterno| LOGO
TITLE --> INSTRUCTIONS & CREDITS
3.3. Attract mode
El attract mode alterna entre mostrar la demo y volver al logo. En
title.cpp:51 el Title, al construirse, mira Section::attract_mode y fija su
next_section_ (a GAME_DEMO o LOGO), invirtiendo el modo para la
próxima vez. Cuando el Title agota su timeout sin input, salta a esa sección
(ver §8).
4. Renderizado: de la lógica al píxel
A diferencia de un blitter de software, aquí los sprites son texturas GPU
compuestas por SDL_Renderer, y el post-procesado se hace en un backend SDL3
GPU.
4.1. Texture y la jerarquía de sprites
source/core/rendering/texture.hpp—Textureenvuelve unSDL_Texture*, conloadFromFile,createBlank,setAsRenderTargety unrender(x, y, clip, zoom, angle, …). Es la unidad de dibujo.source/core/rendering/sprite/— jerarquía sobreSprite(sprite.hpp:13), que guarda varias texturas (textures_), unsprite_clip_y posición/zoom:AnimatedSprite— animación por fotogramas (clip que avanza).MovingSprite— con velocidad.PathSprite— sigue rutas precalculadas (Path).SmartSprite— sprite con lógica propia (p.ej. el café que salta al ser golpeado).CardSprite— variante para "cartas"/paneles.
El Player, por ejemplo, compone un AnimatedSprite player_sprite_ y un
power_sprite_ para el aura (player.hpp:250).
4.2. Dos render-targets
Hay dos texturas de destino encadenadas:
-
Game::canvas_— textura de la zona de juego.Game::fillCanvas()(game.cpp) fija el render-target acanvas_y dibuja, en este orden:background → smart_sprites → items → balloons → tabe → players → bullets → path_sprites. Ese orden es el z-order del playfield. -
Screen::game_canvas_— textura de pantalla completa (ARGB8888 a la resolución de juego;screen.cpp:92,SDL_TEXTUREACCESS_TARGET).Game::render()hacescreen_->start()(que pone el target engame_canvas_), copiacanvas_sobre él, y encima dibuja scoreboard y los fades de entrada/salida; finalmentescreen_->render().
4.3. Post-procesado y presentación
Screen::renderPresent() (screen.cpp:156) decide cómo llega a la ventana:
- Con backend GPU acelerado: lee los píxeles de
game_canvas_conSDL_RenderReadPixelsa unpixel_buffer_CPU, los sube al backend (shader_backend_->uploadPixels(...)) y este los renderiza con el shader activo (shader_backend_->render()). - Sin backend (fallback): vuelca
game_canvas_directamente a la ventana conSDL_RenderTexture.
El backend vive en source/core/rendering/sdl3gpu/ (SDL3 GPU API; shaders
SPIR-V compilados offline desde GLSL en data/shaders/, embebidos en cabeceras
spv//msl/). Hay dos shaders seleccionables, PostFX y CrtPi, con
presets en postfx.yaml/crtpi.yaml (screen.cpp:382). El build NO_SHADERS
(Anbernic) desactiva todo el pipeline de shaders.
graph TD
OBJ["background, balloons, players, bullets…"] -->|SDL_RenderTexture| CANVAS["Game::canvas_ (zona de juego)"]
CANVAS -->|copiar| GC["Screen::game_canvas_ (pantalla)"]
SB2[scoreboard] --> GC
FADE[fades de entrada/salida] --> GC
GC -->|RenderReadPixels → uploadPixels| SHADER["ShaderBackend (PostFX/CrtPi)"]
SHADER --> WIN[Ventana]
GC -.fallback sin GPU.-> WIN
4.4. Efectos de pantalla
Screen integra efectos globales sobre la presentación: shake
(ShakeEffect, desplaza el rect), flash (FlashEffect, tinte temporal) y
attenuate (atenuación) — todos definidos como structs internos en
screen.hpp:108–205. Las transiciones entre estados usan
source/core/rendering/fade.* (cuadrícula de cuadros).
5. Entrada
5.1. Input singleton
source/core/input/input.hpp. Centraliza teclado y mandos bajo un enum de
acciones (Input::Action, alias de InputAction). El mapeo por defecto
vive en structs:
Keyboard(input.hpp:54): flechas = mover; Q/W/E = disparar izquierda/centro/derecha; Enter = START; F12 = SERVICE; P = PAUSE; ESC = EXIT; F1–F11 = ventana/vídeo/audio/idioma/reset/info.Gamepad(input.hpp:99): cruceta = mover; WEST/NORTH/EAST = disparar; START/ BACK = start/service. Cada mando es unshared_ptr<Gamepad>con su propiobindings, nombre y path.
La consulta principal es checkAction(action, repeat, check_keyboard, gamepad)
(input.hpp:168). Hay además detección de ejes y triggers como botones, y
gestión de configuraciones de mando persistidas (gamepad_config_manager,
controllers.json).
5.2. Eventos globales y hotkeys
source/core/system/global_events.cpp—GlobalEvents::handle(event)trata lo común a cualquier sección:SDL_EVENT_QUIT, resize, render target reset, hot-plug de mandos (con notificación trasmarkStartupComplete), el toggle del menú de servicio y el ratón.source/core/input/global_inputs.cpp— hotkeys de sistema, despachadas con un mapa acción→lambda (global_inputs.cpp:187): fullscreen, zoom ±, audio, autofire, idioma (CHANGE_LANG →Section::RESET, reinicia), VSync, integer scale, info; más PostFX/shader/preset. F10/RESET poneSection::name = RESET(reinicio) y ESC/EXIT poneQUIT.
5.3. Cómo llega la entrada a un jugador
Dentro de Game, checkInput() → handlePlayersInput() →
handleNormalPlayerInput(player) consulta Input y traduce a
player->setInput(Input::Action) y disparos (handleFireInput). El Player
sabe si usa teclado o un mando concreto (uses_keyboard_, gamepad_;
player.hpp:217). En modo demo, esta misma vía se alimenta de datos grabados
(§8).
6. Lógica del juego: la clase Game
source/game/scenes/game.{hpp,cpp} es la sección de gameplay y la clase más
grande del proyecto. Coordina jugadores, globos, balas, ítems, fases,
puntuación y efectos.
6.1. FSM de la partida
Game::State (game.hpp:75): FADE_IN → ENTERING_PLAYER → SHOWING_GET_READY_MESSAGE → PLAYING → COMPLETED / GAME_OVER. checkState()
(game.cpp) detecta fin de juego (stage_manager_->isGameCompleted()) o game
over (allPlayersAreGameOver()); setState() hace la transición.
6.2. El frame de Game
void Game::iterate() { // game.cpp
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
render();
}
void Game::update(float dt) {
screen_->update(dt);
Audio::update();
updateDemo(dt); // no-op si no es demo
updateGameStates(dt); // dispatch por State
fillCanvas(); // dibuja la zona de juego en canvas_
}
updateGameStates despacha a un updateGameState<Estado> por cada valor de la
FSM (game.hpp:218).
6.3. Sistemas que orquesta
Game posee (vía unique_ptr) los managers y objetos del nivel
(game.hpp:136):
StageManager— progresión por "poder": cada fase necesitapower_to_complete; el juego se completa al acumular el poder total. También define umbrales de amenaza (menace) por fase. ImplementaIStageInfo(stage.hpp:51), interfaz mínima que se inyecta a jugador y globos.BalloonManager— despliega formaciones (deployRandomFormation), globos hijos al explotar uno, power balls, ajusta velocidad (Balloon::GAME_TEMPO) y calcula la amenaza en pantalla (§7).BulletManager— pool de balas; las colisiones se resuelven con callbacks que registraGame(setBalloonCollisionCallback,setTabeCollisionCallback,setOutOfBoundsCallback;bullet_manager.hpp:49), manteniendo la lógica de juego dentro deGame.Background,Fade×2,Tabe(enemigo especial volador),Scoreboard,PauseManager.- Listas de
Item(power-ups y puntos),SmartSpriteyPathSprite.
6.4. Mecánicas destacadas
- Ítems / power-ups (
game.hpp:268): café (toque extra), máquina de café (power-up), power ball, reloj (= detener el tiempo,enableTimeStopItem), e ítems de puntos. La probabilidad de soltar ítem la decidedropItem()con odds configurables (Helper,game.hpp:100). - Sistema de amenaza (
updateMenace/setMenace): si la amenaza cae por debajo de un umbral, se generan más globos. - Multijugador:
players_es unvector<shared_ptr<Player>>(1 o 2). Unplayers_draw_list_separado mantiene el z-order de dibujo (buildPlayerDrawList,sendPlayerToBack/bringPlayerToFront;game.hpp:338). - Récords: al perder con buena puntuación, el jugador entra en
ENTERING_NAME(enter_name.*,manage_hiscore_table.*).
7. Entidades y managers de gameplay
source/game/entities/:
Player(player.hpp) — la entidad más compleja. Hereda deAnimatedSprite. Tiene tres ejes de estado independientes:walking_state_,firing_state_yplaying_state_(player.hpp:266). El disparo usa un sistema de dos líneas: una funcional (cooldown,canFire) y otra visual (animacionesNORMAL→AIMING→RECOILING→THREAT_POSE,player.hpp:296). Soporta power-up, invulnerabilidad con parpadeo, continue y entrada de nombre. Puede controlarse por teclado o por unGamepadconcreto.Balloon(balloon.hpp) — el enemigo básico; al explotar puede crear globos hijos.Balloon::GAME_TEMPOdefine velocidades por progreso.Bullet(bullet.hpp) — proyectil con tipo (UP/LEFT/RIGHT) y color (según power-up del jugador).Item— power-ups y objetos de puntos que caen.Explosions— efectos de explosión (lo poseeBalloonManager).Tabe— enemigo/objeto especial volador con su propia lógica de impacto.
source/game/gameplay/ contiene los managers y sistemas (no entidades en
sí): BalloonManager, BulletManager, StageManager, Difficulty,
Scoreboard, balloon_formations (lee formations.txt), cooldown,
enter_name, manage_hiscore_table, game_logo.
El patrón general aquí no es un EntityManager polimórfico único (como en
el proyecto hermano), sino managers especializados por tipo + listas
homogéneas en Game, con callbacks para cruzar responsabilidades sin
acoplar (caso de BulletManager).
8. Modo demo y attract mode
A diferencia de muchos juegos, aquí el modo demo SÍ existe — pero NO es IA. Es reproducción de input pregrabado.
8.1. Formato de los datos
source/core/system/demo.hpp: cada fotograma de demo es un DemoKeys con seis
banderas (left, right, no_input, fire, fire_left, fire_right). Una
demo es un vector<DemoKeys> de TOTAL_DEMO_DATA = 2000 fotogramas. Hay tres
ficheros: data/demo/demo{1,2,3}.bin, leídos por loadDemoDataFromFile
(demo.cpp:11).
8.2. Reproducción
Cuando Game se construye con DEMO_ON (sección GAME_DEMO), initDemo()
(game.cpp) carga todas las demos del Asset y asigna una a cada jugador de
forma aleatoria (shuffle), elige una fase de inicio al azar, da cafés
aleatorios, pone los marcadores en modo DEMO y silencia los globos. Luego cada
frame, demoHandlePlayerInput() lee el fotograma actual y lo inyecta por la
misma vía de input que un humano:
// game.cpp — demoHandlePlayerInput
const auto& demo_data = demo_data_vec.at(demo_.index % demo_data_vec.size());
if (demo_data.left == 1) player->setInput(Input::Action::LEFT);
else if (demo_data.right == 1) player->setInput(Input::Action::RIGHT);
...
if (demo_data.fire == 1) handleFireInput(player, Bullet::Type::UP);
No hay toma de decisiones: el jugador "demo" repite exactamente las pulsaciones
grabadas. demoHandlePassInput() permite salir con cualquier botón,
volviendo a TITLE y dejando armado el siguiente attract (TITLE_TO_DEMO).
8.3. Attract mode
El Title alterna TITLE_TO_DEMO ↔ TITLE_TO_LOGO (title.cpp:51): tras su
timeout, una vez lanza la demo y la siguiente vuelve al logo, en bucle de
atracción típico de recreativa.
8.4. Grabación y autoplay
#ifdef RECORDING(demo.cpp:43,game.hpp:349): build especial que arranca directamente en GAME y graba las pulsaciones a un.binconsaveDemoFile. Así se generan las demos.autoplay(debug): en_DEBUG,debug_config.autoplaypermite alimentar datos de demo a los jugadores en una partida normal (game.hpp:354,initDemo).
9. Recursos
9.1. Resource (singleton)
source/core/resources/resource.hpp. Cachea por nombre texturas, música,
sonidos, ficheros de texto/fuentes, animaciones y datos de demo
(getDemoData). Dos modos (LoadingMode):
PRELOAD— carga incremental al arranque conbeginLoad()+loadStep(budget_ms)+renderProgress()(la sección Preload pinta la barra). Es el modo por defecto.LAZY_LOAD— carga bajo demanda (seleccionable víadebug.yaml).
9.2. Asset y el pack
source/core/resources/asset.* mantiene el índice de ficheros a partir del
manifiesto data/config/assets.txt (director.cpp:296) y valida que no falte
ninguno (Asset::check()). La lectura física pasa por
ResourceHelper::loadFile, que sirve desde resources.pack y hace
fallback al filesystem en builds de desarrollo (en Release nativo es estricto:
solo el pack; director.cpp:143). resource_pack.* y resource_loader.*
implementan el formato del pack y la carga.
Formatos: imágenes vía stb_image + gif propio; audio .ogg vía
stb_vorbis; texto/JSON vía nlohmann/json; configs vía fkyaml.
10. Audio
source/core/audio/audio.hpp — Audio singleton sobre jail_audio
(primer-party, no librería externa) y audio_adapter. API: playMusic(name, loop, crossfade_ms), playSound(name, Group), control de volumen por grupos
(Group::GAME, INTERFACE, …), pause/resume/stop, crossfade y fade out
(audio.hpp:49). Audio::update() se llama cada frame desde las secciones
(p.ej. Game::update). El build NO_AUDIO compila sin audio.
11. Configuración, parámetros y constantes
El proyecto tiene tres capas de configuración que conviene no confundir:
Options(source/game/options.hpp) — opciones persistentes del usuario, agrupadas en structs (Window,Video,Audio,Settings,Gamepad,Keyboard) más presets de shaders (PostFXPreset,CrtPiPreset). Se guardan enconfig.yaml/controllers.json/postfx.yaml/crtpi.yaml. Incluyeparams_preset∈ {classic,arcade,red} (options.hpp:339).param/Param(source/utils/param.hpp) — parámetros de gameplay y layout (dimensiones del juego,play_area, ajustes de globos, scoreboard, título, fades, jugador, Tabe…). Se cargan de ficheros de textoparam_320x240.txt/param_320x256.txt/classic.txtconloadParamsFromFile(director.cpp:275). El objeto globalparamlo leen casi todos los subsistemas.- Otros datos de juego:
stages.txt(fases, lo leeStageManager),formations.txt(formaciones de globos),assets.txt(manifiesto).
Los valores de fallback compilados viven en
source/core/system/defaults.hpp (y source/utils/defines.hpp para constantes
base). En Debug, debug.yaml controla sección inicial, opciones, fase, modo de
carga, autoplay e invincibility (director.cpp:318).
Builds condicionales (multiplataforma)
El juego se adapta por defines (ver CLAUDE.md): WINDOWS_BUILD /
LINUX_BUILD / MACOS_BUILD, RELEASE_BUILD, MACOS_BUNDLE, ANBERNIC
(handheld), __EMSCRIPTEN__ (web), NO_SHADERS, NO_AUDIO, RECORDING,
ARCADE, DEBUG/VERBOSE. Aparecen sobre todo en Director::init y Screen.
12. Localización
source/core/locale/lang.* — Lang carga el idioma desde JSON en
data/lang/. Lang::setLanguage(Options::settings.language) se llama en
init() y en cada reset(). Cambiar idioma en caliente (F9 / CHANGE_LANG)
fuerza un Section::RESET (reinicio del proceso) para recargarlo todo
(global_inputs.cpp).
13. Convenciones y patrones recurrentes
- Naming (de
.clang-format/.clang-tidy, resumido enCLAUDE.md): clasesCamelCase, métodoscamelBack, variables/paramssnake_case, miembros privadossnake_case_(guion bajo final), constantesUPPER_CASE, namespacesCamelCase, valores de enumUPPER_CASE. clang-tidy trata los warnings como errores. - Singletons con init/destroy manual:
Screen,Resource,Audio,Input,Asset,ServiceMenu,Notifier,Lang. Se crean/destruyen en orden explícito enDirector::init/shutdownSubsystems(director.cpp:227) — no se confía en destructores estáticos. - Estado de flujo por variable global (
Section::name): patrón central; no hay objetos de transición tipados. - Time-based en todo: cada sistema recibe
delta_time; las constantes de tiempo se documentan como "N frames a 60fps → segundos" (p.ej.game.hpp:85). - Callbacks para desacoplar:
BulletManageryStageManagerrecibenstd::functiondesdeGamepara no conocer la lógica de colisión/puntuación. - Interfaz mínima
IStageInfo(stage_interface.hpp): se inyecta a jugador y globos para que consulten amenaza/poder sin ver todo elStageManager. - Managers por tipo + listas homogéneas en
Game, en vez de un contenedor de entidades polimórfico. - Comentarios en español/valenciano; muchos
#includellevan comentario "// Para X" indicando qué símbolo justifican (estilo IWYU). - Multiplataforma por
#ifdef(ver §11). Conviene leerlos al tocar arranque o render.
14. Guía de navegación: "si quieres tocar X, mira Y"
| Quiero… | Empieza por… |
|---|---|
| Entender el arranque | source/core/system/director.cpp (init, iterate) |
| Cambiar el flujo de pantallas | source/core/system/section.hpp + handleSectionTransition |
| Añadir/editar una pantalla | source/game/scenes/ (crea iterate()+handleEvent()) y un case en director.cpp |
| La barra de carga / arranque lento | Resource::beginLoad/loadStep + scenes/preload.* |
| Cómo se dibuja todo | Game::fillCanvas + Game::render + Screen::renderPresent (screen.cpp:156) |
| Sprites / animaciones | source/core/rendering/sprite/ + texture.hpp |
| Shaders / CRT / post-FX | source/core/rendering/sdl3gpu/ + data/shaders/ + Options presets |
| Efectos (shake/flash/fade) | screen.hpp (structs) + core/rendering/fade.* |
| Controles / mandos | source/core/input/input.hpp (Keyboard/Gamepad/checkAction) |
| Hotkeys F1–F12 / reset | source/core/input/global_inputs.cpp |
| Eventos globales / hot-plug | source/core/system/global_events.cpp |
| Lógica de partida y estados | source/game/scenes/game.cpp (updateGameState*) |
| Globos y formaciones | gameplay/balloon_manager.* + entities/balloon.* + formations.txt |
| Balas y colisiones | gameplay/bullet_manager.* (callbacks) + entities/bullet.* |
| Fases / progresión / dificultad | gameplay/stage.* (StageManager) + stages.txt + gameplay/difficulty.* |
| El jugador (movimiento, disparo) | entities/player.* (sistema de disparo de dos líneas) |
| Ítems y power-ups | entities/item.* + Game::dropItem/createItem |
| Récords / entrada de nombre | gameplay/manage_hiscore_table.*, gameplay/enter_name.* |
| Modo demo / attract | core/system/demo.*, Game::initDemo/demoHandle*, scenes/title.cpp |
| Grabar nuevas demos | build con RECORDING + Game::updateRecording |
| Cargar un recurso | core/resources/resource.hpp + asset.* + assets.txt |
| Audio (música/SFX) | core/audio/audio.hpp |
| Opciones del usuario | game/options.hpp (+ config.yaml) |
| Parámetros de gameplay/layout | utils/param.hpp + param_320x*.txt |
| Valores por defecto | core/system/defaults.hpp, utils/defines.hpp |
| Idiomas | core/locale/lang.* + data/lang/ |
| Menú de servicio / notificaciones | game/ui/service_menu.*, game/ui/notifier.* |
| Empaquetar datos | tools/ (pack_resources) + make resources.pack |
Documento generado a partir de la lectura directa del código en el commit
actual de la rama main. Si algo aquí no cuadra con el código, el código
manda: actualiza este documento.