afegit segon jugador
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
# ship2.shp - Nau del jugador (triangle amb base còncava - punta de fletxa)
|
# ship.shp - Nau del jugador 1 (triangle amb base còncava - punta de fletxa)
|
||||||
# © 1999 Visente i Sergi (versió Pascal)
|
# © 1999 Visente i Sergi (versió Pascal)
|
||||||
# © 2025 Port a C++20 amb SDL3
|
# © 2025 Port a C++20 amb SDL3
|
||||||
|
|
||||||
name: ship2
|
name: ship
|
||||||
scale: 1.0
|
scale: 1.0
|
||||||
center: 0, 0
|
center: 0, 0
|
||||||
|
|
||||||
|
|||||||
27
data/shapes/ship2.shp
Normal file
27
data/shapes/ship2.shp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# ship2.shp - Nau del jugador 2 (interceptor amb ales)
|
||||||
|
# © 2025 Orni Attack - Jugador 2
|
||||||
|
|
||||||
|
name: ship2
|
||||||
|
scale: 1.0
|
||||||
|
center: 0, 0
|
||||||
|
|
||||||
|
# Interceptor amb ales laterals
|
||||||
|
# Disseny més ample i agressiu que P1
|
||||||
|
#
|
||||||
|
# Geometria:
|
||||||
|
# - Punta més curta i ampla
|
||||||
|
# - Ales laterals pronunciades
|
||||||
|
# - Base més ampla per estabilitat visual
|
||||||
|
#
|
||||||
|
# Punts (cartesianes, Y negatiu = amunt):
|
||||||
|
# p1: (0, -10) → punta (més curta que P1)
|
||||||
|
# p2: (4, -6) → transició ala dreta
|
||||||
|
# p3: (10, 2) → punta ala dreta (més ampla)
|
||||||
|
# p4: (6, 8) → base ala dreta
|
||||||
|
# p5: (0, 6) → base centre (menys còncava)
|
||||||
|
# p6: (-6, 8) → base ala esquerra
|
||||||
|
# p7: (-10, 2) → punta ala esquerra
|
||||||
|
# p8: (-4, -6) → transició ala esquerra
|
||||||
|
# p1: (0, -10) → tanca
|
||||||
|
|
||||||
|
polyline: 0,-10 4,-6 10,2 6,8 0,6 -6,8 -10,2 -4,-6 0,-10
|
||||||
@@ -138,6 +138,11 @@ constexpr float INIT_HUD_SHIP_RATIO = 1.0f; // Proporción animación nave
|
|||||||
|
|
||||||
// Posición inicial de la nave en INIT_HUD (75% de altura de zona de juego)
|
// Posición inicial de la nave en INIT_HUD (75% de altura de zona de juego)
|
||||||
constexpr float INIT_HUD_SHIP_START_Y_RATIO = 0.75f; // 75% desde el top de PLAYAREA
|
constexpr float INIT_HUD_SHIP_START_Y_RATIO = 0.75f; // 75% desde el top de PLAYAREA
|
||||||
|
|
||||||
|
// Spawn positions (distribución horizontal para 2 jugadores)
|
||||||
|
constexpr float P1_SPAWN_X_RATIO = 0.33f; // 33% desde izquierda
|
||||||
|
constexpr float P2_SPAWN_X_RATIO = 0.67f; // 67% desde izquierda
|
||||||
|
constexpr float SPAWN_Y_RATIO = 0.75f; // 75% desde arriba
|
||||||
} // namespace Game
|
} // namespace Game
|
||||||
|
|
||||||
// Física (valores actuales del juego, sincronizados con joc_asteroides.cpp)
|
// Física (valores actuales del juego, sincronizados con joc_asteroides.cpp)
|
||||||
@@ -245,6 +250,23 @@ constexpr const char* LOGO = "effects/logo.wav"; //
|
|||||||
constexpr const char* GOOD_JOB_COMMANDER = "voices/good_job_commander.wav"; // Voz: "Good job, commander"
|
constexpr const char* GOOD_JOB_COMMANDER = "voices/good_job_commander.wav"; // Voz: "Good job, commander"
|
||||||
} // namespace Sound
|
} // namespace Sound
|
||||||
|
|
||||||
|
// Controls (mapeo de teclas para los jugadores)
|
||||||
|
namespace Controls {
|
||||||
|
namespace P1 {
|
||||||
|
constexpr SDL_Scancode ROTATE_RIGHT = SDL_SCANCODE_RIGHT;
|
||||||
|
constexpr SDL_Scancode ROTATE_LEFT = SDL_SCANCODE_LEFT;
|
||||||
|
constexpr SDL_Scancode THRUST = SDL_SCANCODE_UP;
|
||||||
|
constexpr SDL_Keycode SHOOT = SDLK_SPACE;
|
||||||
|
} // namespace P1
|
||||||
|
|
||||||
|
namespace P2 {
|
||||||
|
constexpr SDL_Scancode ROTATE_RIGHT = SDL_SCANCODE_D;
|
||||||
|
constexpr SDL_Scancode ROTATE_LEFT = SDL_SCANCODE_A;
|
||||||
|
constexpr SDL_Scancode THRUST = SDL_SCANCODE_W;
|
||||||
|
constexpr SDL_Keycode SHOOT = SDLK_LSHIFT;
|
||||||
|
} // namespace P2
|
||||||
|
} // namespace Controls
|
||||||
|
|
||||||
// Enemy type configuration (tipus d'enemics)
|
// Enemy type configuration (tipus d'enemics)
|
||||||
namespace Enemies {
|
namespace Enemies {
|
||||||
// Pentagon (esquivador - zigzag evasion)
|
// Pentagon (esquivador - zigzag evasion)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ void Bala::inicialitzar() {
|
|||||||
velocitat_ = 0.0f;
|
velocitat_ = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bala::disparar(const Punt& posicio, float angle) {
|
void Bala::disparar(const Punt& posicio, float angle, uint8_t owner_id) {
|
||||||
// Activar bala i posicionar-la a la nau
|
// Activar bala i posicionar-la a la nau
|
||||||
// Basat en joc_asteroides.cpp línies 188-200
|
// Basat en joc_asteroides.cpp línies 188-200
|
||||||
|
|
||||||
@@ -50,6 +50,9 @@ void Bala::disparar(const Punt& posicio, float angle) {
|
|||||||
// Angle = angle de la nau (dispara en la direcció que apunta)
|
// Angle = angle de la nau (dispara en la direcció que apunta)
|
||||||
angle_ = angle;
|
angle_ = angle;
|
||||||
|
|
||||||
|
// Almacenar propietario (0=P1, 1=P2)
|
||||||
|
owner_id_ = owner_id;
|
||||||
|
|
||||||
// Velocitat alta (el joc Pascal original usava 7 px/frame)
|
// Velocitat alta (el joc Pascal original usava 7 px/frame)
|
||||||
// 7 px/frame × 20 FPS = 140 px/s
|
// 7 px/frame × 20 FPS = 140 px/s
|
||||||
velocitat_ = 140.0f;
|
velocitat_ = 140.0f;
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ class Bala {
|
|||||||
Bala(SDL_Renderer* renderer);
|
Bala(SDL_Renderer* renderer);
|
||||||
|
|
||||||
void inicialitzar();
|
void inicialitzar();
|
||||||
void disparar(const Punt& posicio, float angle);
|
void disparar(const Punt& posicio, float angle, uint8_t owner_id);
|
||||||
void actualitzar(float delta_time);
|
void actualitzar(float delta_time);
|
||||||
void dibuixar() const;
|
void dibuixar() const;
|
||||||
|
|
||||||
// Getters (API pública sense canvis)
|
// Getters (API pública sense canvis)
|
||||||
bool esta_activa() const { return esta_; }
|
bool esta_activa() const { return esta_; }
|
||||||
const Punt& get_centre() const { return centre_; }
|
const Punt& get_centre() const { return centre_; }
|
||||||
|
uint8_t get_owner_id() const { return owner_id_; }
|
||||||
void desactivar() { esta_ = false; }
|
void desactivar() { esta_ = false; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -37,6 +38,7 @@ class Bala {
|
|||||||
float angle_;
|
float angle_;
|
||||||
float velocitat_;
|
float velocitat_;
|
||||||
bool esta_;
|
bool esta_;
|
||||||
|
uint8_t owner_id_; // 0=P1, 1=P2
|
||||||
float brightness_; // Factor de brillantor (0.0-1.0)
|
float brightness_; // Factor de brillantor (0.0-1.0)
|
||||||
|
|
||||||
void mou(float delta_time);
|
void mou(float delta_time);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include "core/rendering/shape_renderer.hpp"
|
#include "core/rendering/shape_renderer.hpp"
|
||||||
#include "game/constants.hpp"
|
#include "game/constants.hpp"
|
||||||
|
|
||||||
Nau::Nau(SDL_Renderer* renderer)
|
Nau::Nau(SDL_Renderer* renderer, const char* shape_file)
|
||||||
: renderer_(renderer),
|
: renderer_(renderer),
|
||||||
centre_({0.0f, 0.0f}),
|
centre_({0.0f, 0.0f}),
|
||||||
angle_(0.0f),
|
angle_(0.0f),
|
||||||
@@ -23,10 +23,10 @@ Nau::Nau(SDL_Renderer* renderer)
|
|||||||
brightness_(Defaults::Brightness::NAU),
|
brightness_(Defaults::Brightness::NAU),
|
||||||
invulnerable_timer_(0.0f) {
|
invulnerable_timer_(0.0f) {
|
||||||
// [NUEVO] Carregar forma compartida des de fitxer
|
// [NUEVO] Carregar forma compartida des de fitxer
|
||||||
forma_ = Graphics::ShapeLoader::load("ship.shp");
|
forma_ = Graphics::ShapeLoader::load(shape_file);
|
||||||
|
|
||||||
if (!forma_ || !forma_->es_valida()) {
|
if (!forma_ || !forma_->es_valida()) {
|
||||||
std::cerr << "[Nau] Error: no s'ha pogut carregar ship.shp" << std::endl;
|
std::cerr << "[Nau] Error: no s'ha pogut carregar " << shape_file << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ void Nau::inicialitzar(const Punt* spawn_point, bool activar_invulnerabilitat) {
|
|||||||
esta_tocada_ = false;
|
esta_tocada_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nau::processar_input(float delta_time) {
|
void Nau::processar_input(float delta_time, uint8_t player_id) {
|
||||||
// Processar input continu (com teclapuls() del Pascal original)
|
// Processar input continu (com teclapuls() del Pascal original)
|
||||||
// Basat en joc_asteroides.cpp línies 66-85
|
// Basat en joc_asteroides.cpp línies 66-85
|
||||||
// Només processa input si la nau està viva
|
// Només processa input si la nau està viva
|
||||||
@@ -74,17 +74,28 @@ void Nau::processar_input(float delta_time) {
|
|||||||
// Obtenir estat actual del teclat (no events, sinó estat continu)
|
// Obtenir estat actual del teclat (no events, sinó estat continu)
|
||||||
const bool* keyboard_state = SDL_GetKeyboardState(nullptr);
|
const bool* keyboard_state = SDL_GetKeyboardState(nullptr);
|
||||||
|
|
||||||
|
// Seleccionar controles según player_id
|
||||||
|
SDL_Scancode key_right = (player_id == 0)
|
||||||
|
? Defaults::Controls::P1::ROTATE_RIGHT
|
||||||
|
: Defaults::Controls::P2::ROTATE_RIGHT;
|
||||||
|
SDL_Scancode key_left = (player_id == 0)
|
||||||
|
? Defaults::Controls::P1::ROTATE_LEFT
|
||||||
|
: Defaults::Controls::P2::ROTATE_LEFT;
|
||||||
|
SDL_Scancode key_thrust = (player_id == 0)
|
||||||
|
? Defaults::Controls::P1::THRUST
|
||||||
|
: Defaults::Controls::P2::THRUST;
|
||||||
|
|
||||||
// Rotació
|
// Rotació
|
||||||
if (keyboard_state[SDL_SCANCODE_RIGHT]) {
|
if (keyboard_state[key_right]) {
|
||||||
angle_ += Defaults::Physics::ROTATION_SPEED * delta_time;
|
angle_ += Defaults::Physics::ROTATION_SPEED * delta_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyboard_state[SDL_SCANCODE_LEFT]) {
|
if (keyboard_state[key_left]) {
|
||||||
angle_ -= Defaults::Physics::ROTATION_SPEED * delta_time;
|
angle_ -= Defaults::Physics::ROTATION_SPEED * delta_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acceleració
|
// Acceleració
|
||||||
if (keyboard_state[SDL_SCANCODE_UP]) {
|
if (keyboard_state[key_thrust]) {
|
||||||
if (velocitat_ < Defaults::Physics::MAX_VELOCITY) {
|
if (velocitat_ < Defaults::Physics::MAX_VELOCITY) {
|
||||||
velocitat_ += Defaults::Physics::ACCELERATION * delta_time;
|
velocitat_ += Defaults::Physics::ACCELERATION * delta_time;
|
||||||
if (velocitat_ > Defaults::Physics::MAX_VELOCITY) {
|
if (velocitat_ > Defaults::Physics::MAX_VELOCITY) {
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ class Nau {
|
|||||||
public:
|
public:
|
||||||
Nau()
|
Nau()
|
||||||
: renderer_(nullptr) {}
|
: renderer_(nullptr) {}
|
||||||
Nau(SDL_Renderer* renderer);
|
Nau(SDL_Renderer* renderer, const char* shape_file = "ship.shp");
|
||||||
|
|
||||||
void inicialitzar(const Punt* spawn_point = nullptr, bool activar_invulnerabilitat = false);
|
void inicialitzar(const Punt* spawn_point = nullptr, bool activar_invulnerabilitat = false);
|
||||||
void processar_input(float delta_time);
|
void processar_input(float delta_time, uint8_t player_id);
|
||||||
void actualitzar(float delta_time);
|
void actualitzar(float delta_time);
|
||||||
void dibuixar() const;
|
void dibuixar() const;
|
||||||
|
|
||||||
|
|||||||
@@ -28,14 +28,15 @@ EscenaJoc::EscenaJoc(SDLManager& sdl, ContextEscenes& context)
|
|||||||
context_(context),
|
context_(context),
|
||||||
debris_manager_(sdl.obte_renderer()),
|
debris_manager_(sdl.obte_renderer()),
|
||||||
gestor_puntuacio_(sdl.obte_renderer()),
|
gestor_puntuacio_(sdl.obte_renderer()),
|
||||||
nau_(sdl.obte_renderer()),
|
|
||||||
itocado_(0),
|
|
||||||
puntuacio_total_(0),
|
|
||||||
text_(sdl.obte_renderer()) {
|
text_(sdl.obte_renderer()) {
|
||||||
// Consumir opcions (preparació per MODE_DEMO futur)
|
// Consumir opcions (preparació per MODE_DEMO futur)
|
||||||
auto opcio = context_.consumir_opcio();
|
auto opcio = context_.consumir_opcio();
|
||||||
(void)opcio; // Suprimir warning de variable no usada
|
(void)opcio; // Suprimir warning de variable no usada
|
||||||
|
|
||||||
|
// Inicialitzar naus amb renderer (P1=ship.shp, P2=ship2.shp)
|
||||||
|
naus_[0] = Nau(sdl.obte_renderer(), "ship.shp"); // Jugador 1: nave estàndar
|
||||||
|
naus_[1] = Nau(sdl.obte_renderer(), "ship2.shp"); // Jugador 2: interceptor amb ales
|
||||||
|
|
||||||
// Inicialitzar bales amb renderer
|
// Inicialitzar bales amb renderer
|
||||||
for (auto& bala : bales_) {
|
for (auto& bala : bales_) {
|
||||||
bala = Bala(sdl.obte_renderer());
|
bala = Bala(sdl.obte_renderer());
|
||||||
@@ -132,37 +133,43 @@ void EscenaJoc::inicialitzar() {
|
|||||||
stage_manager_ = std::make_unique<StageSystem::StageManager>(stage_config_.get());
|
stage_manager_ = std::make_unique<StageSystem::StageManager>(stage_config_.get());
|
||||||
stage_manager_->inicialitzar();
|
stage_manager_->inicialitzar();
|
||||||
|
|
||||||
// [NEW] Set ship position reference for safe spawn
|
// [NEW] Set ship position reference for safe spawn (P1 for now, TODO: dual tracking)
|
||||||
stage_manager_->get_spawn_controller().set_ship_position(&nau_.get_centre());
|
stage_manager_->get_spawn_controller().set_ship_position(&naus_[0].get_centre());
|
||||||
|
|
||||||
// Inicialitzar estat de col·lisió
|
// Inicialitzar timers de muerte per jugador
|
||||||
itocado_ = 0;
|
itocado_per_jugador_[0] = 0.0f;
|
||||||
|
itocado_per_jugador_[1] = 0.0f;
|
||||||
|
|
||||||
// Initialize lives and game over state
|
// Initialize lives and game over state (independent lives per player)
|
||||||
num_vides_ = Defaults::Game::STARTING_LIVES;
|
vides_per_jugador_[0] = Defaults::Game::STARTING_LIVES;
|
||||||
|
vides_per_jugador_[1] = Defaults::Game::STARTING_LIVES;
|
||||||
game_over_ = false;
|
game_over_ = false;
|
||||||
game_over_timer_ = 0.0f;
|
game_over_timer_ = 0.0f;
|
||||||
|
|
||||||
// Initialize score
|
// Initialize scores (separate per player)
|
||||||
puntuacio_total_ = 0;
|
puntuacio_per_jugador_[0] = 0;
|
||||||
|
puntuacio_per_jugador_[1] = 0;
|
||||||
gestor_puntuacio_.reiniciar();
|
gestor_puntuacio_.reiniciar();
|
||||||
|
|
||||||
// Set spawn point to center X, 75% Y (same as INIT_HUD final position)
|
// Set spawn point to center X, 75% Y (legacy, for INIT_HUD animation)
|
||||||
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
||||||
punt_spawn_.x = zona.x + zona.w * 0.5f;
|
punt_spawn_.x = zona.x + zona.w * 0.5f;
|
||||||
punt_spawn_.y = zona.y + zona.h * Defaults::Game::INIT_HUD_SHIP_START_Y_RATIO;
|
punt_spawn_.y = zona.y + zona.h * Defaults::Game::INIT_HUD_SHIP_START_Y_RATIO;
|
||||||
|
|
||||||
// Inicialitzar nau amb posició especial
|
// Inicialitzar AMBAS naus amb posicions específiques
|
||||||
nau_.inicialitzar(&punt_spawn_);
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
|
Punt spawn_pos = obtenir_punt_spawn(i);
|
||||||
|
naus_[i].inicialitzar(&spawn_pos, false); // No invulnerability at start
|
||||||
|
}
|
||||||
|
|
||||||
// [MODIFIED] Initialize enemies as inactive (stage system will spawn them)
|
// [MODIFIED] Initialize enemies as inactive (stage system will spawn them)
|
||||||
for (auto& enemy : orni_) {
|
for (auto& enemy : orni_) {
|
||||||
enemy = Enemic(sdl_.obte_renderer());
|
enemy = Enemic(sdl_.obte_renderer());
|
||||||
enemy.set_ship_position(&nau_.get_centre()); // Set ship reference for tracking
|
enemy.set_ship_position(&naus_[0].get_centre()); // Set ship reference (P1 for now)
|
||||||
// DON'T call enemy.inicialitzar() here - stage system handles spawning
|
// DON'T call enemy.inicialitzar() here - stage system handles spawning
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inicialitzar bales
|
// Inicialitzar bales (now 6 instead of 3)
|
||||||
for (auto& bala : bales_) {
|
for (auto& bala : bales_) {
|
||||||
bala.inicialitzar();
|
bala.inicialitzar();
|
||||||
}
|
}
|
||||||
@@ -201,30 +208,43 @@ void EscenaJoc::actualitzar(float delta_time) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check death sequence state
|
// Check death sequence state for BOTH players
|
||||||
if (itocado_ > 0.0f) {
|
bool algun_jugador_mort = false;
|
||||||
// Death sequence active: update timer
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
itocado_ += delta_time;
|
if (itocado_per_jugador_[i] > 0.0f && itocado_per_jugador_[i] < 999.0f) {
|
||||||
|
algun_jugador_mort = true;
|
||||||
|
// Death sequence active: update timer
|
||||||
|
itocado_per_jugador_[i] += delta_time;
|
||||||
|
|
||||||
// Check if death duration completed
|
// Check if death duration completed (only trigger ONCE using sentinel value)
|
||||||
if (itocado_ >= Defaults::Game::DEATH_DURATION) {
|
if (itocado_per_jugador_[i] >= Defaults::Game::DEATH_DURATION) {
|
||||||
// *** PHASE 3: RESPAWN OR GAME OVER ***
|
// *** PHASE 3: RESPAWN OR GAME OVER ***
|
||||||
|
|
||||||
// Decrement lives
|
// Decrement lives for this player (only once)
|
||||||
num_vides_--;
|
vides_per_jugador_[i]--;
|
||||||
|
|
||||||
if (num_vides_ > 0) {
|
if (vides_per_jugador_[i] > 0) {
|
||||||
// Respawn ship en posición de muerte con invulnerabilidad
|
// Respawn ship en spawn position con invulnerabilidad
|
||||||
nau_.inicialitzar(&punt_mort_, true);
|
Punt spawn_pos = obtenir_punt_spawn(i);
|
||||||
itocado_ = 0.0f;
|
naus_[i].inicialitzar(&spawn_pos, true);
|
||||||
} else {
|
itocado_per_jugador_[i] = 0.0f;
|
||||||
// Game over
|
} else {
|
||||||
game_over_ = true;
|
// Player is permanently dead (out of lives)
|
||||||
game_over_timer_ = Defaults::Game::GAME_OVER_DURATION;
|
// Set sentinel value to prevent re-entering this block
|
||||||
itocado_ = 0.0f;
|
itocado_per_jugador_[i] = 999.0f;
|
||||||
|
|
||||||
|
// Check if BOTH players are dead (game over)
|
||||||
|
if (vides_per_jugador_[0] <= 0 && vides_per_jugador_[1] <= 0) {
|
||||||
|
game_over_ = true;
|
||||||
|
game_over_timer_ = Defaults::Game::GAME_OVER_DURATION;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any player is dead, still update enemies/bullets/effects
|
||||||
|
if (algun_jugador_mort) {
|
||||||
// Enemies and bullets continue moving during death sequence
|
// Enemies and bullets continue moving during death sequence
|
||||||
for (auto& enemy : orni_) {
|
for (auto& enemy : orni_) {
|
||||||
enemy.actualitzar(delta_time);
|
enemy.actualitzar(delta_time);
|
||||||
@@ -236,7 +256,8 @@ void EscenaJoc::actualitzar(float delta_time) {
|
|||||||
|
|
||||||
debris_manager_.actualitzar(delta_time);
|
debris_manager_.actualitzar(delta_time);
|
||||||
gestor_puntuacio_.actualitzar(delta_time);
|
gestor_puntuacio_.actualitzar(delta_time);
|
||||||
return;
|
|
||||||
|
// Don't return - allow alive players to continue playing
|
||||||
}
|
}
|
||||||
|
|
||||||
// *** STAGE SYSTEM STATE MACHINE ***
|
// *** STAGE SYSTEM STATE MACHINE ***
|
||||||
@@ -263,10 +284,10 @@ void EscenaJoc::actualitzar(float delta_time) {
|
|||||||
float ship_anim_progress = ship_progress / Defaults::Game::INIT_HUD_SHIP_RATIO;
|
float ship_anim_progress = ship_progress / Defaults::Game::INIT_HUD_SHIP_RATIO;
|
||||||
ship_anim_progress = std::min(1.0f, ship_anim_progress);
|
ship_anim_progress = std::min(1.0f, ship_anim_progress);
|
||||||
|
|
||||||
// Actualitzar posició de la nau segons animació
|
// Actualitzar posició de la nau P1 segons animació (P2 apareix directamente)
|
||||||
if (ship_anim_progress < 1.0f) {
|
if (ship_anim_progress < 1.0f) {
|
||||||
Punt pos_animada = calcular_posicio_nau_init_hud(ship_anim_progress);
|
Punt pos_animada = calcular_posicio_nau_init_hud(ship_anim_progress);
|
||||||
nau_.set_centre(pos_animada);
|
naus_[0].set_centre(pos_animada); // Solo P1 animación
|
||||||
}
|
}
|
||||||
|
|
||||||
// Una vegada l'animació acaba, permetre control normal
|
// Una vegada l'animació acaba, permetre control normal
|
||||||
@@ -279,16 +300,20 @@ void EscenaJoc::actualitzar(float delta_time) {
|
|||||||
// [DEBUG] Log entrada a LEVEL_START
|
// [DEBUG] Log entrada a LEVEL_START
|
||||||
static bool first_entry = true;
|
static bool first_entry = true;
|
||||||
if (first_entry) {
|
if (first_entry) {
|
||||||
std::cout << "[LEVEL_START] ENTERED with pos.y=" << nau_.get_centre().y << std::endl;
|
std::cout << "[LEVEL_START] ENTERED with P1 pos.y=" << naus_[0].get_centre().y << std::endl;
|
||||||
first_entry = false;
|
first_entry = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update countdown timer
|
// Update countdown timer
|
||||||
stage_manager_->actualitzar(delta_time);
|
stage_manager_->actualitzar(delta_time);
|
||||||
|
|
||||||
// [NEW] Allow ship movement and shooting during intro
|
// [NEW] Allow both ships movement and shooting during intro
|
||||||
nau_.processar_input(delta_time);
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
nau_.actualitzar(delta_time);
|
if (itocado_per_jugador_[i] == 0.0f) { // Only alive players
|
||||||
|
naus_[i].processar_input(delta_time, i);
|
||||||
|
naus_[i].actualitzar(delta_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// [NEW] Update bullets
|
// [NEW] Update bullets
|
||||||
for (auto& bala : bales_) {
|
for (auto& bala : bales_) {
|
||||||
@@ -301,12 +326,13 @@ void EscenaJoc::actualitzar(float delta_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case StageSystem::EstatStage::PLAYING: {
|
case StageSystem::EstatStage::PLAYING: {
|
||||||
// [NEW] Update stage manager (spawns enemies, pass pause flag)
|
// [NEW] Update stage manager (spawns enemies, pause if BOTH dead)
|
||||||
bool pausar_spawn = (itocado_ > 0.0f); // Pause during death animation
|
bool pausar_spawn = (itocado_per_jugador_[0] > 0.0f && itocado_per_jugador_[1] > 0.0f);
|
||||||
stage_manager_->get_spawn_controller().actualitzar(delta_time, orni_, pausar_spawn);
|
stage_manager_->get_spawn_controller().actualitzar(delta_time, orni_, pausar_spawn);
|
||||||
|
|
||||||
// [NEW] Check stage completion (only when not in death sequence)
|
// [NEW] Check stage completion (only when at least one player alive)
|
||||||
if (itocado_ == 0.0f) {
|
bool algun_jugador_viu = (itocado_per_jugador_[0] == 0.0f || itocado_per_jugador_[1] == 0.0f);
|
||||||
|
if (algun_jugador_viu) {
|
||||||
auto& spawn_ctrl = stage_manager_->get_spawn_controller();
|
auto& spawn_ctrl = stage_manager_->get_spawn_controller();
|
||||||
if (spawn_ctrl.tots_enemics_destruits(orni_)) {
|
if (spawn_ctrl.tots_enemics_destruits(orni_)) {
|
||||||
stage_manager_->stage_completat();
|
stage_manager_->stage_completat();
|
||||||
@@ -315,9 +341,13 @@ void EscenaJoc::actualitzar(float delta_time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [EXISTING] Normal gameplay
|
// [EXISTING] Normal gameplay - update BOTH players
|
||||||
nau_.processar_input(delta_time);
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
nau_.actualitzar(delta_time);
|
if (itocado_per_jugador_[i] == 0.0f) { // Only alive players
|
||||||
|
naus_[i].processar_input(delta_time, i);
|
||||||
|
naus_[i].actualitzar(delta_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& enemy : orni_) {
|
for (auto& enemy : orni_) {
|
||||||
enemy.actualitzar(delta_time);
|
enemy.actualitzar(delta_time);
|
||||||
@@ -328,7 +358,7 @@ void EscenaJoc::actualitzar(float delta_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
detectar_col·lisions_bales_enemics();
|
detectar_col·lisions_bales_enemics();
|
||||||
detectar_col·lisio_nau_enemics();
|
detectar_col·lisio_naus_enemics();
|
||||||
debris_manager_.actualitzar(delta_time);
|
debris_manager_.actualitzar(delta_time);
|
||||||
gestor_puntuacio_.actualitzar(delta_time);
|
gestor_puntuacio_.actualitzar(delta_time);
|
||||||
break;
|
break;
|
||||||
@@ -338,9 +368,13 @@ void EscenaJoc::actualitzar(float delta_time) {
|
|||||||
// Update countdown timer
|
// Update countdown timer
|
||||||
stage_manager_->actualitzar(delta_time);
|
stage_manager_->actualitzar(delta_time);
|
||||||
|
|
||||||
// [NEW] Allow ship movement and shooting during outro
|
// [NEW] Allow both ships movement and shooting during outro
|
||||||
nau_.processar_input(delta_time);
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
nau_.actualitzar(delta_time);
|
if (itocado_per_jugador_[i] == 0.0f) { // Only alive players
|
||||||
|
naus_[i].processar_input(delta_time, i);
|
||||||
|
naus_[i].actualitzar(delta_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// [NEW] Update bullets (allow last shots to continue)
|
// [NEW] Update bullets (allow last shots to continue)
|
||||||
for (auto& bala : bales_) {
|
for (auto& bala : bales_) {
|
||||||
@@ -420,9 +454,10 @@ void EscenaJoc::dibuixar() {
|
|||||||
dibuixar_marcador_animat(score_progress);
|
dibuixar_marcador_animat(score_progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dibuixar nau (usant el sistema normal, la posició ja està actualitzada)
|
// Dibuixar nau P1 (usant el sistema normal, la posició ja està actualitzada)
|
||||||
if (ship_progress > 0.0f && !nau_.esta_tocada()) {
|
// Durante INIT_HUD solo se anima P1
|
||||||
nau_.dibuixar();
|
if (ship_progress > 0.0f && !naus_[0].esta_tocada()) {
|
||||||
|
naus_[0].dibuixar();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -430,9 +465,11 @@ void EscenaJoc::dibuixar() {
|
|||||||
|
|
||||||
case StageSystem::EstatStage::LEVEL_START:
|
case StageSystem::EstatStage::LEVEL_START:
|
||||||
dibuixar_marges();
|
dibuixar_marges();
|
||||||
// [NEW] Draw ship if alive
|
// [NEW] Draw both ships if alive
|
||||||
if (itocado_ == 0.0f) {
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
nau_.dibuixar();
|
if (itocado_per_jugador_[i] == 0.0f) {
|
||||||
|
naus_[i].dibuixar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [NEW] Draw bullets
|
// [NEW] Draw bullets
|
||||||
@@ -452,9 +489,11 @@ void EscenaJoc::dibuixar() {
|
|||||||
case StageSystem::EstatStage::PLAYING:
|
case StageSystem::EstatStage::PLAYING:
|
||||||
dibuixar_marges();
|
dibuixar_marges();
|
||||||
|
|
||||||
// [EXISTING] Normal rendering
|
// [EXISTING] Normal rendering - both ships
|
||||||
if (itocado_ == 0.0f) {
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
nau_.dibuixar();
|
if (itocado_per_jugador_[i] == 0.0f) {
|
||||||
|
naus_[i].dibuixar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& enemy : orni_) {
|
for (const auto& enemy : orni_) {
|
||||||
@@ -472,9 +511,11 @@ void EscenaJoc::dibuixar() {
|
|||||||
|
|
||||||
case StageSystem::EstatStage::LEVEL_COMPLETED:
|
case StageSystem::EstatStage::LEVEL_COMPLETED:
|
||||||
dibuixar_marges();
|
dibuixar_marges();
|
||||||
// [NEW] Draw ship if alive
|
// [NEW] Draw both ships if alive
|
||||||
if (itocado_ == 0.0f) {
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
nau_.dibuixar();
|
if (itocado_per_jugador_[i] == 0.0f) {
|
||||||
|
naus_[i].dibuixar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [NEW] Draw bullets (allow last shots to be visible)
|
// [NEW] Draw bullets (allow last shots to be visible)
|
||||||
@@ -499,97 +540,60 @@ void EscenaJoc::processar_input(const SDL_Event& event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore ship controls during death sequence
|
|
||||||
if (itocado_ > 0.0f) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processament d'input per events puntuals (no continus)
|
// Processament d'input per events puntuals (no continus)
|
||||||
// L'input continu (fletxes) es processa en actualitzar() amb
|
// L'input continu (fletxes/WASD) es processa en actualitzar() amb
|
||||||
// SDL_GetKeyboardState()
|
// SDL_GetKeyboardState()
|
||||||
|
|
||||||
if (event.type == SDL_EVENT_KEY_DOWN) {
|
if (event.type == SDL_EVENT_KEY_DOWN) {
|
||||||
switch (event.key.key) {
|
// P1 shoot
|
||||||
case SDLK_SPACE: {
|
if (event.key.key == Defaults::Controls::P1::SHOOT) {
|
||||||
// No disparar si la nau està morta
|
disparar_bala(0);
|
||||||
if (!nau_.esta_viva()) {
|
}
|
||||||
break;
|
// P2 shoot
|
||||||
}
|
else if (event.key.key == Defaults::Controls::P2::SHOOT) {
|
||||||
|
disparar_bala(1);
|
||||||
// Disparar bala des del front de la nau
|
|
||||||
// El ship.shp té el front a (0, -12) en coordenades locals
|
|
||||||
|
|
||||||
// 1. Calcular posició del front de la nau
|
|
||||||
constexpr float LOCAL_TIP_X = 0.0f;
|
|
||||||
constexpr float LOCAL_TIP_Y = -12.0f;
|
|
||||||
|
|
||||||
const Punt& ship_centre = nau_.get_centre();
|
|
||||||
float ship_angle = nau_.get_angle();
|
|
||||||
|
|
||||||
// Aplicar transformació: rotació + trasllació
|
|
||||||
float cos_a = std::cos(ship_angle);
|
|
||||||
float sin_a = std::sin(ship_angle);
|
|
||||||
|
|
||||||
float tip_x = LOCAL_TIP_X * cos_a - LOCAL_TIP_Y * sin_a + ship_centre.x;
|
|
||||||
float tip_y = LOCAL_TIP_X * sin_a + LOCAL_TIP_Y * cos_a + ship_centre.y;
|
|
||||||
|
|
||||||
Punt posicio_dispar = {tip_x, tip_y};
|
|
||||||
|
|
||||||
// 2. Buscar primera bala inactiva i disparar
|
|
||||||
for (auto& bala : bales_) {
|
|
||||||
if (!bala.esta_activa()) {
|
|
||||||
bala.disparar(posicio_dispar, ship_angle);
|
|
||||||
break; // Només una bala per polsació
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::tocado() {
|
void EscenaJoc::tocado(uint8_t player_id) {
|
||||||
// Death sequence: 3 phases
|
// Death sequence: 3 phases
|
||||||
// Phase 1: First call (itocado_ == 0) - trigger explosion
|
// Phase 1: First call (itocado_per_jugador_[player_id] == 0) - trigger explosion
|
||||||
// Phase 2: Animation (0 < itocado_ < 3.0s) - debris animation
|
// Phase 2: Animation (0 < itocado_ < 3.0s) - debris animation
|
||||||
// Phase 3: Respawn or game over (itocado_ >= 3.0s) - handled in actualitzar()
|
// Phase 3: Respawn or game over (itocado_ >= 3.0s) - handled in actualitzar()
|
||||||
|
|
||||||
if (itocado_ == 0.0f) {
|
if (itocado_per_jugador_[player_id] == 0.0f) {
|
||||||
// *** PHASE 1: TRIGGER DEATH ***
|
// *** PHASE 1: TRIGGER DEATH ***
|
||||||
|
|
||||||
// Guardar posición de muerte para respawn
|
|
||||||
punt_mort_ = nau_.get_centre();
|
|
||||||
|
|
||||||
// Mark ship as dead (stops rendering and input)
|
// Mark ship as dead (stops rendering and input)
|
||||||
nau_.marcar_tocada();
|
naus_[player_id].marcar_tocada();
|
||||||
|
|
||||||
// Create ship explosion
|
// Create ship explosion
|
||||||
const Punt& ship_pos = nau_.get_centre();
|
const Punt& ship_pos = naus_[player_id].get_centre();
|
||||||
float ship_angle = nau_.get_angle();
|
float ship_angle = naus_[player_id].get_angle();
|
||||||
Punt vel_nau = nau_.get_velocitat_vector();
|
Punt vel_nau = naus_[player_id].get_velocitat_vector();
|
||||||
// Reduir a 80% la velocitat heretada per la nau (més realista)
|
// Reduir a 80% la velocitat heretada per la nau (més realista)
|
||||||
Punt vel_nau_80 = {vel_nau.x * 0.8f, vel_nau.y * 0.8f};
|
Punt vel_nau_80 = {vel_nau.x * 0.8f, vel_nau.y * 0.8f};
|
||||||
|
|
||||||
debris_manager_.explotar(
|
debris_manager_.explotar(
|
||||||
nau_.get_forma(), // Ship shape (3 lines)
|
naus_[player_id].get_forma(), // Ship shape (3 lines)
|
||||||
ship_pos, // Center position
|
ship_pos, // Center position
|
||||||
ship_angle, // Ship orientation
|
ship_angle, // Ship orientation
|
||||||
1.0f, // Normal scale
|
1.0f, // Normal scale
|
||||||
Defaults::Physics::Debris::VELOCITAT_BASE, // 80 px/s
|
Defaults::Physics::Debris::VELOCITAT_BASE, // 80 px/s
|
||||||
nau_.get_brightness(), // Heredar brightness
|
naus_[player_id].get_brightness(), // Heredar brightness
|
||||||
vel_nau_80, // Heredar 80% velocitat
|
vel_nau_80, // Heredar 80% velocitat
|
||||||
0.0f, // Nave: trayectorias rectas (sin drotacio)
|
0.0f, // Nave: trayectorias rectas (sin drotacio)
|
||||||
0.0f // Sin herencia visual (rotación aleatoria)
|
0.0f // Sin herencia visual (rotación aleatoria)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Audio::get()->playSound(Defaults::Sound::EXPLOSION, Audio::Group::GAME);
|
||||||
|
|
||||||
// Start death timer (non-zero to avoid re-triggering)
|
// Start death timer (non-zero to avoid re-triggering)
|
||||||
itocado_ = 0.001f;
|
itocado_per_jugador_[player_id] = 0.001f;
|
||||||
}
|
}
|
||||||
// Phase 2 is automatic (debris updates in actualitzar())
|
// Phase 2 is automatic (debris updates in actualitzar())
|
||||||
// Phase 3 is handled in actualitzar() when itocado_ >= DEATH_DURATION
|
// Phase 3 is handled in actualitzar() when itocado_per_jugador_ >= DEATH_DURATION
|
||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::dibuixar_marges() const {
|
void EscenaJoc::dibuixar_marges() const {
|
||||||
@@ -750,26 +754,30 @@ Punt EscenaJoc::calcular_posicio_nau_init_hud(float progress) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string EscenaJoc::construir_marcador() const {
|
std::string EscenaJoc::construir_marcador() const {
|
||||||
// Puntuació P1 (5 dígits)
|
// Puntuació P1 (6 dígits)
|
||||||
std::string score_p1 = std::to_string(puntuacio_total_);
|
std::string score_p1 = std::to_string(puntuacio_per_jugador_[0]);
|
||||||
score_p1 = std::string(6 - std::min(6, static_cast<int>(score_p1.length())), '0') + score_p1;
|
score_p1 = std::string(6 - std::min(6, static_cast<int>(score_p1.length())), '0') + score_p1;
|
||||||
|
|
||||||
// Vides P1 (2 dígits)
|
// Vides P1 (2 dígits)
|
||||||
std::string vides_p1 = (num_vides_ < 10) ? "0" + std::to_string(num_vides_)
|
std::string vides_p1 = (vides_per_jugador_[0] < 10)
|
||||||
: std::to_string(num_vides_);
|
? "0" + std::to_string(vides_per_jugador_[0])
|
||||||
|
: std::to_string(vides_per_jugador_[0]);
|
||||||
|
|
||||||
// Nivell (2 dígits)
|
// Nivell (2 dígits)
|
||||||
uint8_t stage_num = stage_manager_->get_stage_actual();
|
uint8_t stage_num = stage_manager_->get_stage_actual();
|
||||||
std::string stage_str = (stage_num < 10) ? "0" + std::to_string(stage_num)
|
std::string stage_str = (stage_num < 10) ? "0" + std::to_string(stage_num)
|
||||||
: std::to_string(stage_num);
|
: std::to_string(stage_num);
|
||||||
|
|
||||||
// Puntuació P2 (sempre 00000 per ara)
|
// Puntuació P2 (6 dígits)
|
||||||
std::string score_p2 = "000000";
|
std::string score_p2 = std::to_string(puntuacio_per_jugador_[1]);
|
||||||
|
score_p2 = std::string(6 - std::min(6, static_cast<int>(score_p2.length())), '0') + score_p2;
|
||||||
|
|
||||||
// Vides P2 (sempre 03 per ara)
|
// Vides P2 (2 dígits)
|
||||||
std::string vides_p2 = "03";
|
std::string vides_p2 = (vides_per_jugador_[1] < 10)
|
||||||
|
? "0" + std::to_string(vides_per_jugador_[1])
|
||||||
|
: std::to_string(vides_per_jugador_[1]);
|
||||||
|
|
||||||
// Format: "12345 03 LEVEL 01 00000 03"
|
// Format: "123456 03 LEVEL 01 654321 02"
|
||||||
// Nota: dos espais entre seccions
|
// Nota: dos espais entre seccions
|
||||||
return score_p1 + " " + vides_p1 + " LEVEL " + stage_str + " " + score_p2 + " " + vides_p2;
|
return score_p1 + " " + vides_p1 + " LEVEL " + stage_str + " " + score_p2 + " " + vides_p2;
|
||||||
}
|
}
|
||||||
@@ -823,8 +831,9 @@ void EscenaJoc::detectar_col·lisions_bales_enemics() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Add to total score
|
// 2. Add score to the player who shot it
|
||||||
puntuacio_total_ += punts;
|
uint8_t owner_id = bala.get_owner_id();
|
||||||
|
puntuacio_per_jugador_[owner_id] += punts;
|
||||||
|
|
||||||
// 3. Create floating score number
|
// 3. Create floating score number
|
||||||
gestor_puntuacio_.crear(punts, pos_enemic);
|
gestor_puntuacio_.crear(punts, pos_enemic);
|
||||||
@@ -856,12 +865,7 @@ void EscenaJoc::detectar_col·lisions_bales_enemics() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::detectar_col·lisio_nau_enemics() {
|
void EscenaJoc::detectar_col·lisio_naus_enemics() {
|
||||||
// Skip collisions if ship is dead or invulnerable
|
|
||||||
if (!nau_.esta_viva() || nau_.es_invulnerable()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generous collision detection (80% hitbox)
|
// Generous collision detection (80% hitbox)
|
||||||
constexpr float RADI_NAU = Defaults::Entities::SHIP_RADIUS;
|
constexpr float RADI_NAU = Defaults::Entities::SHIP_RADIUS;
|
||||||
constexpr float RADI_ENEMIC = Defaults::Entities::ENEMY_RADIUS;
|
constexpr float RADI_ENEMIC = Defaults::Entities::ENEMY_RADIUS;
|
||||||
@@ -869,30 +873,38 @@ void EscenaJoc::detectar_col·lisio_nau_enemics() {
|
|||||||
(RADI_NAU + RADI_ENEMIC) * Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER;
|
(RADI_NAU + RADI_ENEMIC) * Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER;
|
||||||
constexpr float SUMA_RADIS_QUADRAT = SUMA_RADIS * SUMA_RADIS;
|
constexpr float SUMA_RADIS_QUADRAT = SUMA_RADIS * SUMA_RADIS;
|
||||||
|
|
||||||
const Punt& pos_nau = nau_.get_centre();
|
// Check collision for BOTH players
|
||||||
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
|
// Skip collisions if player is dead or invulnerable
|
||||||
|
if (itocado_per_jugador_[i] > 0.0f) continue;
|
||||||
|
if (!naus_[i].esta_viva()) continue;
|
||||||
|
if (naus_[i].es_invulnerable()) continue;
|
||||||
|
|
||||||
// Check collision with all active enemies
|
const Punt& pos_nau = naus_[i].get_centre();
|
||||||
for (const auto& enemic : orni_) {
|
|
||||||
if (!enemic.esta_actiu()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [NEW] Skip collision if enemy is invulnerable
|
// Check collision with all active enemies
|
||||||
if (enemic.es_invulnerable()) {
|
for (const auto& enemic : orni_) {
|
||||||
continue;
|
if (!enemic.esta_actiu()) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const Punt& pos_enemic = enemic.get_centre();
|
// Skip collision if enemy is invulnerable
|
||||||
|
if (enemic.es_invulnerable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate squared distance (avoid sqrt)
|
const Punt& pos_enemic = enemic.get_centre();
|
||||||
float dx = static_cast<float>(pos_nau.x - pos_enemic.x);
|
|
||||||
float dy = static_cast<float>(pos_nau.y - pos_enemic.y);
|
|
||||||
float distancia_quadrada = dx * dx + dy * dy;
|
|
||||||
|
|
||||||
// Check collision
|
// Calculate squared distance (avoid sqrt)
|
||||||
if (distancia_quadrada <= SUMA_RADIS_QUADRAT) {
|
float dx = static_cast<float>(pos_nau.x - pos_enemic.x);
|
||||||
tocado(); // Trigger death sequence
|
float dy = static_cast<float>(pos_nau.y - pos_enemic.y);
|
||||||
return; // Only one collision per frame
|
float distancia_quadrada = dx * dx + dy * dy;
|
||||||
|
|
||||||
|
// Check collision
|
||||||
|
if (distancia_quadrada <= SUMA_RADIS_QUADRAT) {
|
||||||
|
tocado(i); // Trigger death sequence for player i
|
||||||
|
break; // Only one collision per player per frame
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -964,3 +976,46 @@ void EscenaJoc::dibuixar_missatge_stage(const std::string& missatge) {
|
|||||||
Punt pos = {x, y};
|
Punt pos = {x, y};
|
||||||
text_.render(partial_message, pos, escala, spacing);
|
text_.render(partial_message, pos, escala, spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Helper methods for 2-player support
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
Punt EscenaJoc::obtenir_punt_spawn(uint8_t player_id) const {
|
||||||
|
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
||||||
|
float x_ratio = (player_id == 0)
|
||||||
|
? Defaults::Game::P1_SPAWN_X_RATIO
|
||||||
|
: Defaults::Game::P2_SPAWN_X_RATIO;
|
||||||
|
|
||||||
|
return {
|
||||||
|
zona.x + zona.w * x_ratio,
|
||||||
|
zona.y + zona.h * Defaults::Game::SPAWN_Y_RATIO
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void EscenaJoc::disparar_bala(uint8_t player_id) {
|
||||||
|
// Verificar que el jugador está vivo
|
||||||
|
if (itocado_per_jugador_[player_id] > 0.0f) return;
|
||||||
|
if (!naus_[player_id].esta_viva()) return;
|
||||||
|
|
||||||
|
// Calcular posición en la punta de la nave
|
||||||
|
const Punt& ship_centre = naus_[player_id].get_centre();
|
||||||
|
float ship_angle = naus_[player_id].get_angle();
|
||||||
|
|
||||||
|
constexpr float LOCAL_TIP_X = 0.0f;
|
||||||
|
constexpr float LOCAL_TIP_Y = -12.0f;
|
||||||
|
float cos_a = std::cos(ship_angle);
|
||||||
|
float sin_a = std::sin(ship_angle);
|
||||||
|
float tip_x = LOCAL_TIP_X * cos_a - LOCAL_TIP_Y * sin_a + ship_centre.x;
|
||||||
|
float tip_y = LOCAL_TIP_X * sin_a + LOCAL_TIP_Y * cos_a + ship_centre.y;
|
||||||
|
Punt posicio_dispar = {tip_x, tip_y};
|
||||||
|
|
||||||
|
// Buscar primera bala inactiva en el pool del jugador
|
||||||
|
int start_idx = player_id * 3; // P1=[0,1,2], P2=[3,4,5]
|
||||||
|
for (int i = start_idx; i < start_idx + 3; i++) {
|
||||||
|
if (!bales_[i].esta_activa()) {
|
||||||
|
bales_[i].disparar(posicio_dispar, ship_angle, player_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,19 +45,19 @@ class EscenaJoc {
|
|||||||
Effects::GestorPuntuacioFlotant gestor_puntuacio_;
|
Effects::GestorPuntuacioFlotant gestor_puntuacio_;
|
||||||
|
|
||||||
// Estat del joc
|
// Estat del joc
|
||||||
Nau nau_;
|
std::array<Nau, 2> naus_; // [0]=P1, [1]=P2
|
||||||
std::array<Enemic, Constants::MAX_ORNIS> orni_;
|
std::array<Enemic, Constants::MAX_ORNIS> orni_;
|
||||||
std::array<Bala, Constants::MAX_BALES> bales_;
|
std::array<Bala, Constants::MAX_BALES * 2> bales_; // 6 balas: P1=[0,1,2], P2=[3,4,5]
|
||||||
Poligon chatarra_cosmica_;
|
Poligon chatarra_cosmica_;
|
||||||
float itocado_; // Death timer (seconds)
|
std::array<float, 2> itocado_per_jugador_; // Death timers per player (seconds)
|
||||||
|
|
||||||
// Lives and game over system
|
// Lives and game over system
|
||||||
int num_vides_; // Current lives count
|
std::array<int, 2> vides_per_jugador_; // [0]=P1, [1]=P2
|
||||||
bool game_over_; // Game over state flag
|
bool game_over_; // Game over state flag
|
||||||
float game_over_timer_; // Countdown timer for auto-return (seconds)
|
float game_over_timer_; // Countdown timer for auto-return (seconds)
|
||||||
Punt punt_spawn_; // Configurable spawn point
|
Punt punt_spawn_; // Configurable spawn point (legacy)
|
||||||
Punt punt_mort_; // Death position (for respawn)
|
Punt punt_mort_; // Death position (for respawn, legacy)
|
||||||
int puntuacio_total_; // Current score
|
std::array<int, 2> puntuacio_per_jugador_; // [0]=P1, [1]=P2
|
||||||
|
|
||||||
// Text vectorial
|
// Text vectorial
|
||||||
Graphics::VectorText text_;
|
Graphics::VectorText text_;
|
||||||
@@ -67,11 +67,13 @@ class EscenaJoc {
|
|||||||
std::unique_ptr<StageSystem::StageManager> stage_manager_;
|
std::unique_ptr<StageSystem::StageManager> stage_manager_;
|
||||||
|
|
||||||
// Funcions privades
|
// Funcions privades
|
||||||
void tocado();
|
void tocado(uint8_t player_id);
|
||||||
void detectar_col·lisions_bales_enemics(); // Col·lisions bala-enemic
|
void detectar_col·lisions_bales_enemics(); // Col·lisions bala-enemic
|
||||||
void detectar_col·lisio_nau_enemics(); // Ship-enemy collision detection
|
void detectar_col·lisio_naus_enemics(); // Ship-enemy collision detection (plural)
|
||||||
void dibuixar_marges() const; // Dibuixar vores de la zona de joc
|
void dibuixar_marges() const; // Dibuixar vores de la zona de joc
|
||||||
void dibuixar_marcador(); // Dibuixar marcador de puntuació
|
void dibuixar_marcador(); // Dibuixar marcador de puntuació
|
||||||
|
void disparar_bala(uint8_t player_id); // Shoot bullet from player
|
||||||
|
Punt obtenir_punt_spawn(uint8_t player_id) const; // Get spawn position for player
|
||||||
|
|
||||||
// [NEW] Stage system helpers
|
// [NEW] Stage system helpers
|
||||||
void dibuixar_missatge_stage(const std::string& missatge);
|
void dibuixar_missatge_stage(const std::string& missatge);
|
||||||
|
|||||||
Reference in New Issue
Block a user