afegit segon jugador
This commit is contained in:
@@ -36,7 +36,7 @@ void Bala::inicialitzar() {
|
||||
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
|
||||
// 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;
|
||||
|
||||
// Almacenar propietario (0=P1, 1=P2)
|
||||
owner_id_ = owner_id;
|
||||
|
||||
// Velocitat alta (el joc Pascal original usava 7 px/frame)
|
||||
// 7 px/frame × 20 FPS = 140 px/s
|
||||
velocitat_ = 140.0f;
|
||||
|
||||
@@ -17,13 +17,14 @@ class Bala {
|
||||
Bala(SDL_Renderer* renderer);
|
||||
|
||||
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 dibuixar() const;
|
||||
|
||||
// Getters (API pública sense canvis)
|
||||
bool esta_activa() const { return esta_; }
|
||||
const Punt& get_centre() const { return centre_; }
|
||||
uint8_t get_owner_id() const { return owner_id_; }
|
||||
void desactivar() { esta_ = false; }
|
||||
|
||||
private:
|
||||
@@ -37,6 +38,7 @@ class Bala {
|
||||
float angle_;
|
||||
float velocitat_;
|
||||
bool esta_;
|
||||
uint8_t owner_id_; // 0=P1, 1=P2
|
||||
float brightness_; // Factor de brillantor (0.0-1.0)
|
||||
|
||||
void mou(float delta_time);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include "core/rendering/shape_renderer.hpp"
|
||||
#include "game/constants.hpp"
|
||||
|
||||
Nau::Nau(SDL_Renderer* renderer)
|
||||
Nau::Nau(SDL_Renderer* renderer, const char* shape_file)
|
||||
: renderer_(renderer),
|
||||
centre_({0.0f, 0.0f}),
|
||||
angle_(0.0f),
|
||||
@@ -23,10 +23,10 @@ Nau::Nau(SDL_Renderer* renderer)
|
||||
brightness_(Defaults::Brightness::NAU),
|
||||
invulnerable_timer_(0.0f) {
|
||||
// [NUEVO] Carregar forma compartida des de fitxer
|
||||
forma_ = Graphics::ShapeLoader::load("ship.shp");
|
||||
forma_ = Graphics::ShapeLoader::load(shape_file);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
// Basat en joc_asteroides.cpp línies 66-85
|
||||
// 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)
|
||||
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ó
|
||||
if (keyboard_state[SDL_SCANCODE_RIGHT]) {
|
||||
if (keyboard_state[key_right]) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Acceleració
|
||||
if (keyboard_state[SDL_SCANCODE_UP]) {
|
||||
if (keyboard_state[key_thrust]) {
|
||||
if (velocitat_ < Defaults::Physics::MAX_VELOCITY) {
|
||||
velocitat_ += Defaults::Physics::ACCELERATION * delta_time;
|
||||
if (velocitat_ > Defaults::Physics::MAX_VELOCITY) {
|
||||
|
||||
@@ -15,10 +15,10 @@ class Nau {
|
||||
public:
|
||||
Nau()
|
||||
: 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 processar_input(float delta_time);
|
||||
void processar_input(float delta_time, uint8_t player_id);
|
||||
void actualitzar(float delta_time);
|
||||
void dibuixar() const;
|
||||
|
||||
|
||||
@@ -28,14 +28,15 @@ EscenaJoc::EscenaJoc(SDLManager& sdl, ContextEscenes& context)
|
||||
context_(context),
|
||||
debris_manager_(sdl.obte_renderer()),
|
||||
gestor_puntuacio_(sdl.obte_renderer()),
|
||||
nau_(sdl.obte_renderer()),
|
||||
itocado_(0),
|
||||
puntuacio_total_(0),
|
||||
text_(sdl.obte_renderer()) {
|
||||
// Consumir opcions (preparació per MODE_DEMO futur)
|
||||
auto opcio = context_.consumir_opcio();
|
||||
(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
|
||||
for (auto& bala : bales_) {
|
||||
bala = Bala(sdl.obte_renderer());
|
||||
@@ -132,37 +133,43 @@ void EscenaJoc::inicialitzar() {
|
||||
stage_manager_ = std::make_unique<StageSystem::StageManager>(stage_config_.get());
|
||||
stage_manager_->inicialitzar();
|
||||
|
||||
// [NEW] Set ship position reference for safe spawn
|
||||
stage_manager_->get_spawn_controller().set_ship_position(&nau_.get_centre());
|
||||
// [NEW] Set ship position reference for safe spawn (P1 for now, TODO: dual tracking)
|
||||
stage_manager_->get_spawn_controller().set_ship_position(&naus_[0].get_centre());
|
||||
|
||||
// Inicialitzar estat de col·lisió
|
||||
itocado_ = 0;
|
||||
// Inicialitzar timers de muerte per jugador
|
||||
itocado_per_jugador_[0] = 0.0f;
|
||||
itocado_per_jugador_[1] = 0.0f;
|
||||
|
||||
// Initialize lives and game over state
|
||||
num_vides_ = Defaults::Game::STARTING_LIVES;
|
||||
// Initialize lives and game over state (independent lives per player)
|
||||
vides_per_jugador_[0] = Defaults::Game::STARTING_LIVES;
|
||||
vides_per_jugador_[1] = Defaults::Game::STARTING_LIVES;
|
||||
game_over_ = false;
|
||||
game_over_timer_ = 0.0f;
|
||||
|
||||
// Initialize score
|
||||
puntuacio_total_ = 0;
|
||||
// Initialize scores (separate per player)
|
||||
puntuacio_per_jugador_[0] = 0;
|
||||
puntuacio_per_jugador_[1] = 0;
|
||||
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;
|
||||
punt_spawn_.x = zona.x + zona.w * 0.5f;
|
||||
punt_spawn_.y = zona.y + zona.h * Defaults::Game::INIT_HUD_SHIP_START_Y_RATIO;
|
||||
|
||||
// Inicialitzar nau amb posició especial
|
||||
nau_.inicialitzar(&punt_spawn_);
|
||||
// Inicialitzar AMBAS naus amb posicions específiques
|
||||
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)
|
||||
for (auto& enemy : orni_) {
|
||||
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
|
||||
}
|
||||
|
||||
// Inicialitzar bales
|
||||
// Inicialitzar bales (now 6 instead of 3)
|
||||
for (auto& bala : bales_) {
|
||||
bala.inicialitzar();
|
||||
}
|
||||
@@ -201,30 +208,43 @@ void EscenaJoc::actualitzar(float delta_time) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check death sequence state
|
||||
if (itocado_ > 0.0f) {
|
||||
// Death sequence active: update timer
|
||||
itocado_ += delta_time;
|
||||
// Check death sequence state for BOTH players
|
||||
bool algun_jugador_mort = false;
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
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
|
||||
if (itocado_ >= Defaults::Game::DEATH_DURATION) {
|
||||
// *** PHASE 3: RESPAWN OR GAME OVER ***
|
||||
// Check if death duration completed (only trigger ONCE using sentinel value)
|
||||
if (itocado_per_jugador_[i] >= Defaults::Game::DEATH_DURATION) {
|
||||
// *** PHASE 3: RESPAWN OR GAME OVER ***
|
||||
|
||||
// Decrement lives
|
||||
num_vides_--;
|
||||
// Decrement lives for this player (only once)
|
||||
vides_per_jugador_[i]--;
|
||||
|
||||
if (num_vides_ > 0) {
|
||||
// Respawn ship en posición de muerte con invulnerabilidad
|
||||
nau_.inicialitzar(&punt_mort_, true);
|
||||
itocado_ = 0.0f;
|
||||
} else {
|
||||
// Game over
|
||||
game_over_ = true;
|
||||
game_over_timer_ = Defaults::Game::GAME_OVER_DURATION;
|
||||
itocado_ = 0.0f;
|
||||
if (vides_per_jugador_[i] > 0) {
|
||||
// Respawn ship en spawn position con invulnerabilidad
|
||||
Punt spawn_pos = obtenir_punt_spawn(i);
|
||||
naus_[i].inicialitzar(&spawn_pos, true);
|
||||
itocado_per_jugador_[i] = 0.0f;
|
||||
} else {
|
||||
// Player is permanently dead (out of lives)
|
||||
// Set sentinel value to prevent re-entering this block
|
||||
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
|
||||
for (auto& enemy : orni_) {
|
||||
enemy.actualitzar(delta_time);
|
||||
@@ -236,7 +256,8 @@ void EscenaJoc::actualitzar(float delta_time) {
|
||||
|
||||
debris_manager_.actualitzar(delta_time);
|
||||
gestor_puntuacio_.actualitzar(delta_time);
|
||||
return;
|
||||
|
||||
// Don't return - allow alive players to continue playing
|
||||
}
|
||||
|
||||
// *** 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;
|
||||
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) {
|
||||
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
|
||||
@@ -279,16 +300,20 @@ void EscenaJoc::actualitzar(float delta_time) {
|
||||
// [DEBUG] Log entrada a LEVEL_START
|
||||
static bool first_entry = true;
|
||||
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;
|
||||
}
|
||||
|
||||
// Update countdown timer
|
||||
stage_manager_->actualitzar(delta_time);
|
||||
|
||||
// [NEW] Allow ship movement and shooting during intro
|
||||
nau_.processar_input(delta_time);
|
||||
nau_.actualitzar(delta_time);
|
||||
// [NEW] Allow both ships movement and shooting during intro
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
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
|
||||
for (auto& bala : bales_) {
|
||||
@@ -301,12 +326,13 @@ void EscenaJoc::actualitzar(float delta_time) {
|
||||
}
|
||||
|
||||
case StageSystem::EstatStage::PLAYING: {
|
||||
// [NEW] Update stage manager (spawns enemies, pass pause flag)
|
||||
bool pausar_spawn = (itocado_ > 0.0f); // Pause during death animation
|
||||
// [NEW] Update stage manager (spawns enemies, pause if BOTH dead)
|
||||
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);
|
||||
|
||||
// [NEW] Check stage completion (only when not in death sequence)
|
||||
if (itocado_ == 0.0f) {
|
||||
// [NEW] Check stage completion (only when at least one player alive)
|
||||
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();
|
||||
if (spawn_ctrl.tots_enemics_destruits(orni_)) {
|
||||
stage_manager_->stage_completat();
|
||||
@@ -315,9 +341,13 @@ void EscenaJoc::actualitzar(float delta_time) {
|
||||
}
|
||||
}
|
||||
|
||||
// [EXISTING] Normal gameplay
|
||||
nau_.processar_input(delta_time);
|
||||
nau_.actualitzar(delta_time);
|
||||
// [EXISTING] Normal gameplay - update BOTH players
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
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_) {
|
||||
enemy.actualitzar(delta_time);
|
||||
@@ -328,7 +358,7 @@ void EscenaJoc::actualitzar(float delta_time) {
|
||||
}
|
||||
|
||||
detectar_col·lisions_bales_enemics();
|
||||
detectar_col·lisio_nau_enemics();
|
||||
detectar_col·lisio_naus_enemics();
|
||||
debris_manager_.actualitzar(delta_time);
|
||||
gestor_puntuacio_.actualitzar(delta_time);
|
||||
break;
|
||||
@@ -338,9 +368,13 @@ void EscenaJoc::actualitzar(float delta_time) {
|
||||
// Update countdown timer
|
||||
stage_manager_->actualitzar(delta_time);
|
||||
|
||||
// [NEW] Allow ship movement and shooting during outro
|
||||
nau_.processar_input(delta_time);
|
||||
nau_.actualitzar(delta_time);
|
||||
// [NEW] Allow both ships movement and shooting during outro
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
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)
|
||||
for (auto& bala : bales_) {
|
||||
@@ -420,9 +454,10 @@ void EscenaJoc::dibuixar() {
|
||||
dibuixar_marcador_animat(score_progress);
|
||||
}
|
||||
|
||||
// Dibuixar nau (usant el sistema normal, la posició ja està actualitzada)
|
||||
if (ship_progress > 0.0f && !nau_.esta_tocada()) {
|
||||
nau_.dibuixar();
|
||||
// Dibuixar nau P1 (usant el sistema normal, la posició ja està actualitzada)
|
||||
// Durante INIT_HUD solo se anima P1
|
||||
if (ship_progress > 0.0f && !naus_[0].esta_tocada()) {
|
||||
naus_[0].dibuixar();
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -430,9 +465,11 @@ void EscenaJoc::dibuixar() {
|
||||
|
||||
case StageSystem::EstatStage::LEVEL_START:
|
||||
dibuixar_marges();
|
||||
// [NEW] Draw ship if alive
|
||||
if (itocado_ == 0.0f) {
|
||||
nau_.dibuixar();
|
||||
// [NEW] Draw both ships if alive
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
if (itocado_per_jugador_[i] == 0.0f) {
|
||||
naus_[i].dibuixar();
|
||||
}
|
||||
}
|
||||
|
||||
// [NEW] Draw bullets
|
||||
@@ -452,9 +489,11 @@ void EscenaJoc::dibuixar() {
|
||||
case StageSystem::EstatStage::PLAYING:
|
||||
dibuixar_marges();
|
||||
|
||||
// [EXISTING] Normal rendering
|
||||
if (itocado_ == 0.0f) {
|
||||
nau_.dibuixar();
|
||||
// [EXISTING] Normal rendering - both ships
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
if (itocado_per_jugador_[i] == 0.0f) {
|
||||
naus_[i].dibuixar();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& enemy : orni_) {
|
||||
@@ -472,9 +511,11 @@ void EscenaJoc::dibuixar() {
|
||||
|
||||
case StageSystem::EstatStage::LEVEL_COMPLETED:
|
||||
dibuixar_marges();
|
||||
// [NEW] Draw ship if alive
|
||||
if (itocado_ == 0.0f) {
|
||||
nau_.dibuixar();
|
||||
// [NEW] Draw both ships if alive
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
if (itocado_per_jugador_[i] == 0.0f) {
|
||||
naus_[i].dibuixar();
|
||||
}
|
||||
}
|
||||
|
||||
// [NEW] Draw bullets (allow last shots to be visible)
|
||||
@@ -499,97 +540,60 @@ void EscenaJoc::processar_input(const SDL_Event& event) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore ship controls during death sequence
|
||||
if (itocado_ > 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
if (event.type == SDL_EVENT_KEY_DOWN) {
|
||||
switch (event.key.key) {
|
||||
case SDLK_SPACE: {
|
||||
// No disparar si la nau està morta
|
||||
if (!nau_.esta_viva()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// P1 shoot
|
||||
if (event.key.key == Defaults::Controls::P1::SHOOT) {
|
||||
disparar_bala(0);
|
||||
}
|
||||
// P2 shoot
|
||||
else if (event.key.key == Defaults::Controls::P2::SHOOT) {
|
||||
disparar_bala(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EscenaJoc::tocado() {
|
||||
void EscenaJoc::tocado(uint8_t player_id) {
|
||||
// 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 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 ***
|
||||
|
||||
// Guardar posición de muerte para respawn
|
||||
punt_mort_ = nau_.get_centre();
|
||||
|
||||
// Mark ship as dead (stops rendering and input)
|
||||
nau_.marcar_tocada();
|
||||
naus_[player_id].marcar_tocada();
|
||||
|
||||
// Create ship explosion
|
||||
const Punt& ship_pos = nau_.get_centre();
|
||||
float ship_angle = nau_.get_angle();
|
||||
Punt vel_nau = nau_.get_velocitat_vector();
|
||||
const Punt& ship_pos = naus_[player_id].get_centre();
|
||||
float ship_angle = naus_[player_id].get_angle();
|
||||
Punt vel_nau = naus_[player_id].get_velocitat_vector();
|
||||
// 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};
|
||||
|
||||
debris_manager_.explotar(
|
||||
nau_.get_forma(), // Ship shape (3 lines)
|
||||
naus_[player_id].get_forma(), // Ship shape (3 lines)
|
||||
ship_pos, // Center position
|
||||
ship_angle, // Ship orientation
|
||||
1.0f, // Normal scale
|
||||
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
|
||||
0.0f, // Nave: trayectorias rectas (sin drotacio)
|
||||
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)
|
||||
itocado_ = 0.001f;
|
||||
itocado_per_jugador_[player_id] = 0.001f;
|
||||
}
|
||||
// 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 {
|
||||
@@ -750,26 +754,30 @@ Punt EscenaJoc::calcular_posicio_nau_init_hud(float progress) const {
|
||||
}
|
||||
|
||||
std::string EscenaJoc::construir_marcador() const {
|
||||
// Puntuació P1 (5 dígits)
|
||||
std::string score_p1 = std::to_string(puntuacio_total_);
|
||||
// Puntuació P1 (6 dígits)
|
||||
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;
|
||||
|
||||
// Vides P1 (2 dígits)
|
||||
std::string vides_p1 = (num_vides_ < 10) ? "0" + std::to_string(num_vides_)
|
||||
: std::to_string(num_vides_);
|
||||
std::string vides_p1 = (vides_per_jugador_[0] < 10)
|
||||
? "0" + std::to_string(vides_per_jugador_[0])
|
||||
: std::to_string(vides_per_jugador_[0]);
|
||||
|
||||
// Nivell (2 dígits)
|
||||
uint8_t stage_num = stage_manager_->get_stage_actual();
|
||||
std::string stage_str = (stage_num < 10) ? "0" + std::to_string(stage_num)
|
||||
: std::to_string(stage_num);
|
||||
|
||||
// Puntuació P2 (sempre 00000 per ara)
|
||||
std::string score_p2 = "000000";
|
||||
// Puntuació P2 (6 dígits)
|
||||
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)
|
||||
std::string vides_p2 = "03";
|
||||
// Vides P2 (2 dígits)
|
||||
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
|
||||
return score_p1 + " " + vides_p1 + " LEVEL " + stage_str + " " + score_p2 + " " + vides_p2;
|
||||
}
|
||||
@@ -823,8 +831,9 @@ void EscenaJoc::detectar_col·lisions_bales_enemics() {
|
||||
break;
|
||||
}
|
||||
|
||||
// 2. Add to total score
|
||||
puntuacio_total_ += punts;
|
||||
// 2. Add score to the player who shot it
|
||||
uint8_t owner_id = bala.get_owner_id();
|
||||
puntuacio_per_jugador_[owner_id] += punts;
|
||||
|
||||
// 3. Create floating score number
|
||||
gestor_puntuacio_.crear(punts, pos_enemic);
|
||||
@@ -856,12 +865,7 @@ void EscenaJoc::detectar_col·lisions_bales_enemics() {
|
||||
}
|
||||
}
|
||||
|
||||
void EscenaJoc::detectar_col·lisio_nau_enemics() {
|
||||
// Skip collisions if ship is dead or invulnerable
|
||||
if (!nau_.esta_viva() || nau_.es_invulnerable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -869,30 +873,38 @@ void EscenaJoc::detectar_col·lisio_nau_enemics() {
|
||||
(RADI_NAU + RADI_ENEMIC) * Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER;
|
||||
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
|
||||
for (const auto& enemic : orni_) {
|
||||
if (!enemic.esta_actiu()) {
|
||||
continue;
|
||||
}
|
||||
const Punt& pos_nau = naus_[i].get_centre();
|
||||
|
||||
// [NEW] Skip collision if enemy is invulnerable
|
||||
if (enemic.es_invulnerable()) {
|
||||
continue;
|
||||
}
|
||||
// Check collision with all active enemies
|
||||
for (const auto& enemic : orni_) {
|
||||
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)
|
||||
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;
|
||||
const Punt& pos_enemic = enemic.get_centre();
|
||||
|
||||
// Check collision
|
||||
if (distancia_quadrada <= SUMA_RADIS_QUADRAT) {
|
||||
tocado(); // Trigger death sequence
|
||||
return; // Only one collision per frame
|
||||
// Calculate squared distance (avoid sqrt)
|
||||
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
|
||||
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};
|
||||
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_;
|
||||
|
||||
// Estat del joc
|
||||
Nau nau_;
|
||||
std::array<Nau, 2> naus_; // [0]=P1, [1]=P2
|
||||
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_;
|
||||
float itocado_; // Death timer (seconds)
|
||||
std::array<float, 2> itocado_per_jugador_; // Death timers per player (seconds)
|
||||
|
||||
// Lives and game over system
|
||||
int num_vides_; // Current lives count
|
||||
bool game_over_; // Game over state flag
|
||||
float game_over_timer_; // Countdown timer for auto-return (seconds)
|
||||
Punt punt_spawn_; // Configurable spawn point
|
||||
Punt punt_mort_; // Death position (for respawn)
|
||||
int puntuacio_total_; // Current score
|
||||
std::array<int, 2> vides_per_jugador_; // [0]=P1, [1]=P2
|
||||
bool game_over_; // Game over state flag
|
||||
float game_over_timer_; // Countdown timer for auto-return (seconds)
|
||||
Punt punt_spawn_; // Configurable spawn point (legacy)
|
||||
Punt punt_mort_; // Death position (for respawn, legacy)
|
||||
std::array<int, 2> puntuacio_per_jugador_; // [0]=P1, [1]=P2
|
||||
|
||||
// Text vectorial
|
||||
Graphics::VectorText text_;
|
||||
@@ -67,11 +67,13 @@ class EscenaJoc {
|
||||
std::unique_ptr<StageSystem::StageManager> stage_manager_;
|
||||
|
||||
// Funcions privades
|
||||
void tocado();
|
||||
void tocado(uint8_t player_id);
|
||||
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_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
|
||||
void dibuixar_missatge_stage(const std::string& missatge);
|
||||
|
||||
Reference in New Issue
Block a user