195 lines
8.0 KiB
C++
195 lines
8.0 KiB
C++
#pragma once
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "game/entities/event_bus.hpp"
|
|
|
|
class Text;
|
|
|
|
// Marcador (HUD) de la GameScene. Ocupa la franja superior de 24 px del
|
|
// canvas i es divideix en tres "blocs" simètrics (P1 · COMÚ · P2) que
|
|
// funcionen com a panells independents — estil panell de pinball: cada un
|
|
// té el seu propi mode visible, animació d'entrada, shake i flash.
|
|
//
|
|
// Vida: instanciat per `GameScene` via `unique_ptr` (mateix patró que
|
|
// `PaintLayer`). Cada partida nova crea un HUD net; el destructor
|
|
// desubscriu del bus automàticament. **No és singleton** — és estat de
|
|
// partida, no configuració.
|
|
//
|
|
// Comunicació: subscrit al `EventBus` de l'`EntityManager`. Reacciona a
|
|
// events del joc (`player_hit`, `level_complete`, ...) sense que les
|
|
// entitats hagin de saber que existeix.
|
|
class Hud {
|
|
public:
|
|
// Identificador semàntic de bloc (també índex dins `blocks_`).
|
|
enum class BlockId : std::uint8_t { LEFT = 0,
|
|
CENTER = 1,
|
|
RIGHT = 2 };
|
|
|
|
// Mode actual d'un bloc. Tret d'`IDLE`, els altres modes són
|
|
// *placeholders* per a la primera versió (el render fa res); la API
|
|
// ja està fixada perquè afegir-los més endavant no toqui callers.
|
|
enum class Mode : std::uint8_t { IDLE,
|
|
DYING,
|
|
COUNTDOWN,
|
|
WIN,
|
|
EMPTY };
|
|
|
|
// Prompt mostrat al bloc d'un slot inactiu (en lloc dels zeros). El
|
|
// GameScene fixa aquest valor cada frame segons el `RoomFsm::State`
|
|
// perquè el missatge sigui coherent amb si el jugador pot pulsar
|
|
// START per entrar ara mateix o si ha d'esperar al canvi de fase.
|
|
// - NONE → render històric (zeros). Per a slots actius o per
|
|
// quan el GameScene no vol ensenyar prompt.
|
|
// - CAN_JOIN → "PULSA START / PARA JUGAR". Slot inactiu en PLAYING.
|
|
// - WAIT → "ESPERA / PARA JUGAR". Slot inactiu durant
|
|
// ROOM_INTRO/ROOM_OUTRO i, en fases futures, després
|
|
// de GAME OVER.
|
|
enum class JoinPromptMode : std::uint8_t { NONE,
|
|
CAN_JOIN,
|
|
WAIT };
|
|
|
|
// `initial_lives` es la cantidad de vides amb què s'activa un slot
|
|
// (P1 al ctor i, més tard, els que arriben via "join in progress").
|
|
// Ve del YAML del jugador (`Player::death_config.initial_lives`),
|
|
// així el marcador segueix automàticament la configuració del joc.
|
|
Hud(EventBus& bus, std::shared_ptr<Text> font, int initial_lives);
|
|
~Hud();
|
|
|
|
Hud(const Hud&) = delete;
|
|
Hud(Hud&&) = delete;
|
|
auto operator=(const Hud&) -> Hud& = delete;
|
|
auto operator=(Hud&&) -> Hud& = delete;
|
|
|
|
// Cada frame: avança les animacions actives (entrada, shake, flash).
|
|
void update(int delta_ms);
|
|
|
|
// Pinta els tres blocs sobre el canvas en la seva posició actual.
|
|
// El caller decideix l'ordre respecte a la resta de capes (en general
|
|
// cal cridar-lo a sobre del tilemap perquè quedi visible).
|
|
void render() const;
|
|
|
|
// Substitueix el mode d'un bloc (commutar el que es veu sense canviar
|
|
// la geometria — útil per a Dying/Countdown/Win quan estiguin fets).
|
|
void setMode(BlockId block, Mode mode);
|
|
|
|
// Actualitza l'identificador de piràmide i sala (apareix al bloc
|
|
// central). El caller en farà ús quan canvi de pantalla amb scroll o
|
|
// entre sales.
|
|
void setPyramidRoom(int pyramid, int room);
|
|
|
|
// Activa el bloc d'un jugador (1-based: 1 → P1, 2 → P2). Mentre està
|
|
// inactiu, el bloc mostra zeros i no rep flash/shake; quan s'activa,
|
|
// arrenca amb les vides per defecte.
|
|
void activatePlayer(int player_one_based);
|
|
|
|
// Fixa el prompt visible al bloc d'un slot inactiu (1-based: 1 → P1,
|
|
// 2 → P2). Cridat des de `GameScene::update()` cada frame perquè el
|
|
// missatge depèn de l'estat de la `RoomFsm` (vegeu enum
|
|
// `JoinPromptMode`). Sobre un slot actiu el prompt no es renderitza
|
|
// — el camp existeix per simetria i per evitar branques al caller.
|
|
// Idempotent: cridar amb el mateix mode és no-op.
|
|
void setJoinPrompt(int player_one_based, JoinPromptMode mode);
|
|
|
|
// Geometria pública perquè la GameScene puga calcular si el HUD
|
|
// s'està movent encara (p. ex. retardar l'spawn fins que estiga al
|
|
// seu lloc) — opcional, no obligatori.
|
|
static constexpr int HEIGHT_PX = 24;
|
|
|
|
private:
|
|
// Amplades dels tres blocs. Sumen exactament `GameCanvas::WIDTH` (320).
|
|
// Centrats: el bloc del mig agafa els píxels sobrants (108) i els
|
|
// laterals queden idèntics (106 cadascun) per garantir simetria.
|
|
static constexpr int LEFT_W = 106;
|
|
static constexpr int CENTER_W = 108;
|
|
static constexpr int RIGHT_W = 106;
|
|
|
|
// Files de text dins del bloc (y relatiu al bloc), per a una font de
|
|
// 8 px d'alçada amb un petit padding visual a dalt i a baix.
|
|
static constexpr int LINE1_Y = 4;
|
|
static constexpr int LINE2_Y = 13;
|
|
|
|
struct Block {
|
|
Mode mode{Mode::IDLE};
|
|
// Coordenades de pantalla on viu el bloc (constants durant la
|
|
// partida; només varien els offsets transitoris d'entrada/shake).
|
|
int x{0};
|
|
int y{0};
|
|
int w{0};
|
|
int h{0};
|
|
|
|
// Animació d'entrada: ms restants fins a quedar-se a la posició
|
|
// final. Quan val 0 el bloc està quiet. El Hud ctor l'inicialitza
|
|
// amb la durada viva a data/config/ui.yaml (hud.entry_ms).
|
|
int entry_ms_left{0};
|
|
// Vector inicial des d'on entra: {-LEFT_W,0} per al bloc esquerre,
|
|
// {0,-HEIGHT_PX} per al central i {+RIGHT_W,0} per al dret.
|
|
int entry_offset_x{0};
|
|
int entry_offset_y{0};
|
|
|
|
// Shake actiu: amplitud i ms restants. Decau linealment.
|
|
int shake_ms_left{0};
|
|
float shake_intensity{0.0F};
|
|
|
|
// Flash actiu: mentre val >0, el text del valor es pinta amb el
|
|
// color de flash en compte del de valor.
|
|
int flash_ms_left{0};
|
|
};
|
|
|
|
struct PlayerStats {
|
|
int lives{0};
|
|
int score{0};
|
|
int treasures{0};
|
|
// `false` → el bloc mostra el prompt actiu (`join_prompt`) o
|
|
// bé zeros si el prompt és NONE. Sempre es renderitza per
|
|
// mantenir el layout fix.
|
|
bool active{false};
|
|
// Missatge a mostrar quan `active==false`. El GameScene el
|
|
// refresca cada frame segons l'estat de la RoomFsm (vegeu
|
|
// `Hud::setJoinPrompt`).
|
|
JoinPromptMode join_prompt{JoinPromptMode::NONE};
|
|
};
|
|
|
|
// Pinta el bloc indicat amb el seu fons + el contingut del seu mode
|
|
// actual. Aplica l'offset (entrada + shake) abans de dibuixar.
|
|
void renderBlock(BlockId id) const;
|
|
|
|
// Subrutines del mode `Idle` per als tres blocs. Reben el rectangle
|
|
// ja desplaçat (x, y) on han d'escriure.
|
|
void renderIdleSide(int x, int y, int w, const Block& blk, int player_index, const std::string& label) const;
|
|
void renderIdleCenter(int x, int y, int w) const;
|
|
|
|
// Calcula l'offset transitori (entrada + shake) en píxels per a un
|
|
// bloc. Static perquè depèn només dels camps del propi `Block` i de
|
|
// les constants de classe (no toca cap altre membre).
|
|
[[nodiscard]] static auto computeOffset(const Block& b) -> SDL_FPoint;
|
|
|
|
// Estat actual del parpadeig dels prompts inactius. Llegit per
|
|
// renderIdleSide() per saltar el draw quan toca ocult.
|
|
[[nodiscard]] auto blinkVisible() const -> bool;
|
|
|
|
EventBus& bus_;
|
|
std::vector<EventBus::SubId> subs_;
|
|
|
|
std::shared_ptr<Text> font_;
|
|
|
|
std::array<Block, 3> blocks_;
|
|
std::array<PlayerStats, 2> players_;
|
|
int pyramid_{1};
|
|
int room_{1};
|
|
// Acumulador continu (ms) per al parpadeig dels prompts inactius.
|
|
// Es manté entre canvis de mode (CAN_JOIN ↔ WAIT) per evitar salts
|
|
// visuals i sincronitza els dos slots — vegeu Hud::blinkVisible().
|
|
int blink_elapsed_ms_{0};
|
|
// Vides amb què s'arrenca un slot (`activatePlayer`). Cachejat al
|
|
// ctor des de `Player::death_config.initial_lives` per no llegir el
|
|
// singleton estàtic en el camí calent del bus de events.
|
|
int initial_lives_{0};
|
|
};
|