From 47f7ffb169963bbeda537973cfd81d6cd4620cda Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 19 Dec 2025 13:01:58 +0100 Subject: [PATCH] feat: implementar jerarquia d'entitats amb classe base Entitat --- CMakeLists.txt | 2 +- source/core/defaults.hpp | 9 ++-- source/core/entities/entitat.hpp | 49 ++++++++++++++++++ source/core/physics/collision.hpp | 32 ++++++++++++ source/game/entities/bala.cpp | 14 ++++-- source/game/entities/bala.hpp | 36 +++++++------- source/game/entities/enemic.cpp | 10 ++-- source/game/entities/enemic.hpp | 40 +++++++-------- source/game/entities/nau.cpp | 12 +++-- source/game/entities/nau.hpp | 41 ++++++++------- source/game/escenes/escena_joc.cpp | 80 +++++++----------------------- source/game/escenes/escena_joc.hpp | 4 +- 12 files changed, 188 insertions(+), 141 deletions(-) create mode 100644 source/core/entities/entitat.hpp create mode 100644 source/core/physics/collision.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e67b28..c1c5b98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # CMakeLists.txt cmake_minimum_required(VERSION 3.10) -project(orni VERSION 0.7.0) +project(orni VERSION 0.7.1) # Info del proyecto set(PROJECT_LONG_NAME "Orni Attack") diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index 8ee62d2..d8f581b 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -120,10 +120,11 @@ constexpr float BLINK_INVISIBLE_TIME = 0.1F; // Tiempo invisible (segundos) // Game rules (lives, respawn, game over) namespace Game { -constexpr int STARTING_LIVES = 3; // Initial lives -constexpr float DEATH_DURATION = 3.0F; // Seconds of death animation -constexpr float GAME_OVER_DURATION = 5.0F; // Seconds to display game over -constexpr float COLLISION_SHIP_ENEMY_AMPLIFIER = 0.80F; // 80% hitbox (generous) +constexpr int STARTING_LIVES = 3; // Initial lives +constexpr float DEATH_DURATION = 3.0F; // Seconds of death animation +constexpr float GAME_OVER_DURATION = 5.0F; // Seconds to display game over +constexpr float COLLISION_SHIP_ENEMY_AMPLIFIER = 0.80F; // 80% hitbox (generous) +constexpr float COLLISION_BULLET_ENEMY_AMPLIFIER = 1.15F; // 115% hitbox (generous) // Friendly fire system constexpr bool FRIENDLY_FIRE_ENABLED = true; // Activar friendly fire diff --git a/source/core/entities/entitat.hpp b/source/core/entities/entitat.hpp new file mode 100644 index 0000000..1210d32 --- /dev/null +++ b/source/core/entities/entitat.hpp @@ -0,0 +1,49 @@ +// entitat.hpp - Classe base abstracta per a totes les entitats del joc +// © 2025 Orni Attack - Arquitectura d'entitats + +#pragma once + +#include + +#include + +#include "core/graphics/shape.hpp" +#include "core/types.hpp" + +namespace Entities { + +class Entitat { + public: + virtual ~Entitat() = default; + + // Interfície principal (virtual pur) + virtual void inicialitzar() = 0; + virtual void actualitzar(float delta_time) = 0; + virtual void dibuixar() const = 0; + [[nodiscard]] virtual bool esta_actiu() const = 0; + + // Interfície de col·lisió (override opcional) + [[nodiscard]] virtual float get_collision_radius() const { return 0.0F; } + [[nodiscard]] virtual bool es_collidable() const { return false; } + + // Getters comuns (inline, sense overhead) + [[nodiscard]] const Punt& get_centre() const { return centre_; } + [[nodiscard]] float get_angle() const { return angle_; } + [[nodiscard]] float get_brightness() const { return brightness_; } + [[nodiscard]] const std::shared_ptr& get_forma() const { return forma_; } + + protected: + // Estat comú (accés directe, sense overhead) + SDL_Renderer* renderer_; + std::shared_ptr forma_; + Punt centre_; + float angle_{0.0F}; + float brightness_{1.0F}; + + // Constructor protegit (classe abstracta) + Entitat(SDL_Renderer* renderer = nullptr) + : renderer_(renderer), + centre_({.x = 0.0F, .y = 0.0F}) {} +}; + +} // namespace Entities diff --git a/source/core/physics/collision.hpp b/source/core/physics/collision.hpp new file mode 100644 index 0000000..4bc73af --- /dev/null +++ b/source/core/physics/collision.hpp @@ -0,0 +1,32 @@ +// collision.hpp - Utilitats de detecció de col·lisions +// © 2025 Orni Attack - Sistema de física + +#pragma once + +#include "core/entities/entitat.hpp" +#include "core/types.hpp" + +namespace Physics { + +// Comprovació genèrica de col·lisió entre dues entitats +inline bool check_collision(const Entities::Entitat& a, const Entities::Entitat& b, float amplifier = 1.0F) { + // Comprovar si ambdós són col·lisionables + if (!a.es_collidable() || !b.es_collidable()) { + return false; + } + + // Calcular radi combinat (amb amplificador per hitbox generós) + float suma_radis = (a.get_collision_radius() + b.get_collision_radius()) * amplifier; + float suma_radis_sq = suma_radis * suma_radis; + + // Comprovació distància al quadrat (sense sqrt) + const Punt& pos_a = a.get_centre(); + const Punt& pos_b = b.get_centre(); + float dx = pos_a.x - pos_b.x; + float dy = pos_a.y - pos_b.y; + float dist_sq = (dx * dx) + (dy * dy); + + return dist_sq <= suma_radis_sq; +} + +} // namespace Physics diff --git a/source/game/entities/bala.cpp b/source/game/entities/bala.cpp index 03ce6e5..17794cd 100644 --- a/source/game/entities/bala.cpp +++ b/source/game/entities/bala.cpp @@ -6,22 +6,26 @@ #include #include +#include #include #include "core/audio/audio.hpp" #include "core/defaults.hpp" +#include "core/entities/entitat.hpp" #include "core/graphics/shape_loader.hpp" #include "core/rendering/shape_renderer.hpp" +#include "core/types.hpp" #include "game/constants.hpp" Bala::Bala(SDL_Renderer* renderer) - : renderer_(renderer), - centre_({.x = 0.0F, .y = 0.0F}), - angle_(0.0F), + : Entitat(renderer), velocitat_(0.0F), esta_(false), - grace_timer_(0.0F), - brightness_(Defaults::Brightness::BALA) { + owner_id_(0), + grace_timer_(0.0F) { + // [NUEVO] Brightness específic per bales + brightness_ = Defaults::Brightness::BALA; + // [NUEVO] Carregar forma compartida des de fitxer forma_ = Graphics::ShapeLoader::load("bullet.shp"); diff --git a/source/game/entities/bala.hpp b/source/game/entities/bala.hpp index 32f0ab3..de801bd 100644 --- a/source/game/entities/bala.hpp +++ b/source/game/entities/bala.hpp @@ -6,43 +6,45 @@ #include #include -#include -#include "core/graphics/shape.hpp" +#include "core/defaults.hpp" +#include "core/entities/entitat.hpp" #include "core/types.hpp" -class Bala { +class Bala : public Entities::Entitat { public: Bala() - : renderer_(nullptr) {} + : Entitat(nullptr) {} Bala(SDL_Renderer* renderer); - void inicialitzar(); + void inicialitzar() override; void disparar(const Punt& posicio, float angle, uint8_t owner_id); - void actualitzar(float delta_time); - void dibuixar() const; + void actualitzar(float delta_time) override; + void dibuixar() const override; + + // Override: Interfície d'Entitat + [[nodiscard]] bool esta_actiu() const override { return esta_; } + + // Override: Interfície de col·lisió + [[nodiscard]] float get_collision_radius() const override { + return Defaults::Entities::BULLET_RADIUS; + } + [[nodiscard]] bool es_collidable() const override { + return esta_ && grace_timer_ <= 0.0F; + } // Getters (API pública sense canvis) [[nodiscard]] bool esta_activa() const { return esta_; } - [[nodiscard]] const Punt& get_centre() const { return centre_; } [[nodiscard]] uint8_t get_owner_id() const { return owner_id_; } [[nodiscard]] float get_grace_timer() const { return grace_timer_; } void desactivar() { esta_ = false; } private: - SDL_Renderer* renderer_; - - // [NUEVO] Forma vectorial (compartida entre totes les bales) - std::shared_ptr forma_; - - // [NUEVO] Estat de la instància (separat de la geometria) - Punt centre_; - float angle_; + // Membres específics de Bala (heretats: renderer_, forma_, centre_, angle_, brightness_) float velocitat_; bool esta_; uint8_t owner_id_; // 0=P1, 1=P2 float grace_timer_; // Grace period timer (0.0 = vulnerable) - float brightness_; // Factor de brillantor (0.0-1.0) void mou(float delta_time); }; diff --git a/source/game/entities/enemic.cpp b/source/game/entities/enemic.cpp index b15bd35..4d28f11 100644 --- a/source/game/entities/enemic.cpp +++ b/source/game/entities/enemic.cpp @@ -10,24 +10,26 @@ #include #include "core/defaults.hpp" +#include "core/entities/entitat.hpp" #include "core/graphics/shape_loader.hpp" #include "core/rendering/shape_renderer.hpp" +#include "core/types.hpp" #include "game/constants.hpp" Enemic::Enemic(SDL_Renderer* renderer) - : renderer_(renderer), - centre_({.x = 0.0F, .y = 0.0F}), - angle_(0.0F), + : Entitat(renderer), velocitat_(0.0F), drotacio_(0.0F), rotacio_(0.0F), esta_(false), - brightness_(Defaults::Brightness::ENEMIC), tipus_(TipusEnemic::PENTAGON), tracking_timer_(0.0F), ship_position_(nullptr), tracking_strength_(0.5F), // Default tracking strength timer_invulnerabilitat_(0.0F) { // Start vulnerable + // [NUEVO] Brightness específic per enemics + brightness_ = Defaults::Brightness::ENEMIC; + // [NUEVO] Forma es carrega a inicialitzar() segons el tipus // Constructor no carrega forma per permetre tipus diferents } diff --git a/source/game/entities/enemic.hpp b/source/game/entities/enemic.hpp index 5ee951a..5a8e14d 100644 --- a/source/game/entities/enemic.hpp +++ b/source/game/entities/enemic.hpp @@ -7,9 +7,9 @@ #include #include -#include -#include "core/graphics/shape.hpp" +#include "core/defaults.hpp" +#include "core/entities/entitat.hpp" #include "core/types.hpp" #include "game/constants.hpp" @@ -36,22 +36,30 @@ struct AnimacioEnemic { float drotacio_duracio = 0.0F; // Duration of transition (seconds) }; -class Enemic { +class Enemic : public Entities::Entitat { public: Enemic() - : renderer_(nullptr) {} + : Entitat(nullptr) {} Enemic(SDL_Renderer* renderer); - void inicialitzar(TipusEnemic tipus = TipusEnemic::PENTAGON, const Punt* ship_pos = nullptr); - void actualitzar(float delta_time); - void dibuixar() const; + void inicialitzar() override { inicialitzar(TipusEnemic::PENTAGON, nullptr); } + void inicialitzar(TipusEnemic tipus, const Punt* ship_pos = nullptr); + void actualitzar(float delta_time) override; + void dibuixar() const override; + + // Override: Interfície d'Entitat + [[nodiscard]] bool esta_actiu() const override { return esta_; } + + // Override: Interfície de col·lisió + [[nodiscard]] float get_collision_radius() const override { + return Defaults::Entities::ENEMY_RADIUS; + } + [[nodiscard]] bool es_collidable() const override { + return esta_ && timer_invulnerabilitat_ <= 0.0F; + } // Getters (API pública sense canvis) - [[nodiscard]] bool esta_actiu() const { return esta_; } - [[nodiscard]] const Punt& get_centre() const { return centre_; } - [[nodiscard]] const std::shared_ptr& get_forma() const { return forma_; } void destruir() { esta_ = false; } - [[nodiscard]] float get_brightness() const { return brightness_; } [[nodiscard]] float get_drotacio() const { return drotacio_; } [[nodiscard]] Punt get_velocitat_vector() const { return { @@ -80,19 +88,11 @@ class Enemic { [[nodiscard]] float get_temps_invulnerabilitat() const { return timer_invulnerabilitat_; } private: - SDL_Renderer* renderer_; - - // [NUEVO] Forma vectorial (compartida entre tots els enemics) - std::shared_ptr forma_; - - // [NUEVO] Estat de la instància (separat de la geometria) - Punt centre_; - float angle_; // Angle de moviment + // Membres específics d'Enemic (heretats: renderer_, forma_, centre_, angle_, brightness_) float velocitat_; float drotacio_; // Delta rotació visual (rad/s) float rotacio_; // Rotació visual acumulada bool esta_; - float brightness_; // Factor de brillantor (0.0-1.0) // [NEW] Enemy type and configuration TipusEnemic tipus_; diff --git a/source/game/entities/nau.cpp b/source/game/entities/nau.cpp index 2472e8c..de97759 100644 --- a/source/game/entities/nau.cpp +++ b/source/game/entities/nau.cpp @@ -8,22 +8,26 @@ #include #include +#include #include #include "core/defaults.hpp" +#include "core/entities/entitat.hpp" #include "core/graphics/shape_loader.hpp" #include "core/input/input.hpp" +#include "core/input/input_types.hpp" #include "core/rendering/shape_renderer.hpp" +#include "core/types.hpp" #include "game/constants.hpp" Nau::Nau(SDL_Renderer* renderer, const char* shape_file) - : renderer_(renderer), - centre_({.x = 0.0F, .y = 0.0F}), - angle_(0.0F), + : Entitat(renderer), velocitat_(0.0F), esta_tocada_(false), - brightness_(Defaults::Brightness::NAU), invulnerable_timer_(0.0F) { + // [NUEVO] Brightness específic per naus + brightness_ = Defaults::Brightness::NAU; + // [NUEVO] Carregar forma compartida des de fitxer forma_ = Graphics::ShapeLoader::load(shape_file); diff --git a/source/game/entities/nau.hpp b/source/game/entities/nau.hpp index 1825713..cc477fe 100644 --- a/source/game/entities/nau.hpp +++ b/source/game/entities/nau.hpp @@ -7,31 +7,39 @@ #include #include -#include -#include "core/graphics/shape.hpp" +#include "core/defaults.hpp" +#include "core/entities/entitat.hpp" #include "core/types.hpp" #include "game/constants.hpp" -class Nau { +class Nau : public Entities::Entitat { public: Nau() - : renderer_(nullptr) {} + : Entitat(nullptr) {} Nau(SDL_Renderer* renderer, const char* shape_file = "ship.shp"); - void inicialitzar(const Punt* spawn_point = nullptr, bool activar_invulnerabilitat = false); + void inicialitzar() override { inicialitzar(nullptr, false); } + void inicialitzar(const Punt* spawn_point, bool activar_invulnerabilitat = false); void processar_input(float delta_time, uint8_t player_id); - void actualitzar(float delta_time); - void dibuixar() const; + void actualitzar(float delta_time) override; + void dibuixar() const override; + + // Override: Interfície d'Entitat + [[nodiscard]] bool esta_actiu() const override { return !esta_tocada_; } + + // Override: Interfície de col·lisió + [[nodiscard]] float get_collision_radius() const override { + return Defaults::Entities::SHIP_RADIUS; + } + [[nodiscard]] bool es_collidable() const override { + return !esta_tocada_ && invulnerable_timer_ <= 0.0F; + } // Getters (API pública sense canvis) - [[nodiscard]] const Punt& get_centre() const { return centre_; } - [[nodiscard]] float get_angle() const { return angle_; } [[nodiscard]] bool esta_viva() const { return !esta_tocada_; } [[nodiscard]] bool esta_tocada() const { return esta_tocada_; } [[nodiscard]] bool es_invulnerable() const { return invulnerable_timer_ > 0.0F; } - [[nodiscard]] const std::shared_ptr& get_forma() const { return forma_; } - [[nodiscard]] float get_brightness() const { return brightness_; } [[nodiscard]] Punt get_velocitat_vector() const { return { .x = velocitat_ * std::cos(angle_ - (Constants::PI / 2.0F)), @@ -45,18 +53,9 @@ class Nau { void marcar_tocada() { esta_tocada_ = true; } private: - SDL_Renderer* renderer_; - - // [NUEVO] Forma vectorial (compartida, només 1 instància de Nau però preparat - // per reutilització) - std::shared_ptr forma_; - - // [NUEVO] Estat de la instància (separat de la geometria) - Punt centre_; - float angle_; // Angle d'orientació + // Membres específics de Nau (heretats: renderer_, forma_, centre_, angle_, brightness_) float velocitat_; // Velocitat (px/s) bool esta_tocada_; - float brightness_; // Factor de brillantor (0.0-1.0) float invulnerable_timer_; // 0.0f = vulnerable, >0.0f = invulnerable void aplicar_fisica(float delta_time); diff --git a/source/game/escenes/escena_joc.cpp b/source/game/escenes/escena_joc.cpp index 0166cbf..be6d3a3 100644 --- a/source/game/escenes/escena_joc.cpp +++ b/source/game/escenes/escena_joc.cpp @@ -12,9 +12,11 @@ #include #include "core/audio/audio.hpp" +#include "core/entities/entitat.hpp" #include "core/input/input.hpp" #include "core/input/mouse.hpp" #include "core/math/easing.hpp" +#include "core/physics/collision.hpp" #include "core/rendering/line_renderer.hpp" #include "core/system/context_escenes.hpp" #include "core/system/global_events.hpp" @@ -942,40 +944,21 @@ std::string EscenaJoc::construir_marcador() const { } void EscenaJoc::detectar_col·lisions_bales_enemics() { - // Constants amplificades per hitbox més generós (115%) - constexpr float RADI_BALA = Defaults::Entities::BULLET_RADIUS; - constexpr float RADI_ENEMIC = Defaults::Entities::ENEMY_RADIUS; - constexpr float SUMA_RADIS = (RADI_BALA + RADI_ENEMIC) * 1.15F; // 28.75 px - constexpr float SUMA_RADIS_QUADRAT = SUMA_RADIS * SUMA_RADIS; // 826.56 + // Amplificador per hitbox més generós (115%) + constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER; // Velocitat d'explosió reduïda per efecte suau constexpr float VELOCITAT_EXPLOSIO = 80.0F; // px/s (en lloc de 80.0f per defecte) - // Iterar per totes les bales actives + // Iterar per totes les bales i enemics for (auto& bala : bales_) { - if (!bala.esta_activa()) { - continue; - } - - const Punt& pos_bala = bala.get_centre(); - - // Comprovar col·lisió amb tots els enemics actius for (auto& enemic : orni_) { - if (!enemic.esta_actiu()) { - continue; - } - - const Punt& pos_enemic = enemic.get_centre(); - - // Calcular distància quadrada (evita sqrt) - float dx = pos_bala.x - pos_enemic.x; - float dy = pos_bala.y - pos_enemic.y; - float distancia_quadrada = (dx * dx) + (dy * dy); - - // Comprovar col·lisió - if (distancia_quadrada <= SUMA_RADIS_QUADRAT) { + // Comprovar col·lisió utilitzant la interfície genèrica + if (Physics::check_collision(bala, enemic, AMPLIFIER)) { // *** COL·LISIÓ DETECTADA *** + const Punt& pos_enemic = enemic.get_centre(); + // 1. Calculate score for enemy type int punts = 0; switch (enemic.get_tipus()) { @@ -1025,12 +1008,8 @@ void EscenaJoc::detectar_col·lisions_bales_enemics() { } void EscenaJoc::detectar_col·lisio_naus_enemics() { - // Generous collision detection (80% hitbox) - constexpr float RADI_NAU = Defaults::Entities::SHIP_RADIUS; - constexpr float RADI_ENEMIC = Defaults::Entities::ENEMY_RADIUS; - constexpr float SUMA_RADIS = - (RADI_NAU + RADI_ENEMIC) * Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER; - constexpr float SUMA_RADIS_QUADRAT = SUMA_RADIS * SUMA_RADIS; + // Amplificador per hitbox generós (80%) + constexpr float AMPLIFIER = Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER; // Check collision for BOTH players for (uint8_t i = 0; i < 2; i++) { @@ -1045,28 +1024,15 @@ void EscenaJoc::detectar_col·lisio_naus_enemics() { continue; } - const Punt& pos_nau = naus_[i].get_centre(); - // Check collision with all active enemies for (const auto& enemic : orni_) { - if (!enemic.esta_actiu()) { - continue; - } - // Skip collision if enemy is invulnerable if (enemic.es_invulnerable()) { continue; } - const Punt& pos_enemic = enemic.get_centre(); - - // Calculate squared distance (avoid sqrt) - auto dx = static_cast(pos_nau.x - pos_enemic.x); - auto dy = static_cast(pos_nau.y - pos_enemic.y); - float distancia_quadrada = (dx * dx) + (dy * dy); - - // Check collision - if (distancia_quadrada <= SUMA_RADIS_QUADRAT) { + // Comprovar col·lisió utilitzant la interfície genèrica + if (Physics::check_collision(naus_[i], enemic, AMPLIFIER)) { tocado(i); // Trigger death sequence for player i break; // Only one collision per player per frame } @@ -1080,12 +1046,8 @@ void EscenaJoc::detectar_col·lisions_bales_jugadors() { return; } - // Collision constants (exact hitbox, 1.0x amplification) - constexpr float RADI_NAU = Defaults::Entities::SHIP_RADIUS; - constexpr float RADI_BALA = Defaults::Entities::BULLET_RADIUS; - constexpr float SUMA_RADIS = (RADI_NAU + RADI_BALA) * - Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER; // 15.0 px - constexpr float SUMA_RADIS_QUADRAT = SUMA_RADIS * SUMA_RADIS; // 225.0 + // Amplificador per hitbox exacte (100%) + constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER; // Check all active bullets for (auto& bala : bales_) { @@ -1098,7 +1060,6 @@ void EscenaJoc::detectar_col·lisions_bales_jugadors() { continue; } - const Punt& pos_bala = bala.get_centre(); uint8_t bullet_owner = bala.get_owner_id(); // Check collision with BOTH players @@ -1121,15 +1082,8 @@ void EscenaJoc::detectar_col·lisions_bales_jugadors() { continue; } - const Punt& pos_nau = naus_[player_id].get_centre(); - - // Calculate squared distance (avoid sqrt) - float dx = pos_bala.x - pos_nau.x; - float dy = pos_bala.y - pos_nau.y; - float distancia_quadrada = (dx * dx) + (dy * dy); - - // Check collision - if (distancia_quadrada <= SUMA_RADIS_QUADRAT) { + // Comprovar col·lisió utilitzant la interfície genèrica + if (Physics::check_collision(bala, naus_[player_id], AMPLIFIER)) { // *** FRIENDLY FIRE HIT *** if (bullet_owner == player_id) { diff --git a/source/game/escenes/escena_joc.hpp b/source/game/escenes/escena_joc.hpp index 21e3e9a..b9d6be3 100644 --- a/source/game/escenes/escena_joc.hpp +++ b/source/game/escenes/escena_joc.hpp @@ -5,11 +5,10 @@ #ifndef ESCENA_JOC_HPP #define ESCENA_JOC_HPP -#include - #include #include #include +#include #include "core/graphics/vector_text.hpp" #include "core/rendering/sdl_manager.hpp" @@ -22,6 +21,7 @@ #include "game/entities/bala.hpp" #include "game/entities/enemic.hpp" #include "game/entities/nau.hpp" +#include "game/stage_system/stage_config.hpp" #include "game/stage_system/stage_manager.hpp" // Game over state machine