142 lines
6.4 KiB
C++
142 lines
6.4 KiB
C++
#pragma once
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
#include "game/entities/animation_component.hpp"
|
|
|
|
// Forward-declaracions: mantenim aquest header lleuger. `Player` viu a
|
|
// `source/game/entities/player.hpp` i només es referencia per referència
|
|
// dins `onPlayerContact`; `EventBus` és opcional a cada entitat i viu al
|
|
// seu header propi. `Surface` és el backend de render paletitzat del motor.
|
|
class Player;
|
|
class EventBus;
|
|
class Surface;
|
|
|
|
// Entitat genèrica del món. Actua com a dada en comú per a tots els actors
|
|
// del joc (decoratius, interactables, perills mòbils i estàtics): manté
|
|
// posició en tiles i píxels, un component d'animació opcional i flags de
|
|
// comportament (collides, interactable). Les subclasses intermèdies
|
|
// afegeixen semàntica (veure `decoration.hpp`, `interactable.hpp`,
|
|
// `hazard.hpp`) i les concretes materialitzen cada tipus (Sarcophagus,
|
|
// Torch, Mummy, etc.).
|
|
//
|
|
// Decisió de disseny: `Entity` NO hereta d'`AnimatedSprite`. L'animació és
|
|
// un component (composició), no una herència, per dues raons: (a) no totes
|
|
// les entitats en necessiten (trampes invisibles, triggers), i (b) lligar
|
|
// el cicle de vida al de `MovingSprite` imposaria semàntica de moviment
|
|
// contínu a actors estàtics. Quan s'hi cablege el render real, es podrà
|
|
// optar per tenir un `Sprite` intern també com a component opcional.
|
|
class Entity {
|
|
public:
|
|
struct TilePos {
|
|
int col{0};
|
|
int row{0};
|
|
};
|
|
|
|
// Sub-rectangle de col·lisió dins de `pixel_rect_`. Pensat per a
|
|
// personatges amb falsa perspectiva (sprite alt, caixa AABB petita
|
|
// encastada a baix) — mateixa convenció que el Player. `offset_x/y`
|
|
// són relatius a la cantonada superior-esquerra del sprite. Si l'entitat
|
|
// no té `CollisionBox`, `getCollisionRect()` retorna tot el `pixel_rect_`.
|
|
struct CollisionBox {
|
|
int offset_x{0};
|
|
int offset_y{0};
|
|
int width{0};
|
|
int height{0};
|
|
};
|
|
|
|
Entity() = default;
|
|
Entity(std::string name, TilePos tile, SDL_FRect pixel_rect);
|
|
virtual ~Entity() = default;
|
|
|
|
Entity(const Entity&) = delete;
|
|
Entity(Entity&&) = delete;
|
|
auto operator=(const Entity&) -> Entity& = delete;
|
|
auto operator=(Entity&&) -> Entity& = delete;
|
|
|
|
// Avanç lògic per frame. La base només avança l'animació si existeix;
|
|
// les subclasses poden afegir IA, cicles, moviment, etc.
|
|
virtual void update(float delta_time);
|
|
|
|
// Render a la pantalla. La base és no-op: l'andamiatge no cableja encara
|
|
// amb el `Screen` real; un cop integrat al `GameScene`, les subclasses
|
|
// o un mòdul de render dedicat dibuixaran sprites segons
|
|
// `anim_.currentFrame()`.
|
|
virtual void render();
|
|
|
|
// El jugador ha xocat amb aquesta entitat (col·lisió o proximitat).
|
|
// Per defecte no fa res; `Hazard` aplica dany, `Door` oberta publica
|
|
// `"level_complete"`, etc. Es passa per punter (nullable) perquè els
|
|
// smoke tests poden disparar el contacte sense un Player real, i
|
|
// perquè en el futur podria disparar-se per triggers d'àrea sense
|
|
// jugador específic.
|
|
virtual void onPlayerContact(Player* player);
|
|
|
|
// Hook que el `EntityManager` invoca per a cada entitat cada vegada
|
|
// que el jugador entra en un tile nou. Només `Sarcophagus` ho consumeix
|
|
// (per marcar perímetre visitat); per defecte no-op.
|
|
virtual void notifyTileEntered(int col, int row);
|
|
|
|
[[nodiscard]] auto getName() const -> const std::string& { return name_; }
|
|
[[nodiscard]] auto getTilePos() const -> TilePos { return tile_; }
|
|
[[nodiscard]] auto getPixelRect() const -> SDL_FRect { return pixel_rect_; }
|
|
[[nodiscard]] auto collides() const -> bool { return collides_; }
|
|
[[nodiscard]] auto interactable() const -> bool { return interactable_; }
|
|
|
|
// Associar el bus una sola vegada (propietat externa, p.ex.
|
|
// `EntityManager`). Entitats que no publiquen events (Torch, trampes
|
|
// estàtiques de moment) el poden ignorar.
|
|
void setEventBus(EventBus* bus) { bus_ = bus; }
|
|
|
|
// Assigna la caixa de render en píxels. La invoca `EntityManager` durant
|
|
// `loadFromLevel` un cop sap la posició definitiva de l'entitat al mapa.
|
|
void setPixelRect(SDL_FRect rect) { pixel_rect_ = rect; }
|
|
|
|
// Assigna la surface per al dibuix. `shared_ptr` perquè les surfaces
|
|
// viuen al `Resource::Cache` i es comparteixen. Si no s'assigna,
|
|
// `render()` no fa res.
|
|
void setSurface(std::shared_ptr<Surface> surface) { surface_ = std::move(surface); }
|
|
|
|
// Accessor públic (la viewable rep per tipus complet `shared_ptr`, però
|
|
// el valor pot ser nul). L'usa `EntityManager::renderAll` per dibuixar
|
|
// sense dependre de la dispatch virtual — el dibuix és centralitzat al
|
|
// manager per tal de confinar la dependència cap a `Surface`.
|
|
[[nodiscard]] auto getSurface() const -> const std::shared_ptr<Surface>& { return surface_; }
|
|
|
|
// Accessor del component d'animació. `EntityManager::renderAll` el
|
|
// consulta per decidir quin frame del strip vertical clipa al blit.
|
|
// Entitats sense animació retornen `nullopt` → frame 0 per defecte.
|
|
[[nodiscard]] auto getAnimation() const -> const std::optional<AnimationComponent>& { return anim_; }
|
|
|
|
// Caixa de col·lisió: per defecte és el `pixel_rect_` sencer (entitats
|
|
// AABB-aligned). Si s'ha configurat un `CollisionBox`, retorna el
|
|
// sub-rect dins del sprite — vida útil: validar col·lisions amb el
|
|
// jugador o el mapa sense que la caixa inclogui "l'alçada" del sprite.
|
|
[[nodiscard]] auto getCollisionRect() const -> SDL_FRect;
|
|
|
|
// Y dels peus: la vora inferior de la caixa de col·lisió. La usarà el
|
|
// Z-order per ordenar entitats i jugador de menys a més profunditat
|
|
// (REND-01, quan s'implemente).
|
|
[[nodiscard]] auto getFeetY() const -> float;
|
|
|
|
// El loader li assigna una caixa explícita llegida del YAML; sense
|
|
// aquesta crida, l'entitat queda sense `CollisionBox` i la caixa de
|
|
// col·lisió coincideix amb el sprite.
|
|
void setCollisionBox(CollisionBox box) { collision_box_ = box; }
|
|
|
|
protected:
|
|
std::string name_;
|
|
TilePos tile_{};
|
|
SDL_FRect pixel_rect_{};
|
|
std::optional<AnimationComponent> anim_;
|
|
std::optional<CollisionBox> collision_box_;
|
|
std::shared_ptr<Surface> surface_{nullptr};
|
|
bool collides_{false};
|
|
bool interactable_{false};
|
|
EventBus* bus_{nullptr};
|
|
};
|