feat(demo): demos a 1 i 2 jugadors, esquiva de bales enemigues i vides infinites
This commit is contained in:
@@ -71,7 +71,7 @@ namespace SceneManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Nombre d'escenaris demo curats (cicle del attract mode).
|
// Nombre d'escenaris demo curats (cicle del attract mode).
|
||||||
static constexpr std::uint8_t DEMO_SCENARIO_COUNT = 3;
|
static constexpr std::uint8_t DEMO_SCENARIO_COUNT = 4;
|
||||||
|
|
||||||
// Índex de l'escenari demo actual. Persisteix entre transicions (el
|
// Índex de l'escenari demo actual. Persisteix entre transicions (el
|
||||||
// SceneContext el posseeix el Director), així cada entrada al mode demo
|
// SceneContext el posseeix el Director), així cada entrada al mode demo
|
||||||
|
|||||||
@@ -32,11 +32,9 @@ using SceneType = SceneContext::SceneType;
|
|||||||
using Option = SceneContext::Option;
|
using Option = SceneContext::Option;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Attract mode: duració màxima de la demo i marge perquè es vegi l'explosió
|
// Attract mode: durada fixa de la demo. Amb vides infinites, sempre dura
|
||||||
// de la nau abans de saltar al logo (menor que DEATH_DURATION=3s per evitar
|
// això (les morts respawnegen); només input o aquest timeout la tallen.
|
||||||
// la seqüència de respawn/continue).
|
|
||||||
constexpr float DEMO_DURATION = 35.0F;
|
constexpr float DEMO_DURATION = 35.0F;
|
||||||
constexpr float DEMO_DEATH_LINGER = 2.0F;
|
|
||||||
|
|
||||||
// Qualsevol d'aquestes accions trenca la demo i torna al títol.
|
// Qualsevol d'aquestes accions trenca la demo i torna al títol.
|
||||||
constexpr std::array<InputAction, 5> DEMO_EXIT_ACTIONS = {
|
constexpr std::array<InputAction, 5> DEMO_EXIT_ACTIONS = {
|
||||||
@@ -160,10 +158,11 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
|||||||
if (match_config_.mode == GameConfig::Mode::DEMO) {
|
if (match_config_.mode == GameConfig::Mode::DEMO) {
|
||||||
// Attract mode: arrencar directament en PLAYING a l'escenari curat
|
// Attract mode: arrencar directament en PLAYING a l'escenari curat
|
||||||
// actual (partida "ja començada") i avançar l'índex perquè la pròxima
|
// actual (partida "ja començada") i avançar l'índex perquè la pròxima
|
||||||
// demo mostri un escenari diferent.
|
// demo mostri un escenari diferent. El nombre de jugadors ja l'ha fixat
|
||||||
const uint8_t DEMO_STAGE = Systems::Demo::scenarioStage(context_.demoScenarioIndex());
|
// TitleScene al match_config llegint el mateix escenari.
|
||||||
|
const Systems::Demo::Scenario SC = Systems::Demo::scenario(context_.demoScenarioIndex());
|
||||||
context_.advanceDemoScenario();
|
context_.advanceDemoScenario();
|
||||||
stage_manager_->initDemo(DEMO_STAGE);
|
stage_manager_->initDemo(SC.stage);
|
||||||
demo_timer_ = DEMO_DURATION;
|
demo_timer_ = DEMO_DURATION;
|
||||||
} else {
|
} else {
|
||||||
stage_manager_->init();
|
stage_manager_->init();
|
||||||
@@ -339,10 +338,10 @@ void GameScene::updateShipsControl(float delta_time) {
|
|||||||
if (!ACTIU || hit_timer_per_player_[i] != 0.0F) {
|
if (!ACTIU || hit_timer_per_player_[i] != 0.0F) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// En demo, la P1 es mou amb el pilot IA (control calculat a stepDemo);
|
// En demo, cada nau activa es mou amb el seu pilot IA (control calculat
|
||||||
// la resta de casos llegeixen Input com sempre.
|
// a stepDemo); la resta de casos llegeixen Input com sempre.
|
||||||
if (DEMO && i == 0) {
|
if (DEMO) {
|
||||||
ships_[0].applyMovement(demo_ctrl_.left, demo_ctrl_.right, demo_ctrl_.thrust, delta_time);
|
ships_[i].applyMovement(demo_ctrls_[i].left, demo_ctrls_[i].right, demo_ctrls_[i].thrust, delta_time);
|
||||||
} else {
|
} else {
|
||||||
ships_[i].processInput(delta_time, i);
|
ships_[i].processInput(delta_time, i);
|
||||||
}
|
}
|
||||||
@@ -357,22 +356,30 @@ auto GameScene::stepDemo(float delta_time) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// En morir la nau, escurçar el timer perquè es vegi l'explosió i després
|
// Vides infinites: la demo dura sempre el temps fix; en morir, stepDeathSequence
|
||||||
// saltar al logo (sense passar per CONTINUE/GAME_OVER ni parar la música).
|
// respawneja (no acaba ni passa per CONTINUE/GAME_OVER).
|
||||||
if (hit_timer_per_player_[0] > 0.0F) {
|
|
||||||
demo_timer_ = std::min(demo_timer_, DEMO_DEATH_LINGER);
|
|
||||||
}
|
|
||||||
|
|
||||||
demo_timer_ -= delta_time;
|
demo_timer_ -= delta_time;
|
||||||
if (demo_timer_ <= 0.0F) {
|
if (demo_timer_ <= 0.0F) {
|
||||||
endDemo();
|
endDemo();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Control del pilot per al frame; el disparo el dispara GameScene.
|
// Control de cada nau activa per al frame; el disparo el dispara GameScene.
|
||||||
demo_ctrl_ = demo_pilot_.compute(ships_[0], enemies_, Defaults::Zones::PLAYAREA, delta_time);
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
if (demo_ctrl_.shoot) {
|
const bool ACTIU = (i == 0) ? match_config_.player1_active : match_config_.player2_active;
|
||||||
fireBullet(0);
|
if (!ACTIU || hit_timer_per_player_[i] != 0.0F) {
|
||||||
|
demo_ctrls_[i] = {}; // nau inactiva/morta: sense control
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
demo_ctrls_[i] = demo_pilots_[i].compute(
|
||||||
|
ships_[i],
|
||||||
|
enemies_,
|
||||||
|
bullets_,
|
||||||
|
Defaults::Zones::PLAYAREA,
|
||||||
|
delta_time);
|
||||||
|
if (demo_ctrls_[i].shoot) {
|
||||||
|
fireBullet(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -491,6 +498,15 @@ void GameScene::stepDeathSequence(float delta_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *** PHASE 3: RESPAWN OR GAME OVER ***
|
// *** PHASE 3: RESPAWN OR GAME OVER ***
|
||||||
|
// Mode demo: vides infinites. Respawn sempre, sense decrementar vides ni
|
||||||
|
// passar mai per CONTINUE/GAME_OVER — la demo dura el seu temps fix.
|
||||||
|
if (match_config_.mode == GameConfig::Mode::DEMO) {
|
||||||
|
Vec2 spawn_pos = getSpawnPoint(i);
|
||||||
|
ships_[i].init(&spawn_pos, /*activar_invulnerabilitat=*/true);
|
||||||
|
hit_timer_per_player_[i] = 0.0F;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
lives_per_player_[i]--;
|
lives_per_player_[i]--;
|
||||||
if (lives_per_player_[i] > 0) {
|
if (lives_per_player_[i] > 0) {
|
||||||
Vec2 spawn_pos = getSpawnPoint(i);
|
Vec2 spawn_pos = getSpawnPoint(i);
|
||||||
|
|||||||
@@ -102,10 +102,10 @@ class GameScene final : public Scene {
|
|||||||
// Control de sons de animación INIT_HUD
|
// Control de sons de animación INIT_HUD
|
||||||
bool init_hud_rect_sound_played_{false}; // Flag para evitar repetir sonido del rectángulo
|
bool init_hud_rect_sound_played_{false}; // Flag para evitar repetir sonido del rectángulo
|
||||||
|
|
||||||
// Attract mode (mode DEMO): un pilot IA controla la nau P1.
|
// Attract mode (mode DEMO): un pilot IA per nau activa (1 o 2 jugadors).
|
||||||
Systems::Demo::DemoPilot demo_pilot_;
|
std::array<Systems::Demo::DemoPilot, 2> demo_pilots_;
|
||||||
Systems::Demo::Control demo_ctrl_{}; // Control del pilot per al frame actual
|
std::array<Systems::Demo::Control, 2> demo_ctrls_{}; // Control per nau al frame actual
|
||||||
float demo_timer_{0.0F}; // Temps restant de la demo (→ LOGO en esgotar-se)
|
float demo_timer_{0.0F}; // Temps restant de la demo (→ LOGO)
|
||||||
|
|
||||||
// Funciones privades
|
// Funciones privades
|
||||||
// bullet_velocity: velocitat de la bala que ha causat la mort (Vec2{} si no
|
// bullet_velocity: velocitat de la bala que ha causat la mort (Vec2{} si no
|
||||||
@@ -144,7 +144,7 @@ class GameScene final : public Scene {
|
|||||||
void stepPhysics(float delta_time);
|
void stepPhysics(float delta_time);
|
||||||
void stepShootingInput();
|
void stepShootingInput();
|
||||||
void stepMidGameJoin();
|
void stepMidGameJoin();
|
||||||
// Mueve las naves activas: en mode DEMO la P1 usa el pilot IA (demo_ctrl_),
|
// Mueve las naves activas: en mode DEMO cada nau usa su pilot IA (demo_ctrls_),
|
||||||
// el resto usa processInput (Input). Compartido por los 3 estados jugables.
|
// el resto usa processInput (Input). Compartido por los 3 estados jugables.
|
||||||
void updateShipsControl(float delta_time);
|
void updateShipsControl(float delta_time);
|
||||||
// Mode DEMO: gestiona salida (input→título, timeout/muerte→logo) y calcula
|
// Mode DEMO: gestiona salida (input→título, timeout/muerte→logo) y calcula
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "core/rendering/shape_renderer.hpp"
|
#include "core/rendering/shape_renderer.hpp"
|
||||||
#include "core/system/scene_context.hpp"
|
#include "core/system/scene_context.hpp"
|
||||||
#include "core/system/service_menu.hpp"
|
#include "core/system/service_menu.hpp"
|
||||||
|
#include "game/systems/demo_pilot.hpp"
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
|
|
||||||
using SceneManager::SceneContext;
|
using SceneManager::SceneContext;
|
||||||
@@ -359,9 +360,12 @@ void TitleScene::update(float delta_time) {
|
|||||||
idle_timer_ += delta_time;
|
idle_timer_ += delta_time;
|
||||||
}
|
}
|
||||||
if (idle_timer_ >= TITLE_DEMO_TIMEOUT) {
|
if (idle_timer_ >= TITLE_DEMO_TIMEOUT) {
|
||||||
|
// L'escenari curat (mateix índex que llegirà GameScene) decideix
|
||||||
|
// quants jugadors IA hi ha. No avancem l'índex ací: ho fa GameScene.
|
||||||
|
const Systems::Demo::Scenario SC = Systems::Demo::scenario(context_.demoScenarioIndex());
|
||||||
GameConfig::MatchConfig demo_cfg;
|
GameConfig::MatchConfig demo_cfg;
|
||||||
demo_cfg.player1_active = true;
|
demo_cfg.player1_active = true;
|
||||||
demo_cfg.player2_active = false;
|
demo_cfg.player2_active = (SC.players >= 2);
|
||||||
demo_cfg.mode = GameConfig::Mode::DEMO;
|
demo_cfg.mode = GameConfig::Mode::DEMO;
|
||||||
context_.setMatchConfig(demo_cfg);
|
context_.setMatchConfig(demo_cfg);
|
||||||
context_.setNextScene(SceneType::GAME);
|
context_.setNextScene(SceneType::GAME);
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ namespace Systems::Demo {
|
|||||||
|
|
||||||
constexpr float WALL_BIAS = 0.6F; // peso del empuje hacia el centro al esquivar
|
constexpr float WALL_BIAS = 0.6F; // peso del empuje hacia el centro al esquivar
|
||||||
|
|
||||||
|
// Esquiva de balas enemigas.
|
||||||
|
constexpr float DODGE_SCAN_RADIUS = 190.0F; // px: distancia a la que reacciona
|
||||||
|
constexpr float DODGE_HEADING_MIN = 0.25F; // dot mínimo: la bala viene hacia la nave
|
||||||
|
|
||||||
// [-1, 1] aleatorio (estética: jitter de apuntado; no afecta a la simulación).
|
// [-1, 1] aleatorio (estética: jitter de apuntado; no afecta a la simulación).
|
||||||
auto randSigned() -> float {
|
auto randSigned() -> float {
|
||||||
return (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX) * 2.0F) - 1.0F;
|
return (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX) * 2.0F) - 1.0F;
|
||||||
@@ -72,10 +76,71 @@ namespace Systems::Demo {
|
|||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BulletThreat {
|
||||||
|
bool found{false};
|
||||||
|
Vec2 position{};
|
||||||
|
Vec2 velocity{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bala enemiga más cercana que viene hacia la nave (dentro del radio de
|
||||||
|
// reacción). Solo balas de enemic (owner_id >= ENEMY_OWNER_BASE).
|
||||||
|
auto findBulletThreat(const Vec2& ship_pos,
|
||||||
|
const std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& bullets)
|
||||||
|
-> BulletThreat {
|
||||||
|
BulletThreat threat;
|
||||||
|
float best_d2 = 0.0F;
|
||||||
|
for (const auto& bullet : bullets) {
|
||||||
|
if (!bullet.isActive() ||
|
||||||
|
bullet.getOwnerId() < Defaults::Entities::ENEMY_OWNER_BASE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const Vec2 BPOS = bullet.getCenter();
|
||||||
|
const Vec2 TO_SHIP = ship_pos - BPOS;
|
||||||
|
const float D2 = TO_SHIP.lengthSquared();
|
||||||
|
if (D2 > DODGE_SCAN_RADIUS * DODGE_SCAN_RADIUS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const Vec2 BVEL = bullet.getBody().velocity;
|
||||||
|
if (BVEL.lengthSquared() < 1.0F) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// ¿La bala se dirige hacia la nave?
|
||||||
|
if (BVEL.normalized().dot(TO_SHIP.normalized()) < DODGE_HEADING_MIN) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!threat.found || D2 < best_d2) {
|
||||||
|
threat.found = true;
|
||||||
|
best_d2 = D2;
|
||||||
|
threat.position = BPOS;
|
||||||
|
threat.velocity = BVEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return threat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error angular (rad, [-PI,PI]) entre la nariz de la nave y desired_dir,
|
||||||
|
// con el jitter de apuntado aplicado. La nariz apunta hacia (angle - PI/2).
|
||||||
|
auto steerError(const Ship& ship, const Vec2& desired_dir, float jitter) -> float {
|
||||||
|
const float DESIRED = std::atan2(desired_dir.y, desired_dir.x) + jitter;
|
||||||
|
const float NOSE = ship.getAngle() - (PI / 2.0F);
|
||||||
|
return wrapPi(DESIRED - NOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setea rotación según el error (RIGHT incrementa ship.angle; LEFT lo
|
||||||
|
// decrementa), con zona muerta para no oscilar.
|
||||||
|
void applyRotation(Control& ctrl, float error) {
|
||||||
|
if (error > ROTATE_DEADZONE) {
|
||||||
|
ctrl.right = true;
|
||||||
|
} else if (error < -ROTATE_DEADZONE) {
|
||||||
|
ctrl.left = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
auto DemoPilot::compute(const Ship& ship,
|
auto DemoPilot::compute(const Ship& ship,
|
||||||
const std::array<Enemy, Constants::MAX_ORNIS>& enemies,
|
const std::array<Enemy, Constants::MAX_ORNIS>& enemies,
|
||||||
|
const std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& bullets,
|
||||||
const SDL_FRect& play_area,
|
const SDL_FRect& play_area,
|
||||||
float delta_time) -> Control {
|
float delta_time) -> Control {
|
||||||
Control ctrl;
|
Control ctrl;
|
||||||
@@ -94,6 +159,26 @@ namespace Systems::Demo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Vec2 SHIP_POS = ship.getCenter();
|
const Vec2 SHIP_POS = ship.getCenter();
|
||||||
|
const Vec2 PLAY_CENTRE{
|
||||||
|
.x = play_area.x + (play_area.w / 2.0F),
|
||||||
|
.y = play_area.y + (play_area.h / 2.0F)};
|
||||||
|
const Vec2 TO_CENTRE = (PLAY_CENTRE - SHIP_POS).normalized();
|
||||||
|
|
||||||
|
// Prioridad 1: esquivar una bala enemiga entrante. Se mueve perpendicular
|
||||||
|
// a la trayectoria de la bala (con sesgo al centro) y no dispara.
|
||||||
|
const BulletThreat THREAT = findBulletThreat(SHIP_POS, bullets);
|
||||||
|
if (THREAT.found) {
|
||||||
|
const Vec2 BV = THREAT.velocity.normalized();
|
||||||
|
Vec2 perp{.x = -BV.y, .y = BV.x};
|
||||||
|
if ((SHIP_POS - THREAT.position).dot(perp) < 0.0F) {
|
||||||
|
perp = -perp; // hacia el lado en que ya está la nave
|
||||||
|
}
|
||||||
|
const Vec2 ESCAPE = (perp + (TO_CENTRE * WALL_BIAS)).normalized();
|
||||||
|
applyRotation(ctrl, steerError(ship, ESCAPE, aim_jitter_));
|
||||||
|
ctrl.thrust = true;
|
||||||
|
return ctrl;
|
||||||
|
}
|
||||||
|
|
||||||
const Nearest TARGET = findNearest(SHIP_POS, enemies);
|
const Nearest TARGET = findNearest(SHIP_POS, enemies);
|
||||||
target_idx_ = TARGET.index;
|
target_idx_ = TARGET.index;
|
||||||
|
|
||||||
@@ -103,10 +188,6 @@ namespace Systems::Demo {
|
|||||||
return ctrl;
|
return ctrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Centro de la zona de juego (sesgo anti-pared).
|
|
||||||
const Vec2 PLAY_CENTRE{
|
|
||||||
.x = play_area.x + (play_area.w / 2.0F),
|
|
||||||
.y = play_area.y + (play_area.h / 2.0F)};
|
|
||||||
const bool NEAR_WALL =
|
const bool NEAR_WALL =
|
||||||
SHIP_POS.x < play_area.x + WALL_MARGIN ||
|
SHIP_POS.x < play_area.x + WALL_MARGIN ||
|
||||||
SHIP_POS.x > play_area.x + play_area.w - WALL_MARGIN ||
|
SHIP_POS.x > play_area.x + play_area.w - WALL_MARGIN ||
|
||||||
@@ -116,10 +197,8 @@ namespace Systems::Demo {
|
|||||||
Vec2 desired_dir;
|
Vec2 desired_dir;
|
||||||
const bool DANGER = TARGET.distance < DANGER_RADIUS;
|
const bool DANGER = TARGET.distance < DANGER_RADIUS;
|
||||||
if (DANGER) {
|
if (DANGER) {
|
||||||
// Esquiva: alejarse del enemigo, con sesgo hacia el centro para no
|
// Prioridad 2: alejarse del enemigo pegado, con sesgo al centro.
|
||||||
// quedar atrapada contra la pared. Empuje activo para crear espacio.
|
|
||||||
const Vec2 AWAY = (SHIP_POS - TARGET.center).normalized();
|
const Vec2 AWAY = (SHIP_POS - TARGET.center).normalized();
|
||||||
const Vec2 TO_CENTRE = (PLAY_CENTRE - SHIP_POS).normalized();
|
|
||||||
desired_dir = (AWAY + (TO_CENTRE * WALL_BIAS)).normalized();
|
desired_dir = (AWAY + (TO_CENTRE * WALL_BIAS)).normalized();
|
||||||
ctrl.thrust = true;
|
ctrl.thrust = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -128,23 +207,13 @@ namespace Systems::Demo {
|
|||||||
desired_dir = (PREDICTED - SHIP_POS).normalized();
|
desired_dir = (PREDICTED - SHIP_POS).normalized();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ángulo deseado de la nariz; la nariz apunta hacia (angle - PI/2).
|
const float ERROR = steerError(ship, desired_dir, aim_jitter_);
|
||||||
float desired_angle = std::atan2(desired_dir.y, desired_dir.x) + aim_jitter_;
|
applyRotation(ctrl, ERROR);
|
||||||
const float NOSE_ANGLE = ship.getAngle() - (PI / 2.0F);
|
|
||||||
const float ERROR = wrapPi(desired_angle - NOSE_ANGLE);
|
|
||||||
|
|
||||||
// RIGHT incrementa ship.angle (→ nariz); LEFT lo decrementa.
|
|
||||||
if (ERROR > ROTATE_DEADZONE) {
|
|
||||||
ctrl.right = true;
|
|
||||||
} else if (ERROR < -ROTATE_DEADZONE) {
|
|
||||||
ctrl.left = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!DANGER) {
|
if (!DANGER) {
|
||||||
// Acercarse si el objetivo está lejos (mantiene la nave cazando),
|
// Acercarse si el objetivo está lejos (mantiene la nave cazando),
|
||||||
// pero no empujar de cara a una pared.
|
// pero no empujar de cara a una pared.
|
||||||
const bool FACING_WALL = NEAR_WALL &&
|
const bool FACING_WALL = NEAR_WALL && TO_CENTRE.dot(desired_dir) < 0.0F;
|
||||||
(PLAY_CENTRE - SHIP_POS).normalized().dot(desired_dir) < 0.0F;
|
|
||||||
if (TARGET.distance > APPROACH_RADIUS && !FACING_WALL &&
|
if (TARGET.distance > APPROACH_RADIUS && !FACING_WALL &&
|
||||||
std::fabs(ERROR) < FIRE_TOLERANCE) {
|
std::fabs(ERROR) < FIRE_TOLERANCE) {
|
||||||
ctrl.thrust = true;
|
ctrl.thrust = true;
|
||||||
|
|||||||
@@ -14,23 +14,36 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "core/defaults/entities.hpp"
|
||||||
#include "core/system/scene_context.hpp"
|
#include "core/system/scene_context.hpp"
|
||||||
#include "game/constants.hpp"
|
#include "game/constants.hpp"
|
||||||
|
#include "game/entities/bullet.hpp"
|
||||||
#include "game/entities/enemy.hpp"
|
#include "game/entities/enemy.hpp"
|
||||||
#include "game/entities/ship.hpp"
|
#include "game/entities/ship.hpp"
|
||||||
|
|
||||||
namespace Systems::Demo {
|
namespace Systems::Demo {
|
||||||
|
|
||||||
// Stages de arranque de cada escenario curado (se ciclan en attract mode).
|
// Escenari curat del attract mode: stage de arranque + nombre de naus IA.
|
||||||
// Elegidos por variedad visual: pinwheels, stars, squares/mix.
|
// Tots inclouen estrelles (que disparen) per donar feina al pilot.
|
||||||
inline constexpr std::array<std::uint8_t, SceneManager::SceneContext::DEMO_SCENARIO_COUNT>
|
struct Scenario {
|
||||||
SCENARIO_STAGES = {2, 5, 7};
|
std::uint8_t stage; // stage_id de stages.yaml on arrenca la demo
|
||||||
|
std::uint8_t players; // 1 o 2 naus controlades per la IA
|
||||||
|
};
|
||||||
|
|
||||||
// Stage de arranque para el índice de escenario dado.
|
inline constexpr std::array<Scenario, SceneManager::SceneContext::DEMO_SCENARIO_COUNT>
|
||||||
[[nodiscard]] inline auto scenarioStage(std::uint8_t index) -> std::uint8_t {
|
SCENARIOS = {{
|
||||||
return SCENARIO_STAGES.at(index % SCENARIO_STAGES.size());
|
{.stage = 5, .players = 1}, // estrelles + mix, 1 jugador
|
||||||
|
{.stage = 8, .players = 1}, // pressió alta, 1 jugador
|
||||||
|
{.stage = 6, .players = 2}, // densitat alta, 2 jugadors (foc amic ON)
|
||||||
|
{.stage = 10, .players = 2}, // repte final, 2 jugadors (foc amic ON)
|
||||||
|
}};
|
||||||
|
|
||||||
|
// Escenari per a l'índex donat (cíclic).
|
||||||
|
[[nodiscard]] inline auto scenario(std::uint8_t index) -> Scenario {
|
||||||
|
return SCENARIOS.at(index % SCENARIOS.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Control de la nave para un frame.
|
// Control de la nave para un frame.
|
||||||
@@ -45,6 +58,7 @@ namespace Systems::Demo {
|
|||||||
public:
|
public:
|
||||||
[[nodiscard]] auto compute(const Ship& ship,
|
[[nodiscard]] auto compute(const Ship& ship,
|
||||||
const std::array<Enemy, Constants::MAX_ORNIS>& enemies,
|
const std::array<Enemy, Constants::MAX_ORNIS>& enemies,
|
||||||
|
const std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& bullets,
|
||||||
const SDL_FRect& play_area,
|
const SDL_FRect& play_area,
|
||||||
float delta_time) -> Control;
|
float delta_time) -> Control;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user