merge docs/arquitectura: guia d'arquitectura del projecte
This commit is contained in:
@@ -0,0 +1,553 @@
|
||||
# Arquitectura de **JailDoctor's Dilemma**
|
||||
|
||||
> 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 el código
|
||||
> contradice a la documentación previa (`CLAUDE.md`), lo señalo: **manda el
|
||||
> código**.
|
||||
>
|
||||
> **JailDoctor's Dilemma** es un *puzzle-platformer* 2D retro en C++20 + SDL3:
|
||||
> 60+ habitaciones interconectadas, ítems coleccionables, enemigos y logros.
|
||||
> Resolución de juego **256×192**. Los comentarios del código están en
|
||||
> español/valenciano; este documento está en castellano.
|
||||
|
||||
---
|
||||
|
||||
## Índice
|
||||
|
||||
1. [Visión general](#1-visión-general)
|
||||
2. [Punto de entrada y bucle principal](#2-punto-de-entrada-y-bucle-principal)
|
||||
3. [Escenas y flujo de la aplicación](#3-escenas-y-flujo-de-la-aplicación)
|
||||
4. [Renderizado: de la lógica al píxel](#4-renderizado-de-la-lógica-al-píxel)
|
||||
5. [Entrada](#5-entrada)
|
||||
6. [Lógica del juego: la escena `Game`](#6-lógica-del-juego-la-escena-game)
|
||||
7. [Habitaciones y colisión](#7-habitaciones-y-colisión)
|
||||
8. [Entidades](#8-entidades)
|
||||
9. [Logros, estadísticas y marcador](#9-logros-estadísticas-y-marcador)
|
||||
10. [Editor de mapas (Debug)](#10-editor-de-mapas-debug)
|
||||
11. [Consola y notificaciones](#11-consola-y-notificaciones)
|
||||
12. [Modo demo](#12-modo-demo)
|
||||
13. [Recursos](#13-recursos)
|
||||
14. [Audio, localización y configuración](#14-audio-localización-y-configuración)
|
||||
15. [Convenciones y patrones recurrentes](#15-convenciones-y-patrones-recurrentes)
|
||||
16. [Guía de navegación: "si quieres tocar X, mira Y"](#16-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` (`director`, `debug`,
|
||||
`global_events`), `rendering` (+ `sprite`, `sdl3gpu`), `input`, `resources`,
|
||||
`audio`, `locale`.
|
||||
- **`source/game/`** — el juego concreto: `scenes/`, `gameplay/`, `entities/`,
|
||||
`editor/`, `ui/`, `options.*`, `scene_manager.hpp`, `defaults.hpp`.
|
||||
- **`source/utils/`** — `delta_timer`, `easing_functions`, `utils`, `defines`.
|
||||
- **`source/external/`** — vendorizado: `fkyaml`, `stb_image`, `stb_vorbis`.
|
||||
|
||||
Es el proyecto más grande de su familia: **138 ficheros C++, ~54.000 líneas**.
|
||||
|
||||
**Ideas-fuerza que conviene interiorizar:**
|
||||
|
||||
1. **Render paletizado por CPU**: `Surface` de 8 bits indexados + paleta, igual
|
||||
filosofía que un motor retro clásico; la GPU solo escala y aplica post-FX
|
||||
(§4).
|
||||
2. **Flujo por `SceneManager::current`** (variable global) + un único
|
||||
`active_scene_` que el `Director` conmuta (§3).
|
||||
3. **El mundo son habitaciones** de 256×128 px en tiles de 8 px, con colisión
|
||||
por superficies (suelos, paredes, rampas, cintas) y transición entre salas
|
||||
contiguas (§7).
|
||||
4. Trae **editor de mapas** y **consola de comandos** integrados (solo Debug)
|
||||
(§10, §11), un **sistema de logros** persistente (§9), y un **modo demo**
|
||||
que es un *tour de habitaciones* (§12).
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
SDL[SDL3 callbacks · main.cpp] --> DIR[Director]
|
||||
DIR -->|SceneManager::current| SW{switchToActiveScene}
|
||||
SW --> SCN["BootLoader / Logo / Title / Game / Demo / Ending…"]
|
||||
SCN --> GAME["Game (Mode GAME/DEMO)"]
|
||||
GAME --> ROOM[Room + colisión] & RL[room_loader]
|
||||
GAME --> PLAYER[Player] & EM[enemy_manager] & IM[item_manager]
|
||||
GAME --> CHV[Cheevos] & STT[Stats] & SCB[Scoreboard]
|
||||
GAME -->|blit paletizado| SURF["Surface (8-bit indexed)"]
|
||||
SURF -->|toARGBBuffer / copyToTexture| SCREEN[Screen]
|
||||
SCREEN --> GPU["ShaderBackend PostFX/CrtPi"] --> WIN[Ventana]
|
||||
SCREEN -.fallback.-> WIN
|
||||
RES["Resource::Cache / List"] -.-> GAME & SCN
|
||||
EDIT["MapEditor (Debug)"] -.-> GAME
|
||||
CON[Console] -.-> GAME
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
```cpp
|
||||
SDL_AppInit → new Director();
|
||||
SDL_AppIterate→ Director::iterate(); // un frame
|
||||
SDL_AppEvent → Director::handleEvent(event);
|
||||
SDL_AppQuit → delete Director;
|
||||
```
|
||||
|
||||
> ⚠️ **Discrepancia con el `CLAUDE.md`**: este describe un `Director::run()` con
|
||||
> un bucle `while (SceneManager::current != QUIT)`. **El código actual no es
|
||||
> así**: usa la API de callbacks de SDL3. El `Director` real
|
||||
> (`core/system/director.hpp`) expone `iterate()` y `handleEvent()`, no `run()`.
|
||||
|
||||
### 2.2. El `Director`
|
||||
|
||||
`source/core/system/director.{hpp,cpp}`. Mantiene **un solo
|
||||
`std::unique_ptr<Scene> active_scene_`** y un enum `current_scene_`. No guarda un
|
||||
puntero por escena (a diferencia de los proyectos hermanos): construye la escena
|
||||
bajo demanda en `switchToActiveScene()` (`director.cpp`).
|
||||
|
||||
El constructor inicializa los subsistemas en orden: `Resource::List` (registro
|
||||
de assets desde `config/assets.yaml`), `Options`, `Audio`, `Screen`, `Input`,
|
||||
`Resource::Cache` (con `beginLoad()`), y arranca en la escena `BOOT_LOADER`.
|
||||
|
||||
### 2.3. Arranque NO bloqueante
|
||||
|
||||
`Resource::Cache` no se carga de golpe. El constructor deja la escena en
|
||||
`BOOT_LOADER` (una barra de progreso) y cada frame `Director::iterate()` llama a
|
||||
`Resource::Cache::get()->loadStep(50 /*ms*/)` (`director.cpp`): carga assets
|
||||
hasta agotar un presupuesto de 50 ms por frame, manteniendo ventana y eventos
|
||||
vivos. Cuando termina, `finishBoot()` inicializa lo que depende de los recursos
|
||||
(`Notifier`, `RenderInfo`, `Console`, `Cheevos`, `Locale`, en Debug `Debug` y
|
||||
`MapEditor`) y fija la escena destino (`LOGO` en release; en Debug, la que diga
|
||||
`debug.yaml` vía `Debug::getInitialScene()`).
|
||||
|
||||
### 2.4. Gestión del tiempo
|
||||
|
||||
**Time-based**: la escena `Game` posee un `DeltaTimer delta_timer_`
|
||||
(`utils/delta_timer.hpp`) y toda la física/animación consume `delta_time` en
|
||||
segundos. Las constantes de tiempo se documentan como "N frames a 66.67 fps →
|
||||
segundos" (p.ej. `BLACK_SCREEN_DURATION = 0.30F`, `game.hpp:46`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Escenas y flujo de la aplicación
|
||||
|
||||
### 3.1. La base `Scene` y el `SceneManager`
|
||||
|
||||
`source/game/scenes/scene.hpp` es minimalista:
|
||||
|
||||
```cpp
|
||||
class Scene {
|
||||
public:
|
||||
virtual void iterate() = 0; // un frame (update + render)
|
||||
virtual void handleEvent(const SDL_Event&) = 0; // un evento
|
||||
};
|
||||
```
|
||||
|
||||
`source/game/scene_manager.hpp` define el flujo con **variables globales
|
||||
`inline`** en el namespace `SceneManager`:
|
||||
|
||||
```cpp
|
||||
enum class Scene { BOOT_LOADER, LOGO, LOADING_SCREEN, TITLE, CREDITS,
|
||||
GAME, DEMO, GAME_OVER, ENDING, ENDING2, RESTART_CURRENT, QUIT };
|
||||
inline Scene current = Scene::BOOT_LOADER;
|
||||
inline Options options = Options::LOGO_TO_LOADING_SCREEN;
|
||||
inline Scene scene_before_restart = Scene::LOGO;
|
||||
```
|
||||
|
||||
Cualquier escena solicita una transición asignando `SceneManager::current`.
|
||||
|
||||
### 3.2. La conmutación
|
||||
|
||||
`Director::switchToActiveScene()` (`director.cpp`):
|
||||
|
||||
- `RESTART_CURRENT` es especial: restaura `scene_before_restart` (relanza la
|
||||
escena que estaba activa).
|
||||
- `active_scene_.reset()` destruye la anterior (su destructor puede parar la
|
||||
música, etc.).
|
||||
- Un `switch` construye la concreta: `BootLoader`, `Logo`, `LoadingScreen`,
|
||||
`Title`, `Credits`, `Game(Mode::DEMO)`, `Game(Mode::GAME)`, `GameOver`,
|
||||
`Ending`, `Ending2`.
|
||||
|
||||
Nótese que **DEMO y GAME son la misma clase `Game`**, parametrizada por
|
||||
`Game::Mode` (§12).
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
BOOT[BOOT_LOADER] --> LOGO --> LOADING[LOADING_SCREEN] --> TITLE
|
||||
TITLE -->|jugar| GAME --> ENDING --> ENDING2 --> CREDITS
|
||||
TITLE -->|attract| DEMO --> TITLE
|
||||
GAME --> GAME_OVER --> TITLE
|
||||
TITLE --> QUIT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Renderizado: de la lógica al píxel
|
||||
|
||||
El render es **paletizado por CPU**: se dibuja sobre superficies de 8 bits
|
||||
indexados y solo al final se sube a la GPU.
|
||||
|
||||
### 4.1. `Surface`: 8 bits indexados + paleta
|
||||
|
||||
`source/core/rendering/surface.hpp`. Una `Surface` guarda los píxeles como
|
||||
**índices `Uint8`** (`SurfaceData`) más una **`Palette` de 256 colores ARGB** y
|
||||
una **`SubPalette`** (remapeo de índices, identidad por defecto vía `std::iota`).
|
||||
|
||||
Operaciones clave:
|
||||
- `render(...)` / `renderWithColorReplace(src, dst)` — blit con color
|
||||
transparente y reemplazo de índice (para recolorear sprites/glifos).
|
||||
- `renderWithVerticalFade(...)` — disolución por hash 2D (cantos).
|
||||
- `fadePalette()` / `fadeSubPalette()` — fundidos manipulando la paleta.
|
||||
- `copyToTexture(...)` y **`toARGBBuffer(buffer)`** — vuelcan la surface a una
|
||||
`SDL_Texture` o a un buffer ARGB externo.
|
||||
|
||||
Sobre `Surface` se construyen los sprites (`core/rendering/sprite/`):
|
||||
`Sprite` → `AnimatedSprite` (frames `.yaml`) → `MovingSprite` (posición/velocidad)
|
||||
y `DissolveSprite` (transición). Texto: `text.*`. Efectos: `pixel_reveal.*`.
|
||||
Paletas: `palette_manager.*` (el juego permite **cambiar de paleta en caliente**;
|
||||
ver §5).
|
||||
|
||||
### 4.2. `Screen` y la composición
|
||||
|
||||
`source/core/rendering/screen.{hpp,cpp}`. Hay dos superficies/texturas:
|
||||
- **`game_surface_` / `game_texture_`** — el canvas de juego 256×192
|
||||
(`SDL_TEXTUREACCESS_STREAMING`, ARGB8888; `screen.cpp:125`).
|
||||
- **`border_surface_` / `border_texture_`** — el borde/overscan alrededor del
|
||||
canvas.
|
||||
|
||||
El path de presentación (`Screen::render`, `screen.cpp:197`):
|
||||
- **Con backend GPU acelerado**: vuelca las superficies a buffers ARGB
|
||||
(`toARGBBuffer`) y los sube al `shader_backend_` (`uploadPixels`), que renderiza
|
||||
con el shader activo.
|
||||
- **Sin backend** (fallback): `copyToTexture` + `SDL_RenderTexture` de
|
||||
`game_texture_` y `border_texture_` a la ventana.
|
||||
|
||||
El backend vive en `core/rendering/sdl3gpu/` (interfaz `shader_backend.hpp`). Dos
|
||||
shaders: **PostFX** y **CrtPi** (scanlines, curvatura, máscara, etc.), GLSL en
|
||||
`data/shaders/` compilados a SPIR-V (`spv/*_spv.h`), o Metal (MSL) en macOS
|
||||
(`sdl3gpu/msl/`). En Emscripten (`NO_SHADERS`) se fuerza la ruta clásica.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
OBJ["room, player, enemies, items, HUD…"] -->|blit índices| GS["game_surface_ (256×192, 8-bit)"]
|
||||
BORDER["borde / overscan"] --> BS[border_surface_]
|
||||
GS -->|toARGBBuffer / copyToTexture| SCREEN[Screen]
|
||||
BS --> SCREEN
|
||||
SCREEN -->|uploadPixels| SHADER["ShaderBackend (PostFX / CrtPi)"]
|
||||
SHADER --> WIN[Ventana]
|
||||
SCREEN -.fallback SDL_Renderer.-> WIN
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Entrada
|
||||
|
||||
### 5.1. `Input`
|
||||
|
||||
`source/core/input/input.{hpp,cpp}` + `input_types.*` — abstracción de teclado y
|
||||
mando bajo un enum `InputAction`. Las vinculaciones se aplican desde `Options`
|
||||
(`Input::applyKeyboardBindingsFromOptions()` /
|
||||
`applyGamepadBindingsFromOptions()`, `director.cpp`). `mouse.*` gestiona el ratón
|
||||
(usado sobre todo por el editor).
|
||||
|
||||
### 5.2. Hotkeys globales
|
||||
|
||||
`source/core/input/global_inputs.{hpp,cpp}` traduce eventos a acciones de sistema
|
||||
(`global_inputs.cpp`):
|
||||
- **Ventana/vídeo**: fullscreen, zoom ±, integer scale, vsync, info.
|
||||
- **Shaders**: toggle; con **Ctrl** → siguiente shader, con **Shift** →
|
||||
siguiente preset.
|
||||
- **Paletas**: siguiente / anterior (`NEXT_PALETTE`/`PREVIOUS_PALETTE`), y orden
|
||||
de paleta — una seña de identidad de este juego (paleta intercambiable).
|
||||
- **Borde** (overscan) toggle, **consola** toggle, **EXIT**.
|
||||
- En `GAME`, EXIT vuelve a `TITLE`; el `QUIT` global sale del programa.
|
||||
|
||||
Cuando la **consola está activa**, `EXIT`/`ACCEPT` se redirigen a ella en vez de
|
||||
a la escena (`global_inputs.cpp:231`).
|
||||
|
||||
---
|
||||
|
||||
## 6. Lógica del juego: la escena `Game`
|
||||
|
||||
`source/game/scenes/game.{hpp,cpp}` es la escena de gameplay. Hereda de `Scene` y
|
||||
coordina habitación, jugador, enemigos, ítems, marcador, estadísticas y logros.
|
||||
|
||||
### 6.1. FSM de la escena
|
||||
|
||||
`Game::State` (`game.hpp:28`): `PLAYING → BLACK_SCREEN → GAME_OVER →
|
||||
FADE_TO_ENDING → POST_FADE_ENDING`. Cada estado tiene su `updateX`/`renderX`;
|
||||
`transitionToState()` cambia de estado y resetea los timers. El modo
|
||||
(`Game::Mode::GAME` o `DEMO`) condiciona el comportamiento.
|
||||
|
||||
### 6.2. El frame
|
||||
|
||||
`Game::iterate()` calcula el delta con el `DeltaTimer`, llama a `update()`
|
||||
(input + lógica + colisiones + cambio de sala) y a `render()`. El render del
|
||||
estado `PLAYING` va directo a las superficies; los fades de fin de juego usan un
|
||||
`game_backbuffer_surface_`.
|
||||
|
||||
### 6.3. Qué gestiona
|
||||
|
||||
- **Habitación activa** (`std::shared_ptr<Room> room_`) y cambio de sala al tocar
|
||||
un borde (`changeRoom`, `checkPlayerIsOnBorder`; §7).
|
||||
- **Jugador** (`Player`), con muerte (`killPlayer`, `BLACK_SCREEN`), y la "Jail"
|
||||
que restaura vidas con el tiempo (`checkRestoringJail`, `JAIL_RESTORE_INTERVAL`).
|
||||
- **Colisiones**: jugador↔enemigos (`checkPlayerAndEnemies`) y jugador↔ítems
|
||||
(`checkPlayerAndItems`).
|
||||
- **Progresión**: `RoomTracker` (salas visitadas), `Stats`, `Scoreboard`, fin de
|
||||
juego (`checkEndGame`) y secuencias de **ending** (fades a `ENDING`/`ENDING2`).
|
||||
- **Logros**: `checkSomeCheevos`, `checkEndGameCheevos` (§9).
|
||||
|
||||
---
|
||||
|
||||
## 7. Habitaciones y colisión
|
||||
|
||||
`source/game/gameplay/room.{hpp,cpp}` modela cada sala. Geometría: tiles de
|
||||
**8 px**, mapa de **32×16** tiles (256×128 px). Los tipos de tile
|
||||
(`Room::Tile`) son `EMPTY, WALL, PASSABLE, SLOPE_L, SLOPE_R, KILL, ANIMATED`
|
||||
(`room.hpp:32`).
|
||||
|
||||
### 7.1. Datos de sala
|
||||
|
||||
`Room::Data` (`room.hpp:42`) se carga de YAML (vía `RoomLoader`,
|
||||
`Room::loadYAML`). Contiene número/nombre, colores (fondo, borde, ítems),
|
||||
**salas contiguas** (`upper_room`, `lower_room`, `left_room`, `right_room` →
|
||||
navegación tipo *metroidvania*), tileset, el `tile_map` embebido, y las listas
|
||||
de enemigos e ítems.
|
||||
|
||||
### 7.2. Colisión por superficies
|
||||
|
||||
La colisión no es AABB simple contra tiles, sino consultas de **superficies**:
|
||||
`checkRightSurfaces`, `checkLeftSurfaces`, `checkTopSurfaces`,
|
||||
`checkBottomSurfaces`, `checkAutoSurfaces` (cintas), más rampas
|
||||
(`checkLeftSlopes`/`checkRightSlopes`, `getSlopeHeight`, `getSlopeAtPoint`) y
|
||||
cintas transportadoras (`checkConveyorBelts`, `conveyor_belt_direction_`). El
|
||||
jugador aporta puntos de colisión finos (8 `collider_points_` + `under_left_foot_`
|
||||
/ `under_right_foot_`; `player.hpp:147`). Los tiles `KILL` matan al jugador.
|
||||
|
||||
Subobjetos de `Room`: `CollisionMap` (datos de colisión), `TilemapRenderer`
|
||||
(dibujo del tilemap), `EnemyManager` e `ItemManager` (ciclo de vida de enemigos
|
||||
e ítems de la sala). `RoomTracker` (`gameplay/room_tracker.*`) registra las salas
|
||||
visitadas.
|
||||
|
||||
---
|
||||
|
||||
## 8. Entidades
|
||||
|
||||
`source/game/entities/`:
|
||||
|
||||
- **`Player`** (`player.hpp`) — física *time-based* con `JUMP_VELOCITY = -80`,
|
||||
`GRAVITY_FORCE = 155.6` px/s² (`player.hpp:42`). FSM de estados (IDLE/WALKING/
|
||||
JUMPING/…), colisión por 8 puntos + "pies", controladores de sonido de salto y
|
||||
caída (`JumpSoundController`/`FallSoundController`), y un `SpawnData` para
|
||||
reaparecer (también usado al cambiar de sala conservando velocidad).
|
||||
- **`Enemy`** (`enemy.hpp`) — enemigos con datos (`Enemy::Data`), colisión AABB,
|
||||
gestionados por `EnemyManager` (`gameplay/enemy_manager.*`).
|
||||
- **`Item`** (`item.hpp`) — coleccionables (`Item::Data`), gestionados por
|
||||
`ItemManager` (`gameplay/item_manager.*`) y rastreados por `ItemTracker`.
|
||||
|
||||
No hay una clase base de entidad común con polimorfismo profundo: cada tipo tiene
|
||||
su `update`/`render`/colisión y su *manager* dedicado dentro de la `Room`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Logros, estadísticas y marcador
|
||||
|
||||
- **`Cheevos`** (`source/game/gameplay/cheevos.{hpp,cpp}`) — singleton del sistema
|
||||
de logros. `unlock(id)`, `setUnobtainable(id)`, `getTotalUnlockedAchievements()`;
|
||||
estado **persistido en `cheevos.bin`** (`loadFromFile`/`saveToFile`). La escena
|
||||
`Game` llama a `checkSomeCheevos`/`checkEndGameCheevos`, y el `Notifier` muestra
|
||||
el logro en pantalla (§11).
|
||||
- **`Stats`** (`gameplay/stats.*`) — diccionario de estadísticas de partida
|
||||
(`initStats`).
|
||||
- **`Scoreboard`** (`gameplay/scoreboard.*`) — datos y dibujo del marcador
|
||||
(`Scoreboard::Data` se comparte por `shared_ptr` con la sala y el editor).
|
||||
- **`ItemTracker`** / **`RoomTracker`** — progreso de ítems recogidos y salas
|
||||
visitadas.
|
||||
|
||||
---
|
||||
|
||||
## 10. Editor de mapas (Debug)
|
||||
|
||||
`source/game/editor/` — **solo se compila en `_DEBUG`** (todo el header de
|
||||
`MapEditor` está bajo `#ifdef _DEBUG`, `map_editor.hpp:3`). Es un editor de
|
||||
habitaciones *in-game* completo, integrado con la escena `Game` y con la consola.
|
||||
|
||||
### 10.1. `MapEditor` (singleton)
|
||||
|
||||
`map_editor.hpp`. Se entra con `enter(room, player, room_path, scoreboard_data)`
|
||||
sobre la sala viva. Funcionalidades:
|
||||
- **Pintado de tiles** con *brush* (`brush_tile_`, `ERASER_BRUSH`, `painting_`),
|
||||
preview bajo el cursor y rejilla opcional (`renderGrid`, `settings_.grid`).
|
||||
- **Drag & drop** de jugador, enemigos (posición inicial y *bounds* de patrulla)
|
||||
e ítems (`DragTarget`, `DragState`, `handleMouseDown/Up`, `updateDrag`), con
|
||||
*snap* a rejilla.
|
||||
- **Edición de propiedades** de enemigos, ítems y de la sala
|
||||
(`setEnemyProperty`, `setItemProperty`, `setRoomProperty`, colores, color de
|
||||
fondo…), invocables tanto por teclas como por **comandos de consola**.
|
||||
- **Gestión de salas**: crear (`createNewRoom(direction)`), borrar (`deleteRoom`),
|
||||
con conexión a las salas contiguas.
|
||||
- **Persistencia**: `autosave()` + `room_saver.*` escribe el YAML de la sala;
|
||||
`revert()` restaura desde el backup del nodo YAML (`yaml_backup_`).
|
||||
|
||||
### 10.2. Subcomponentes del editor
|
||||
|
||||
- **`TilePicker`** (`tile_picker.*`) — selector visual de tiles del tileset
|
||||
(`openTilePicker`).
|
||||
- **`MiniMap`** (`mini_map.*`) — minimapa de salas con conexiones, colores
|
||||
configurables (`setMiniMapBg`/`setMiniMapConn`).
|
||||
- **`EditorStatusBar`** (`editor_statusbar.*`) — barra de estado con info de
|
||||
edición (`updateStatusBarInfo`).
|
||||
- **`RoomSaver`** (`room_saver.*`) — serialización de la sala a YAML preservando
|
||||
campos no editados.
|
||||
|
||||
El editor guarda/restaura estado del juego al entrar/salir (invencibilidad,
|
||||
overlay de info) para no contaminar la partida.
|
||||
|
||||
---
|
||||
|
||||
## 11. Consola y notificaciones
|
||||
|
||||
### 11.1. `Console`
|
||||
|
||||
`source/game/ui/console.{hpp,cpp}` — consola de comandos *in-game* (singleton),
|
||||
con estética de terminal verde sobre `Surface` propia. Características
|
||||
(`console.hpp`):
|
||||
- Panel animado (`Status` HIDDEN/RISING/ACTIVE/VANISHING), efecto *typewriter*,
|
||||
cursor parpadeante.
|
||||
- **Historial** navegable (flechas), **autocompletado por TAB** (`tab_matches_`),
|
||||
*word-wrap* por ancho en píxeles.
|
||||
- **`CommandRegistry`** (`console_commands.{hpp,cpp}`): metadatos (desde YAML) +
|
||||
*handlers* C++. Los comandos cubren depuración del juego y **pilotan el editor
|
||||
de mapas** (`setEnemyProperty`, `addItem`, `setRoomProperty`, etc.).
|
||||
- **Scopes** (`setScope`/`getScope`): filtran qué comandos y autocompletados
|
||||
están disponibles según el contexto (p.ej. dentro del editor).
|
||||
- `on_toggle` notifica a la escena cuando se abre/cierra (para pausar input de
|
||||
juego).
|
||||
|
||||
### 11.2. `Notifier`
|
||||
|
||||
`source/game/ui/notifier.{hpp,cpp}` — cola de notificaciones en pantalla (logros
|
||||
desbloqueados, cambios de opción…). Se inicializa en `finishBoot()` y el `Screen`
|
||||
las pinta como overlay (`renderNotifications`). El overlay de FPS/driver es
|
||||
`core/rendering/render_info.*` (toggle por hotkey).
|
||||
|
||||
---
|
||||
|
||||
## 12. Modo demo
|
||||
|
||||
> **El modo demo de este juego NO es reproducción de input grabado.** Es un
|
||||
> **tour automático de habitaciones** (escaparate de niveles).
|
||||
|
||||
La escena `Game` construida con `Game::Mode::DEMO` recorre una **lista curada de
|
||||
salas** y va cambiando cada `DEMO_ROOM_DURATION = 6.0F` segundos
|
||||
(`game.hpp:48`):
|
||||
|
||||
```cpp
|
||||
// game.cpp — demoInit()
|
||||
demo_ = DemoData(0.0F, 0, {"04.yaml","54.yaml","20.yaml","09.yaml",
|
||||
"05.yaml","11.yaml","31.yaml","44.yaml"});
|
||||
// demoCheckRoomChange(): acumula delta_time y, al llegar a 6s,
|
||||
// avanza demo_.room_index y changeRoom(...). Al agotar la lista, vuelve.
|
||||
```
|
||||
|
||||
No hay ficheros `.bin` ni `DemoKeys`: la demo simplemente pasea por las
|
||||
habitaciones para la pantalla de atracción. La salida de la demo devuelve a la
|
||||
escena de título.
|
||||
|
||||
---
|
||||
|
||||
## 13. Recursos
|
||||
|
||||
- **`Resource::List`** (`core/resources/resource_list.*`) — registro de rutas de
|
||||
asset cargado de **`config/assets.yaml`**, con consulta `get(filename)` O(1).
|
||||
- **`Resource::Cache`** (`core/resources/resource_cache.*`) — caché de surfaces,
|
||||
música, sonidos y datos de animación (`getSurface`, `getMusic`,
|
||||
`getAnimationData`). Carga **incremental** vía `beginLoad()` + `loadStep(ms)`
|
||||
(§2.3), con una FSM interna de etapas (`LoadStage`).
|
||||
- **Pack y fallback**: `resource_pack.*` + `resource_loader.*` + `resource_helper.*`
|
||||
sirven desde **`resources.pack`** (release) o el filesystem (desarrollo).
|
||||
- **Formatos**: GIF (gráficos + paletas, `core/rendering/gif.*`); `.yaml` para
|
||||
animaciones y para las salas (`data/room/`); `.pal` para paletas
|
||||
(`data/palette/`); OGG/WAV para audio; GLSL para shaders.
|
||||
|
||||
---
|
||||
|
||||
## 14. Audio, localización y configuración
|
||||
|
||||
- **Audio**: `core/audio/audio.*` (singleton, música y SFX) + `audio_adapter.*`
|
||||
sobre **`jail_audio`** (`jail_audio.hpp`), wrapper SDL3 *first-party* con
|
||||
`stb_vorbis` para OGG.
|
||||
- **Localización**: `core/locale/locale.*` carga las cadenas de `data/locale/`.
|
||||
En release el locale vive dentro del pack (`Locale::initFromContent`);
|
||||
`Options::language` selecciona el idioma.
|
||||
- **Configuración**: `source/game/options.{hpp,cpp}` mantiene las opciones
|
||||
(ventana, vídeo+shaders, audio, idioma, controles, presets PostFX/CrtPi) y las
|
||||
persiste; `source/game/defaults.hpp` reúne las constantes de gameplay y layout
|
||||
(canvas 256×192, tamaños de tile, colores de paleta). En Debug, `debug.yaml`
|
||||
(`core/system/debug.*`) fija la escena inicial.
|
||||
- **Builds condicionales**: `_DEBUG` (editor, consola, overlay), `RELEASE_BUILD`,
|
||||
`__EMSCRIPTEN__` (locale/paths especiales, `NO_SHADERS`), y la selección de
|
||||
shaders por plataforma (SPIR-V vs Metal).
|
||||
|
||||
---
|
||||
|
||||
## 15. Convenciones y patrones recurrentes
|
||||
|
||||
- **Singletons con `init()`/`destroy()`/`get()`**: `Screen`, `Input`, `Audio`,
|
||||
`Resource::Cache`, `Resource::List`, `Cheevos`, `Console`, `Notifier`,
|
||||
`RenderInfo`, `MapEditor`, `Debug`. Se crean/destruyen en orden explícito desde
|
||||
el `Director` (no por destructores estáticos).
|
||||
- **Render paletizado por CPU** (`Surface` de 8 bits + `Palette`/`SubPalette`),
|
||||
con recoloreado por reemplazo de índice y paletas intercambiables en caliente.
|
||||
- **Flujo por variable global** (`SceneManager::current`) + un único
|
||||
`active_scene_`.
|
||||
- **Time-based**: todo consume `delta_time` (`DeltaTimer`); las constantes citan
|
||||
su equivalencia en frames a 66.67 fps.
|
||||
- **`#ifdef _DEBUG`** envuelve editor, consola de propiedades, overlays y atajos
|
||||
de depuración — ausentes en release.
|
||||
- **Comentarios** en español/valenciano; muchos `#include` con comentario
|
||||
"// Para X" (estilo IWYU).
|
||||
- **El `CLAUDE.md` puede ir por detrás del código** (caso `Director::run()` vs
|
||||
callbacks). Ante duda, **manda el código**.
|
||||
|
||||
---
|
||||
|
||||
## 16. Guía de navegación: "si quieres tocar X, mira Y"
|
||||
|
||||
| Quiero… | Empieza por… |
|
||||
|---|---|
|
||||
| Entender el arranque | `core/system/director.cpp` (ctor, `iterate`, `finishBoot`) |
|
||||
| Cambiar el flujo de pantallas | `game/scene_manager.hpp` + `Director::switchToActiveScene` |
|
||||
| Añadir/editar una pantalla | `game/scenes/` (hereda de `Scene`) + un `case` en `switchToActiveScene` |
|
||||
| La barra de carga / arranque | `Resource::Cache::beginLoad/loadStep` + `scenes/boot_loader.*` |
|
||||
| Cómo se dibuja todo | `core/rendering/surface.*` + `Screen::render` (`screen.cpp`) |
|
||||
| Sprites / animaciones | `core/rendering/sprite/` + `data/**/*.yaml` (animaciones) |
|
||||
| Paletas / recoloreado | `core/rendering/palette_manager.*` + `Surface` |
|
||||
| Shaders / CRT | `core/rendering/sdl3gpu/` + `data/shaders/` |
|
||||
| Controles / hotkeys | `core/input/input.*` + `global_inputs.cpp` |
|
||||
| **Lógica de partida** | `game/scenes/game.cpp` (`updatePlaying`, FSM `State`) |
|
||||
| Habitaciones / colisión | `game/gameplay/room.*` (`check*Surfaces`, slopes, cintas) |
|
||||
| Cargar una sala | `game/gameplay/room_loader.*` + `data/room/*.yaml` |
|
||||
| El jugador (física) | `game/entities/player.*` (`JUMP_VELOCITY`, `GRAVITY_FORCE`, colisión por puntos) |
|
||||
| Enemigos / ítems | `game/entities/{enemy,item}.*` + `gameplay/{enemy,item}_manager.*` |
|
||||
| Logros | `game/gameplay/cheevos.*` (+ `cheevos.bin`) |
|
||||
| Marcador / estadísticas | `game/gameplay/{scoreboard,stats,item_tracker,room_tracker}.*` |
|
||||
| **Editor de mapas** | `game/editor/map_editor.*` (+ `tile_picker`, `mini_map`, `room_saver`) |
|
||||
| **Consola / comandos** | `game/ui/console.*` + `console_commands.*` |
|
||||
| Notificaciones / FPS | `game/ui/notifier.*` + `core/rendering/render_info.*` |
|
||||
| Modo demo (tour) | `Game::demoInit/demoCheckRoomChange` (`game.cpp`) |
|
||||
| Cargar un recurso | `core/resources/resource_cache.*` + `resource_list.*` + `config/assets.yaml` |
|
||||
| Audio | `core/audio/audio.*` + `jail_audio.hpp` |
|
||||
| Idiomas | `core/locale/locale.*` + `data/locale/` |
|
||||
| Opciones / constantes | `game/options.*`, `game/defaults.hpp` |
|
||||
| Escena inicial en Debug | `core/system/debug.*` + `debug.yaml` |
|
||||
|
||||
---
|
||||
|
||||
*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.*
|
||||
Reference in New Issue
Block a user