Merge branch 'time-based'

This commit is contained in:
2026-05-19 18:42:07 +02:00
30 changed files with 1298 additions and 1238 deletions
+102 -102
View File
@@ -8,88 +8,95 @@
namespace { namespace {
// Normalitza CRLF: fitxers .ani amb terminadors de Windows fan que // Normalitza CRLF: fitxers .ani amb terminadors de Windows fan que
// line == "[animation]" no faci match i el parser entri en bucle // line == "[animation]" no faci match i el parser entri en bucle
// infinit / no carregui cap animació. // infinit / no carregui cap animació.
void stripCr(std::string &s) { void stripCr(std::string &s) {
if (!s.empty() && s.back() == '\r') { if (!s.empty() && s.back() == '\r') {
s.pop_back(); s.pop_back();
}
}
void parseFramesList(const std::string &value, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles) {
std::stringstream ss(value);
std::string tmp;
SDL_Rect rect = {0, 0, frame_width, frame_height};
while (getline(ss, tmp, ',')) {
const int NUM_TILE = std::stoi(tmp) > max_tiles ? 0 : std::stoi(tmp);
rect.x = (NUM_TILE % frames_per_row) * frame_width;
rect.y = (NUM_TILE / frames_per_row) * frame_height;
buffer.frames.push_back(rect);
}
}
void parseAnimationField(const std::string &line, int pos, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) {
const std::string KEY = line.substr(0, pos);
const std::string VALUE = line.substr(pos + 1, line.length());
if (KEY == "name") {
buffer.name = VALUE;
} else if (KEY == "speed") {
buffer.speed = std::stoi(VALUE);
} else if (KEY == "loop") {
buffer.loop = std::stoi(VALUE);
} else if (KEY == "frames") {
parseFramesList(VALUE, buffer, frame_width, frame_height, frames_per_row, max_tiles);
} else {
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n';
}
}
auto parseAnimationBlock(std::istream &file, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) -> Animation {
Animation buffer;
buffer.speed = 0;
buffer.loop = -1;
buffer.counter = 0;
buffer.current_frame = 0;
buffer.completed = false;
std::string line;
do {
if (!std::getline(file, line)) {
break;
} }
stripCr(line); }
int pos = line.find('=');
if (pos != (int)std::string::npos) { void parseFramesList(const std::string &value, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles) {
parseAnimationField(line, pos, buffer, frame_width, frame_height, frames_per_row, max_tiles, filename); std::stringstream ss(value);
std::string tmp;
SDL_Rect rect = {0, 0, frame_width, frame_height};
while (getline(ss, tmp, ',')) {
const int NUM_TILE = std::stoi(tmp) > max_tiles ? 0 : std::stoi(tmp);
rect.x = (NUM_TILE % frames_per_row) * frame_width;
rect.y = (NUM_TILE / frames_per_row) * frame_height;
buffer.frames.push_back(rect);
} }
} while (line != "[/animation]");
return buffer;
}
void parseGlobalField(const std::string &line, int pos, int &frames_per_row, int &frame_width, int &frame_height, int &max_tiles, const Texture *texture, const std::string &filename) {
const std::string KEY = line.substr(0, pos);
const std::string VALUE = line.substr(pos + 1, line.length());
if (KEY == "framesPerRow") {
frames_per_row = std::stoi(VALUE);
} else if (KEY == "frameWidth") {
frame_width = std::stoi(VALUE);
} else if (KEY == "frameHeight") {
frame_height = std::stoi(VALUE);
} else {
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n';
} }
if (frames_per_row == 0 && frame_width > 0) { void parseAnimationField(const std::string &line, int pos, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) {
frames_per_row = texture->getWidth() / frame_width; const std::string KEY = line.substr(0, pos);
const std::string VALUE = line.substr(pos + 1, line.length());
if (KEY == "name") {
buffer.name = VALUE;
} else if (KEY == "speed") {
buffer.speed = std::stoi(VALUE);
// Time-based: el valor del .ani s'expressa en "ticks per frame
// d'animació" (assumint 60 Hz). El camp `speed` (int) es manté per al
// fallback frame-based; el nou `step_duration_s` (float) és el que
// gasta animate(dt).
buffer.step_duration_s = static_cast<float>(buffer.speed) / 60.0F;
} else if (KEY == "loop") {
buffer.loop = std::stoi(VALUE);
} else if (KEY == "frames") {
parseFramesList(VALUE, buffer, frame_width, frame_height, frames_per_row, max_tiles);
} else {
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n';
}
} }
if (max_tiles == 0 && frame_width > 0 && frame_height > 0) {
const int W = texture->getWidth() / frame_width; auto parseAnimationBlock(std::istream &file, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) -> Animation {
const int H = texture->getHeight() / frame_height; Animation buffer;
max_tiles = W * H; buffer.speed = 0;
buffer.step_duration_s = 0.0F;
buffer.loop = -1;
buffer.counter = 0;
buffer.current_frame = 0;
buffer.completed = false;
buffer.time_accumulator_s = 0.0F;
std::string line;
do {
if (!std::getline(file, line)) {
break;
}
stripCr(line);
int pos = line.find('=');
if (pos != (int)std::string::npos) {
parseAnimationField(line, pos, buffer, frame_width, frame_height, frames_per_row, max_tiles, filename);
}
} while (line != "[/animation]");
return buffer;
}
void parseGlobalField(const std::string &line, int pos, int &frames_per_row, int &frame_width, int &frame_height, int &max_tiles, const Texture *texture, const std::string &filename) {
const std::string KEY = line.substr(0, pos);
const std::string VALUE = line.substr(pos + 1, line.length());
if (KEY == "framesPerRow") {
frames_per_row = std::stoi(VALUE);
} else if (KEY == "frameWidth") {
frame_width = std::stoi(VALUE);
} else if (KEY == "frameHeight") {
frame_height = std::stoi(VALUE);
} else {
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n';
}
if (frames_per_row == 0 && frame_width > 0) {
frames_per_row = texture->getWidth() / frame_width;
}
if (max_tiles == 0 && frame_width > 0 && frame_height > 0) {
const int W = texture->getWidth() / frame_width;
const int H = texture->getHeight() / frame_height;
max_tiles = W * H;
}
} }
}
} // namespace } // namespace
@@ -204,33 +211,26 @@ auto AnimatedSprite::getIndex(const std::string &name) -> int {
return -1; return -1;
} }
// Calcula el frame correspondiente a la animación // Avança l'acumulador i calcula el frame actual a partir de `step_duration_s`.
void AnimatedSprite::animate() { void AnimatedSprite::animate(float dt_s) {
if (!enabled_ || animation_[current_animation_].speed == 0) { Animation &anim = animation_[current_animation_];
if (!enabled_ || anim.step_duration_s <= 0.0F) {
return; return;
} }
// Calcula el frame actual a partir del contador anim.time_accumulator_s += dt_s;
animation_[current_animation_].current_frame = animation_[current_animation_].counter / animation_[current_animation_].speed; anim.current_frame = static_cast<int>(anim.time_accumulator_s / anim.step_duration_s);
// Si alcanza el final de la animación, reinicia el contador de la animación if (anim.current_frame >= (int)anim.frames.size()) {
// en función de la variable loop y coloca el nuevo frame if (anim.loop == -1) {
if (animation_[current_animation_].current_frame >= (int)animation_[current_animation_].frames.size()) { anim.current_frame = anim.frames.size();
if (animation_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame anim.completed = true;
animation_[current_animation_].current_frame = animation_[current_animation_].frames.size(); } else {
animation_[current_animation_].completed = true; anim.time_accumulator_s = 0.0F;
} else { // Si hay loop, vuelve al frame indicado anim.current_frame = anim.loop;
animation_[current_animation_].counter = 0;
animation_[current_animation_].current_frame = animation_[current_animation_].loop;
} }
} } else {
// En caso contrario setSpriteClip(anim.frames[anim.current_frame]);
else {
// Escoge el frame correspondiente de la animación
setSpriteClip(animation_[current_animation_].frames[animation_[current_animation_].current_frame]);
// Incrementa el contador de la animacion
animation_[current_animation_].counter++;
} }
} }
@@ -344,10 +344,10 @@ void AnimatedSprite::setCurrentAnimation(int index) {
} }
} }
// Actualiza las variables del objeto // animate(dt) + MovingSprite::update(dt) (move + rotació)
void AnimatedSprite::update() { void AnimatedSprite::update(float dt_s) {
animate(); animate(dt_s);
MovingSprite::update(); MovingSprite::update(dt_s);
} }
// Establece el rectangulo para un frame de una animación // Establece el rectangulo para un frame de una animación
+7 -5
View File
@@ -12,11 +12,13 @@ class Texture;
struct Animation { struct Animation {
std::string name; // Nombre de la animacion std::string name; // Nombre de la animacion
std::vector<SDL_Rect> frames; // Cada uno de los frames que componen la animación std::vector<SDL_Rect> frames; // Cada uno de los frames que componen la animación
int speed; // Velocidad de la animación int speed; // Velocidad de la animación (frame-based: ticks per frame)
float step_duration_s; // Time-based: segons per frame d'animació (derivat de speed al parse: speed/60)
int loop; // Indica a que frame vuelve la animación al terminar. -1 para que no vuelva int loop; // Indica a que frame vuelve la animación al terminar. -1 para que no vuelva
bool completed; // Indica si ha finalizado la animación bool completed; // Indica si ha finalizado la animación
int current_frame; // Frame actual int current_frame; // Frame actual
int counter; // Contador para las animaciones int counter; // Contador per a les animacions (frame-based)
float time_accumulator_s; // Acumulador de temps (time-based)
}; };
struct AnimatedSpriteData { struct AnimatedSpriteData {
@@ -37,7 +39,7 @@ class AnimatedSprite : public MovingSprite {
~AnimatedSprite() override; // Destructor ~AnimatedSprite() override; // Destructor
void animate(); // Calcula el frame correspondiente a la animación actual void animate(float dt_s); // Calcula el frame correspondiente a la animación actual
auto getNumFrames() -> int; // Obtiene el numero de frames de la animación actual auto getNumFrames() -> int; // Obtiene el numero de frames de la animación actual
void setCurrentFrame(int num); // Establece el frame actual de la animación void setCurrentFrame(int num); // Establece el frame actual de la animación
void setAnimationCounter(const std::string &name, int num); // Establece el valor del contador void setAnimationCounter(const std::string &name, int num); // Establece el valor del contador
@@ -56,13 +58,13 @@ class AnimatedSprite : public MovingSprite {
auto getAnimationClip(const std::string &name = "default", Uint8 index = 0) -> SDL_Rect; // Devuelve el rectangulo de una animación y frame concreto auto getAnimationClip(const std::string &name = "default", Uint8 index = 0) -> SDL_Rect; // Devuelve el rectangulo de una animación y frame concreto
auto getAnimationClip(int index_a = 0, Uint8 index_f = 0) -> SDL_Rect; auto getAnimationClip(int index_a = 0, Uint8 index_f = 0) -> SDL_Rect;
auto getIndex(const std::string &name) -> int; // Obtiene el indice de la animación a partir del nombre auto getIndex(const std::string &name) -> int; // Obtiene el indice de la animación a partir del nombre
auto loadFromVector(const std::vector<std::string> *source) -> bool; // Carga la animación desde un vector auto loadFromVector(const std::vector<std::string> *source) -> bool; // Carga la animación desde un vector
void setCurrentAnimation(const std::string &name = "default"); // Establece la animacion actual void setCurrentAnimation(const std::string &name = "default"); // Establece la animacion actual
void setCurrentAnimation(int index = 0); void setCurrentAnimation(int index = 0);
void update() override; // Actualiza las variables del objeto void update(float dt_s) override; // Actualiza las variables del objeto
void setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h); // OLD - Establece el rectangulo para un frame de una animación void setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h); // OLD - Establece el rectangulo para un frame de una animación
void setAnimationCounter(int value); // OLD - Establece el contador para todas las animaciones void setAnimationCounter(int value); // OLD - Establece el contador para todas las animaciones
+11 -5
View File
@@ -31,6 +31,7 @@ void Fade::init(Uint8 r, Uint8 g, Uint8 b) {
enabled_ = false; enabled_ = false;
finished_ = false; finished_ = false;
counter_ = 0; counter_ = 0;
elapsed_s_ = 0.0F;
r_ = r; r_ = r;
g_ = g; g_ = g;
b_ = b; b_ = b;
@@ -155,11 +156,15 @@ void Fade::renderFadeRandomSquare() {
} }
} }
// Actualiza las variables internas // Actualiza les variables internes. `counter_` (Uint16, frames a la cadència
void Fade::update() { // de referència 60Hz) es deriva de `elapsed_s_` perquè els helpers de
if (enabled_) { // `render()` (renderFadeFullscreen / Center / RandomSquare) segueixin
counter_++; // llegint-lo igual que abans.
} void Fade::update(float dt_s) {
if (!enabled_) { return; }
elapsed_s_ += dt_s;
constexpr float FADE_STEPS_PER_S = 60.0F;
counter_ = static_cast<Uint16>(elapsed_s_ * FADE_STEPS_PER_S);
} }
// Activa el fade // Activa el fade
@@ -167,6 +172,7 @@ void Fade::activateFade() {
enabled_ = true; enabled_ = true;
finished_ = false; finished_ = false;
counter_ = 0; counter_ = 0;
elapsed_s_ = 0.0F;
squares_drawn_ = 0; squares_drawn_ = 0;
last_square_ticks_ = 0; last_square_ticks_ = 0;
fullscreen_done_ = false; fullscreen_done_ = false;
+3 -2
View File
@@ -18,7 +18,7 @@ class Fade {
void init(Uint8 r, Uint8 g, Uint8 b); // Inicializa las variables void init(Uint8 r, Uint8 g, Uint8 b); // Inicializa las variables
void render(); // Pinta una transición en pantalla void render(); // Pinta una transición en pantalla
void update(); // Actualiza las variables internas void update(float dt_s); // Actualiza las variables internas
void activateFade(); // Activa el fade void activateFade(); // Activa el fade
[[nodiscard]] auto hasEnded() const -> bool; // Comprueba si ha terminado la transicion [[nodiscard]] auto hasEnded() const -> bool; // Comprueba si ha terminado la transicion
@@ -34,7 +34,8 @@ class Fade {
SDL_Renderer *renderer_ = nullptr; // El renderizador de la ventana SDL_Renderer *renderer_ = nullptr; // El renderizador de la ventana
SDL_Texture *backbuffer_ = nullptr; // Textura para usar como backbuffer SDL_Texture *backbuffer_ = nullptr; // Textura para usar como backbuffer
Type fade_type_{Type::FULLSCREEN}; // Tipo de fade a realizar Type fade_type_{Type::FULLSCREEN}; // Tipo de fade a realizar
Uint16 counter_ = 0; // Contador interno Uint16 counter_ = 0; // Contador intern (frame-based)
float elapsed_s_ = 0.0F; // Acumulador de temps (time-based)
bool enabled_ = false; // Indica si el fade está activo bool enabled_ = false; // Indica si el fade está activo
bool finished_ = false; // Indica si ha terminado la transición bool finished_ = false; // Indica si ha terminado la transición
Uint8 r_ = 0, g_ = 0, b_ = 0; // Colores para el fade Uint8 r_ = 0, g_ = 0, b_ = 0; // Colores para el fade
+16 -25
View File
@@ -34,22 +34,22 @@ void MovingSprite::clear() {
center_ = nullptr; center_ = nullptr;
rotate_speed_ = 0; rotate_speed_ = 0;
rotate_amount_ = 0.0; rotate_amount_ = 0.0;
counter_ = 0;
current_flip_ = SDL_FLIP_NONE; current_flip_ = SDL_FLIP_NONE;
} }
// Mueve el sprite // Mueve el sprite. vx_/vy_ en px/s, ax_/ay_ en px/s². Integració d'Euler
void MovingSprite::move() { // senzilla — suficient per a moviments sense col·lisions sensibles.
void MovingSprite::move(float dt_s) {
if (enabled_) { if (enabled_) {
x_prev_ = x_; x_prev_ = x_;
y_prev_ = y_; y_prev_ = y_;
x_ += vx_; x_ += vx_ * dt_s;
y_ += vy_; y_ += vy_ * dt_s;
vx_ += ax_; vx_ += ax_ * dt_s;
vy_ += ay_; vy_ += ay_ * dt_s;
} }
} }
@@ -180,17 +180,6 @@ auto MovingSprite::getRotateSpeed() const -> Uint16 {
return rotate_speed_; return rotate_speed_;
} }
// Establece la rotacion
void MovingSprite::rotate() {
if (enabled_) {
if (rotate_enabled_) {
if (counter_ % rotate_speed_ == 0) {
incAngle(rotate_amount_);
}
}
}
}
// Establece el valor de la variable // Establece el valor de la variable
void MovingSprite::setRotate(bool value) { void MovingSprite::setRotate(bool value) {
rotate_enabled_ = value; rotate_enabled_ = value;
@@ -216,13 +205,15 @@ void MovingSprite::disableRotate() {
angle_ = (double)0; angle_ = (double)0;
} }
// Actualiza las variables internas del objeto // Actualiza les variables internes (move + rotació integrada). La rotació
void MovingSprite::update() { // frame-based original era `incAngle(rotate_amount_)` cada `rotate_speed_`
move(); // frames a 60Hz, equivalent a velocitat angular constant
rotate(); // = rotate_amount_ * 60 / rotate_speed_ graus/s.
void MovingSprite::update(float dt_s) {
if (enabled_) { move(dt_s);
++counter_ %= 60000; if (enabled_ && rotate_enabled_) {
const double ANGULAR_VELOCITY_DEG_PER_S = rotate_amount_ * 60.0 / static_cast<double>(rotate_speed_);
incAngle(ANGULAR_VELOCITY_DEG_PER_S * dt_s);
} }
} }
+6 -8
View File
@@ -10,11 +10,10 @@ class MovingSprite : public Sprite {
public: public:
explicit MovingSprite(float x = 0, float y = 0, int w = 0, int h = 0, float velx = 0, float vely = 0, float accelx = 0, float accely = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr); // Constructor explicit MovingSprite(float x = 0, float y = 0, int w = 0, int h = 0, float velx = 0, float vely = 0, float accelx = 0, float accely = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr); // Constructor
void move(); // Mueve el sprite void move(float dt_s); // Mueve el sprite (vx/vy/ax/ay en px/s i px/s^2)
void rotate(); // Rota el sprite virtual void update(float dt_s); // Actualiza les variables internes (move + rotació integrada)
virtual void update(); // Actualiza las variables internas del objeto void clear(); // Reinicia todas las variables
void clear(); // Reinicia todas las variables void render() override; // Muestra el sprite por pantalla
void render() override; // Muestra el sprite por pantalla
// cppcheck-suppress duplInheritedMember ; shadow intencional: Sprite::getPosX retorna int (sprites estàtics), MovingSprite::getPosX retorna float (sub-pixel). No s'accedeix via Sprite*: la jerarquia de joc treballa amb el tipus concret // cppcheck-suppress duplInheritedMember ; shadow intencional: Sprite::getPosX retorna int (sprites estàtics), MovingSprite::getPosX retorna float (sub-pixel). No s'accedeix via Sprite*: la jerarquia de joc treballa amb el tipus concret
[[nodiscard]] auto getPosX() const -> float; // Obten el valor de la variable [[nodiscard]] auto getPosX() const -> float; // Obten el valor de la variable
@@ -83,9 +82,8 @@ class MovingSprite : public Sprite {
double angle_{0.0}; // Angulo para dibujarlo double angle_{0.0}; // Angulo para dibujarlo
bool rotate_enabled_{false}; // Indica si ha de rotar bool rotate_enabled_{false}; // Indica si ha de rotar
int rotate_speed_{0}; // Velocidad de giro int rotate_speed_{0}; // Velocidad de giro (frames per pas de rotació al ritme de referència 60Hz)
double rotate_amount_{0.0}; // Cantidad de grados a girar en cada iteración double rotate_amount_{0.0}; // Cantidad de grados a girar en cada pas
int counter_{0}; // Contador interno
SDL_Point *center_{nullptr}; // Centro de rotación SDL_Point *center_{nullptr}; // Centro de rotación
SDL_FlipMode current_flip_{SDL_FLIP_NONE}; // Indica como se voltea el sprite SDL_FlipMode current_flip_{SDL_FLIP_NONE}; // Indica como se voltea el sprite
}; };
+20 -15
View File
@@ -20,21 +20,18 @@ void SmartSprite::init() {
on_destination_ = false; on_destination_ = false;
dest_x_ = 0; dest_x_ = 0;
dest_y_ = 0; dest_y_ = 0;
counter_ = 0;
finished_ = false; finished_ = false;
} }
// Actualiza la posición y comprueba si ha llegado a su destino // La velocitat i acceleració són en px/s i px/s²; el temps de permanència
void SmartSprite::update() { // després d'arribar al destí ve donat per setRemainingTime().
void SmartSprite::update(float dt_s) {
if (enabled_) { if (enabled_) {
// NOLINTNEXTLINE(bugprone-parent-virtual-call): salt deliberat a l'avi — SmartSprite hereta d'AnimatedSprite només per reutilitzar API, però no usa animació de frames, així que es salta AnimatedSprite::update() (que cridaria animate()) // NOLINTNEXTLINE(bugprone-parent-virtual-call): salt deliberat a l'avi — SmartSprite hereta d'AnimatedSprite només per reutilitzar API, però no usa animació de frames, així que es salta AnimatedSprite::update() (que cridaria animate())
MovingSprite::update(); MovingSprite::update(dt_s);
// Comprueba el movimiento
checkMove(); checkMove();
checkFinished(dt_s);
// Comprueba si ha terminado
checkFinished();
} }
} }
@@ -56,6 +53,15 @@ void SmartSprite::setEnabledCounter(int value) {
enabled_counter_ = value; enabled_counter_ = value;
} }
// Time-based: temps de visibilitat post-arribada
void SmartSprite::setRemainingTime(float seconds) {
remaining_time_s_ = seconds;
}
auto SmartSprite::getRemainingTime() const -> float {
return remaining_time_s_;
}
// Establece el valor de la variable // Establece el valor de la variable
void SmartSprite::setDestX(int x) { void SmartSprite::setDestX(int x) {
dest_x_ = x; dest_x_ = x;
@@ -129,16 +135,15 @@ void SmartSprite::checkMove() {
} }
} }
// Comprueba si ha terminado // Decrementa el temps restant cada crida si està al destí
void SmartSprite::checkFinished() { void SmartSprite::checkFinished(float dt_s) {
// Comprueba si ha llegado a su destino
on_destination_ = getPosX() == dest_x_ && getPosY() == dest_y_; on_destination_ = getPosX() == dest_x_ && getPosY() == dest_y_;
if (on_destination_) { // Si esta en el destino comprueba su contador if (on_destination_) {
if (enabled_counter_ == 0) { // Si ha llegado a cero, deshabilita el objeto y lo marca como finalizado if (remaining_time_s_ <= 0.0F) {
finished_ = true; finished_ = true;
} else { // Si no ha llegado a cero, decrementa el contador } else {
enabled_counter_--; remaining_time_s_ -= dt_s;
} }
} }
} }
+21 -18
View File
@@ -10,27 +10,30 @@ class SmartSprite : public AnimatedSprite {
public: public:
SmartSprite(Texture *texture, SDL_Renderer *renderer); // Constructor SmartSprite(Texture *texture, SDL_Renderer *renderer); // Constructor
void init(); // Inicializa el objeto void init(); // Inicializa el objeto
void update() override; // Actualiza la posición y comprueba si ha llegado a su destino void update(float dt_s) override; // Actualiza la posicion
void render() override; // Pinta el objeto en pantalla void render() override; // Pinta el objeto en pantalla
[[nodiscard]] auto getEnabledCounter() const -> int; // Obtiene el valor de la variable [[nodiscard]] auto getEnabledCounter() const -> int; // Obtiene el valor de la variable
void setEnabledCounter(int value); // Establece el valor de la variable void setEnabledCounter(int value); // Establece el valor de la variable
void setDestX(int x); // Establece el valor de la variable void setRemainingTime(float seconds); // Time-based: temps que es queda visible despres d'arribar al desti
void setDestY(int y); // Establece el valor de la variable [[nodiscard]] auto getRemainingTime() const -> float; // Time-based: temps restant
[[nodiscard]] auto getDestX() const -> int; // Obtiene el valor de la variable void setDestX(int x); // Establece el valor de la variable
[[nodiscard]] auto getDestY() const -> int; // Obtiene el valor de la variable void setDestY(int y); // Establece el valor de la variable
[[nodiscard]] auto isOnDestination() const -> bool; // Obtiene el valor de la variable [[nodiscard]] auto getDestX() const -> int; // Obtiene el valor de la variable
[[nodiscard]] auto hasFinished() const -> bool; // Obtiene el valor de la variable [[nodiscard]] auto getDestY() const -> int; // Obtiene el valor de la variable
[[nodiscard]] auto isOnDestination() const -> bool; // Obtiene el valor de la variable
[[nodiscard]] auto hasFinished() const -> bool; // Obtiene el valor de la variable
private: private:
// Variables // Variables
bool on_destination_; // Indica si está en el destino bool on_destination_; // Indica si está en el destino
int dest_x_; // Posicion de destino en el eje X int dest_x_; // Posicion de destino en el eje X
int dest_y_; // Posicion de destino en el eje Y int dest_y_; // Posicion de destino en el eje Y
int enabled_counter_; // Contador para deshabilitarlo int enabled_counter_; // Contador (frames, derivat de remaining_time_s_ * 60)
bool finished_; // Indica si ya ha terminado float remaining_time_s_{0.0F}; // Temps restant per a deshabilitar-lo
bool finished_; // Indica si ya ha terminado
void checkMove(); // Comprueba el movimiento void checkMove(); // Comprueba el movimiento
void checkFinished(); // Comprueba si ha terminado void checkFinished(float dt_s); // Comprueba si ha terminado
}; };
+27 -31
View File
@@ -7,30 +7,27 @@ Writer::Writer(Text *text)
: text_(text) { : text_(text) {
} }
// Actualiza el objeto // Avança un caracter cada `seconds_per_char_` i un cop completat es queda
void Writer::update() { // visible `remaining_time_s_` segons abans de finalitzar.
if (enabled_) { void Writer::update(float dt_s) {
if (!completed_) { // No completado if (!enabled_) { return; }
if (writing_counter_ > 0) {
writing_counter_--;
}
else if (writing_counter_ == 0) { if (!completed_) {
index_++; char_timer_s_ += dt_s;
writing_counter_ = speed_; while (char_timer_s_ >= seconds_per_char_ && index_ < length_) {
} char_timer_s_ -= seconds_per_char_;
++index_;
if (index_ == length_) {
completed_ = true;
}
} }
if (index_ >= length_) {
completed_ = true;
}
}
if (completed_) { // Completado if (completed_) {
if (enabled_counter_ > 0) { if (remaining_time_s_ <= 0.0F) {
enabled_counter_--; finished_ = true;
} else if (enabled_counter_ == 0) { } else {
finished_ = true; remaining_time_s_ -= dt_s;
}
} }
} }
} }
@@ -63,10 +60,10 @@ void Writer::setCaption(const std::string &text) {
length_ = text.length(); length_ = text.length();
} }
// Establece el valor de la variable // Segons per caracter. Quan s'usa, l'update(dt) avança index.
void Writer::setSpeed(int value) { void Writer::setSecondsPerChar(float seconds) {
speed_ = value; seconds_per_char_ = seconds;
writing_counter_ = value; char_timer_s_ = 0.0F;
} }
// Establece el valor de la variable // Establece el valor de la variable
@@ -79,14 +76,13 @@ auto Writer::isEnabled() const -> bool {
return enabled_; return enabled_;
} }
// Establece el valor de la variable // Temps que es mante visible despres de completar el text.
void Writer::setEnabledCounter(int time) { void Writer::setRemainingTime(float seconds) {
enabled_counter_ = time; remaining_time_s_ = seconds;
} }
// Obtiene el valor de la variable auto Writer::getRemainingTime() const -> float {
auto Writer::getEnabledCounter() const -> int { return remaining_time_s_;
return enabled_counter_;
} }
// Centra la cadena de texto a un punto X // Centra la cadena de texto a un punto X
+17 -17
View File
@@ -8,19 +8,19 @@ class Writer {
public: public:
explicit Writer(Text *text); // Constructor explicit Writer(Text *text); // Constructor
void update(); // Actualiza el objeto void update(float dt_s); // Actualiza el objeto
void render(); // Dibuja el objeto en pantalla void render(); // Dibuja el objeto en pantalla
void setPosX(int value); // Establece el valor de la variable void setPosX(int value); // Establece el valor de la variable
void setPosY(int value); // Establece el valor de la variable void setPosY(int value); // Establece el valor de la variable
void setKerning(int value); // Establece el valor de la variable void setKerning(int value); // Establece el valor de la variable
void setCaption(const std::string &text); // Establece el valor de la variable void setCaption(const std::string &text); // Establece el valor de la variable
void setSpeed(int value); // Establece el valor de la variable void setSecondsPerChar(float seconds); // Segons per caracter
void setEnabled(bool value); // Establece el valor de la variable void setEnabled(bool value); // Establece el valor de la variable
[[nodiscard]] auto isEnabled() const -> bool; // Obtiene el valor de la variable [[nodiscard]] auto isEnabled() const -> bool; // Obtiene el valor de la variable
void setEnabledCounter(int time); // Establece el valor de la variable void setRemainingTime(float seconds); // Temps despres de completar
[[nodiscard]] auto getEnabledCounter() const -> int; // Obtiene el valor de la variable [[nodiscard]] auto getRemainingTime() const -> float; // Temps restant
void center(int x); // Centra la cadena de texto a un punto X void center(int x); // Centra la cadena de texto a un punto X
[[nodiscard]] auto hasFinished() const -> bool; // Obtiene el valor de la variable [[nodiscard]] auto hasFinished() const -> bool; // Obtiene el valor de la variable
@@ -30,16 +30,16 @@ class Writer {
Text *text_; // Objeto encargado de escribir el texto Text *text_; // Objeto encargado de escribir el texto
// Variables // Variables
int pos_x_{0}; // Posicion en el eje X donde empezar a escribir el texto int pos_x_{0}; // Posicion en el eje X donde empezar a escribir el texto
int pos_y_{0}; // Posicion en el eje Y donde empezar a escribir el texto int pos_y_{0}; // Posicion en el eje Y donde empezar a escribir el texto
int kerning_{0}; // Kerning del texto, es decir, espaciado entre caracteres int kerning_{0}; // Kerning del texto, es decir, espaciado entre caracteres
std::string caption_; // El texto para escribir std::string caption_; // El texto para escribir
int speed_{0}; // Velocidad de escritura float seconds_per_char_{0.0F}; // Segons per caracter
int writing_counter_{0}; // Temporizador de escritura para cada caracter float char_timer_s_{0.0F}; // Acumulador d'avanç de caracter
int index_{0}; // Posición del texto que se está escribiendo int index_{0}; // Posición del texto que se está escribiendo
int length_{0}; // Longitud de la cadena a escribir int length_{0}; // Longitud de la cadena a escribir
bool completed_{false}; // Indica si se ha escrito todo el texto bool completed_{false}; // Indica si se ha escrito todo el texto
bool enabled_{false}; // Indica si el objeto está habilitado bool enabled_{false}; // Indica si el objeto está habilitado
int enabled_counter_{0}; // Temporizador para deshabilitar el objeto float remaining_time_s_{0.0F}; // Temps restant per a deshabilitar
bool finished_{false}; // Indica si ya ha terminado bool finished_{false}; // Indica si ya ha terminado
}; };
+22
View File
@@ -0,0 +1,22 @@
#include "core/system/delta_time.hpp"
#include <SDL3/SDL.h>
namespace DeltaTime {
namespace {
Uint64 last_time_ms = 0;
}
void reset() {
last_time_ms = SDL_GetTicks();
}
auto tick() -> float {
const Uint64 NOW_MS = SDL_GetTicks();
const float DELTA_S = static_cast<float>(NOW_MS - last_time_ms) / 1000.0F;
last_time_ms = NOW_MS;
return DELTA_S;
}
} // namespace DeltaTime
+20
View File
@@ -0,0 +1,20 @@
#pragma once
// Font única de delta_time per al joc. El loop principal NO té vsync ni
// gates: cada escena crida `tick()` al començament del seu iterate() i rep
// els segons reals transcorreguts des de l'última crida. Així el moviment és
// independent del framerate (visualment suau a 2000 FPS o a 60 FPS).
//
// `reset()` reinicia el rellotge intern: cal cridar-lo en cada canvi
// d'escena (després de càrregues llargues que podrien generar un primer
// delta enorme) i quan es reprèn d'una pausa.
namespace DeltaTime {
// Reinicia el rellotge a "ara". Cap delta acumulat del passat.
void reset();
// Retorna els segons des de l'última crida a `tick()` o `reset()`.
auto tick() -> float;
} // namespace DeltaTime
+142 -234
View File
@@ -1,6 +1,7 @@
#include "game/entities/balloon.h" #include "game/entities/balloon.h"
#include <cmath> // for std::fabs #include <algorithm> // for std::max
#include <cmath> // for std::fabs
#include "core/rendering/animatedsprite.h" // for AnimatedSprite #include "core/rendering/animatedsprite.h" // for AnimatedSprite
#include "core/rendering/movingsprite.h" // for MovingSprite #include "core/rendering/movingsprite.h" // for MovingSprite
@@ -17,194 +18,144 @@ Balloon::Balloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 c
switch (kind) { switch (kind) {
case BALLOON_1: case BALLOON_1:
// Alto y ancho del objeto
width_ = WIDTH_1; width_ = WIDTH_1;
height_ = WIDTH_1; height_ = WIDTH_1;
size_ = SIZE_1; size_ = SIZE_1;
power_ = 1; power_ = 1;
// Inicializa los valores de velocidad y gravedad vel_x_s_ = velx;
this->vel_x_ = velx; vel_y_s_ = 0.0F;
vel_y_ = 0; max_vel_y_s_ = 180.0F;
max_vel_y_ = 3.0F; gravity_s_ = 324.0F;
gravity_ = 0.09F; default_vel_y_s_ = 156.0F;
default_vel_y_ = 2.6F;
// Puntos que da el globo al ser destruido
score_ = SCORE_1; score_ = SCORE_1;
// Amenaza que genera el globo
menace_ = 1; menace_ = 1;
break; break;
case BALLOON_2: case BALLOON_2:
// Alto y ancho del objeto
width_ = WIDTH_2; width_ = WIDTH_2;
height_ = WIDTH_2; height_ = WIDTH_2;
size_ = SIZE_2; size_ = SIZE_2;
power_ = 3; power_ = 3;
// Inicializa los valores de velocidad y gravedad vel_x_s_ = velx;
this->vel_x_ = velx; vel_y_s_ = 0.0F;
vel_y_ = 0; max_vel_y_s_ = 180.0F;
max_vel_y_ = 3.0F; gravity_s_ = 360.0F;
gravity_ = 0.10F; default_vel_y_s_ = 210.0F;
default_vel_y_ = 3.5F;
// Puntos que da el globo al ser destruido
score_ = SCORE_2; score_ = SCORE_2;
// Amenaza que genera el globo
menace_ = 2; menace_ = 2;
break; break;
case BALLOON_3: case BALLOON_3:
// Alto y ancho del objeto
width_ = WIDTH_3; width_ = WIDTH_3;
height_ = WIDTH_3; height_ = WIDTH_3;
size_ = SIZE_3; size_ = SIZE_3;
power_ = 7; power_ = 7;
// Inicializa los valores de velocidad y gravedad vel_x_s_ = velx;
this->vel_x_ = velx; vel_y_s_ = 0.0F;
vel_y_ = 0; max_vel_y_s_ = 180.0F;
max_vel_y_ = 3.0F; gravity_s_ = 360.0F;
gravity_ = 0.10F; default_vel_y_s_ = 270.0F;
default_vel_y_ = 4.50F;
// Puntos que da el globo al ser destruido
score_ = SCORE_3; score_ = SCORE_3;
// Amenaza que genera el globo
menace_ = 4; menace_ = 4;
break; break;
case BALLOON_4: case BALLOON_4:
// Alto y ancho del objeto
width_ = WIDTH_4; width_ = WIDTH_4;
height_ = WIDTH_4; height_ = WIDTH_4;
size_ = SIZE_4; size_ = SIZE_4;
power_ = 15; power_ = 15;
// Inicializa los valores de velocidad y gravedad vel_x_s_ = velx;
this->vel_x_ = velx; vel_y_s_ = 0.0F;
vel_y_ = 0; max_vel_y_s_ = 180.0F;
max_vel_y_ = 3.0F; gravity_s_ = 360.0F;
gravity_ = 0.10F; default_vel_y_s_ = 297.0F;
default_vel_y_ = 4.95F;
// Puntos que da el globo al ser destruido
score_ = SCORE_4; score_ = SCORE_4;
// Amenaza que genera el globo
menace_ = 8; menace_ = 8;
break; break;
case HEXAGON_1: case HEXAGON_1:
// Alto y ancho del objeto
width_ = WIDTH_1; width_ = WIDTH_1;
height_ = WIDTH_1; height_ = WIDTH_1;
size_ = SIZE_1; size_ = SIZE_1;
power_ = 1; power_ = 1;
// Inicializa los valores de velocidad y gravedad vel_x_s_ = velx;
this->vel_x_ = velx; vel_y_s_ = std::fabs(velx) * 2.0F;
vel_y_ = std::fabs(velx) * 2; max_vel_y_s_ = std::fabs(velx) * 2.0F;
max_vel_y_ = std::fabs(velx) * 2; gravity_s_ = 0.0F;
gravity_ = 0.00F; default_vel_y_s_ = std::fabs(velx) * 2.0F;
default_vel_y_ = std::fabs(velx) * 2;
// Puntos que da el globo al ser destruido
score_ = SCORE_1; score_ = SCORE_1;
// Amenaza que genera el globo
menace_ = 1; menace_ = 1;
break; break;
case HEXAGON_2: case HEXAGON_2:
// Alto y ancho del objeto
width_ = WIDTH_2; width_ = WIDTH_2;
height_ = WIDTH_2; height_ = WIDTH_2;
size_ = SIZE_2; size_ = SIZE_2;
power_ = 3; power_ = 3;
// Inicializa los valores de velocidad y gravedad vel_x_s_ = velx;
this->vel_x_ = velx; vel_y_s_ = std::fabs(velx) * 2.0F;
vel_y_ = std::fabs(velx) * 2; max_vel_y_s_ = std::fabs(velx) * 2.0F;
max_vel_y_ = std::fabs(velx) * 2; gravity_s_ = 0.0F;
gravity_ = 0.00F; default_vel_y_s_ = std::fabs(velx) * 2.0F;
default_vel_y_ = std::fabs(velx) * 2;
// Puntos que da el globo al ser destruido
score_ = SCORE_2; score_ = SCORE_2;
// Amenaza que genera el globo
menace_ = 2; menace_ = 2;
break; break;
case HEXAGON_3: case HEXAGON_3:
// Alto y ancho del objeto
width_ = WIDTH_3; width_ = WIDTH_3;
height_ = WIDTH_3; height_ = WIDTH_3;
size_ = SIZE_3; size_ = SIZE_3;
power_ = 7; power_ = 7;
// Inicializa los valores de velocidad y gravedad vel_x_s_ = velx;
this->vel_x_ = velx; vel_y_s_ = std::fabs(velx) * 2.0F;
vel_y_ = std::fabs(velx) * 2; max_vel_y_s_ = std::fabs(velx) * 2.0F;
max_vel_y_ = std::fabs(velx) * 2; gravity_s_ = 0.0F;
gravity_ = 0.00F; default_vel_y_s_ = std::fabs(velx) * 2.0F;
default_vel_y_ = std::fabs(velx) * 2;
// Puntos que da el globo al ser destruido
score_ = SCORE_3; score_ = SCORE_3;
// Amenaza que genera el globo
menace_ = 4; menace_ = 4;
break; break;
case HEXAGON_4: case HEXAGON_4:
// Alto y ancho del objeto
width_ = WIDTH_4; width_ = WIDTH_4;
height_ = WIDTH_4; height_ = WIDTH_4;
size_ = SIZE_4; size_ = SIZE_4;
power_ = 15; power_ = 15;
// Inicializa los valores de velocidad y gravedad vel_x_s_ = velx;
this->vel_x_ = velx; vel_y_s_ = std::fabs(velx) * 2.0F;
vel_y_ = std::fabs(velx) * 2; max_vel_y_s_ = std::fabs(velx) * 2.0F;
max_vel_y_ = std::fabs(velx) * 2; gravity_s_ = 0.0F;
gravity_ = 0.00F; default_vel_y_s_ = std::fabs(velx) * 2.0F;
default_vel_y_ = std::fabs(velx) * 2;
// Puntos que da el globo al ser destruido
score_ = SCORE_4; score_ = SCORE_4;
// Amenaza que genera el globo
menace_ = 8; menace_ = 8;
break; break;
case POWER_BALL: case POWER_BALL:
// Alto y ancho del objeto
width_ = WIDTH_4; width_ = WIDTH_4;
height_ = WIDTH_4; height_ = WIDTH_4;
size_ = 4; size_ = 4;
power_ = 0; power_ = 0;
// Inicializa los valores de velocidad y gravedad vel_x_s_ = velx;
this->vel_x_ = velx; vel_y_s_ = 0.0F;
vel_y_ = 0; max_vel_y_s_ = 180.0F;
max_vel_y_ = 3.0F; gravity_s_ = 360.0F;
gravity_ = 0.10F; default_vel_y_s_ = 297.0F;
default_vel_y_ = 4.95F;
// Puntos que da el globo al ser destruido // Puntos que da el globo al ser destruido
score_ = 0; score_ = 0;
@@ -217,7 +168,7 @@ Balloon::Balloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 c
// sempre que canvia. setRotateSpeed(1) evita la UB del `counter_ // sempre que canvia. setRotateSpeed(1) evita la UB del `counter_
// % 0` dins MovingSprite::rotate(). // % 0` dins MovingSprite::rotate().
sprite_->setRotateSpeed(1); sprite_->setRotateSpeed(1);
sprite_->setRotateAmount(vel_x_ > 0.0F ? 2.0 : -2.0); sprite_->setRotateAmount(vel_x_s_ > 0.0F ? 2.0 : -2.0);
break; break;
@@ -257,18 +208,21 @@ Balloon::Balloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 c
// Inicializa variables // Inicializa variables
stopped_ = true; stopped_ = true;
stopped_counter_ = 0; stopped_counter_ = 0;
stopped_counter_s_ = 0.0F;
blinking_ = false; blinking_ = false;
visible_ = true; visible_ = true;
creation_counter_ = creationtimer; creation_counter_ = creationtimer;
creation_counter_ini_ = creationtimer; creation_counter_ini_ = creationtimer;
creation_counter_s_ = static_cast<float>(creationtimer) / 60.0F;
creation_counter_ini_s_ = creation_counter_s_;
creation_phase_s_ = 0.0F;
bounce_phase_s_ = 0.0F;
popping_ = false; popping_ = false;
// Valores iniciales dependentes del timer // Valores iniciales dependentes del timer
being_created_ = creation_counter_ != 0; being_created_ = creation_counter_ != 0;
invulnerable_ = being_created_; invulnerable_ = being_created_;
counter_ = 0;
travel_y_ = 1.0F;
this->speed_ = speed; this->speed_ = speed;
// Tipo // Tipo
@@ -334,87 +288,38 @@ void Balloon::render() {
} }
} }
// Actualiza la posición y estados del globo // Actualiza la posición y estados del globo. Integració contínua: gravetat i
void Balloon::move() { // posició s'apliquen escalades per `speed_` (tempo del joc) cada tick.
// Comprueba si se puede mover void Balloon::move(float dt_s) {
if (!isStopped()) { if (!isStopped()) {
// Lo mueve a izquierda o derecha // Eix X
pos_x_ += (vel_x_ * speed_); pos_x_ += vel_x_s_ * speed_ * dt_s;
// Si queda fuera de pantalla, corregimos su posición y cambiamos su sentido
if ((pos_x_ < PLAY_AREA_LEFT) || (pos_x_ + width_ > PLAY_AREA_RIGHT)) { if ((pos_x_ < PLAY_AREA_LEFT) || (pos_x_ + width_ > PLAY_AREA_RIGHT)) {
// Corrige posición pos_x_ -= vel_x_s_ * speed_ * dt_s;
pos_x_ -= (vel_x_ * speed_); vel_x_s_ = -vel_x_s_;
// Invierte sentido
vel_x_ = -vel_x_;
// Invierte la rotación
sprite_->switchRotate(); sprite_->switchRotate();
if (kind_ != POWER_BALL) { bounceStart(); }
// Activa el efecto de rebote
if (kind_ != POWER_BALL) {
bounceStart();
}
} }
// Mueve el globo hacia arriba o hacia abajo // Eix Y
pos_y_ += (vel_y_ * speed_); pos_y_ += vel_y_s_ * speed_ * dt_s;
// Si se sale por arriba
if (pos_y_ < PLAY_AREA_TOP) { if (pos_y_ < PLAY_AREA_TOP) {
// Corrige
pos_y_ = PLAY_AREA_TOP; pos_y_ = PLAY_AREA_TOP;
vel_y_s_ = -vel_y_s_;
// Invierte sentido if (kind_ != POWER_BALL) { bounceStart(); }
vel_y_ = -vel_y_;
// Activa el efecto de rebote
if (kind_ != POWER_BALL) {
bounceStart();
}
} }
// Si el globo se sale por la parte inferior
if (pos_y_ + height_ > PLAY_AREA_BOTTOM) { if (pos_y_ + height_ > PLAY_AREA_BOTTOM) {
// Corrige
pos_y_ = PLAY_AREA_BOTTOM - height_; pos_y_ = PLAY_AREA_BOTTOM - height_;
vel_y_s_ = -default_vel_y_s_;
// Invierte colocando una velocidad por defecto if (kind_ != POWER_BALL) { bounceStart(); }
vel_y_ = -default_vel_y_;
// Activa el efecto de rebote
if (kind_ != POWER_BALL) {
bounceStart();
}
} }
/* // Gravetat contínua (el tempo `speed_` escala també la gravetat).
vel_y_s_ += gravity_s_ * speed_ * dt_s;
Para aplicar la gravedad, el diseño original la aplicaba en cada iteración del bucle
Al añadir el modificador de velocidad se reduce la distancia que recorre el objeto y por
tanto recibe mas gravedad. Para solucionarlo se va a aplicar la gravedad cuando se haya
recorrido una distancia igual a la velocidad en Y, que era el cálculo inicial
*/
// Incrementa la variable que calcula la distancia acumulada en Y
travel_y_ += speed_;
// Si la distancia acumulada en Y es igual a la velocidad, se aplica la gravedad
if (travel_y_ >= 1.0F) {
// Quita el excedente
travel_y_ -= 1.0F;
// Aplica la gravedad al objeto sin pasarse de una velocidad máxima
vel_y_ += gravity_;
// Al parecer esta asignación se quedó sin hacer y ahora el juego no funciona
// correctamente si se aplica, así que se deja sin efecto
// velY = std::min(velY, maxVelY);
}
// Actualiza la posición del sprite
sprite_->setPosX(getPosX()); sprite_->setPosX(getPosX());
sprite_->setPosY(getPosY()); sprite_->setPosY(getPosY());
} }
@@ -427,16 +332,19 @@ void Balloon::disable() {
collider_.r = 0; collider_.r = 0;
collider_.x = 0; collider_.x = 0;
collider_.y = 0; collider_.y = 0;
counter_ = 0;
creation_counter_ = 0; creation_counter_ = 0;
creation_counter_ini_ = 0; creation_counter_ini_ = 0;
default_vel_y_ = 0.0F; creation_counter_s_ = 0.0F;
creation_counter_ini_s_ = 0.0F;
creation_phase_s_ = 0.0F;
bounce_phase_s_ = 0.0F;
default_vel_y_s_ = 0.0F;
enabled_ = false; enabled_ = false;
gravity_ = 0.0F; gravity_s_ = 0.0F;
height_ = 0; height_ = 0;
invulnerable_ = false; invulnerable_ = false;
kind_ = 0; kind_ = 0;
max_vel_y_ = 0.0F; max_vel_y_s_ = 0.0F;
menace_ = 0; menace_ = 0;
popping_ = false; popping_ = false;
pos_x_ = 0.0F; pos_x_ = 0.0F;
@@ -447,9 +355,9 @@ void Balloon::disable() {
speed_ = 0; speed_ = 0;
stopped_ = false; stopped_ = false;
stopped_counter_ = 0; stopped_counter_ = 0;
travel_y_ = 0; stopped_counter_s_ = 0.0F;
vel_x_ = 0.0F; vel_x_s_ = 0.0F;
vel_y_ = 0.0F; vel_y_s_ = 0.0F;
visible_ = false; visible_ = false;
width_ = 0; width_ = 0;
sprite_->clear(); sprite_->clear();
@@ -466,30 +374,29 @@ void Balloon::pop() {
} }
// Actualiza al globo a su posicion, animación y controla los contadores // Actualiza al globo a su posicion, animación y controla los contadores
void Balloon::update() { void Balloon::update(float dt_s) {
if (enabled_) { if (enabled_) {
sprite_->MovingSprite::update(); // MovingSprite::update(dt_s) avança la rotació (entre altres). La posició
move(); // del sprite la posa move(dt_s) directament des de pos_x_/pos_y_.
updateAnimation(); sprite_->MovingSprite::update(dt_s);
move(dt_s);
updateAnimation(dt_s);
updateColliders(); updateColliders();
updateState(); updateState(dt_s);
updateBounce(); updateBounce(dt_s);
counter_++;
} }
} }
// Actualiza los estados del globo // Actualiza los estados del globo
void Balloon::updateState() { void Balloon::updateState(float dt_s) {
if (isPopping()) { if (isPopping()) {
updateStatePopping(); updateStatePopping();
} }
if (isBeingCreated()) { if (isBeingCreated()) {
updateStateBeingCreated(); updateStateBeingCreated(dt_s);
} } else if (isStopped()) {
// Solo comprueba el estado detenido cuando no se está creando updateStateStopped(dt_s);
else if (isStopped()) {
updateStateStopped();
} }
} }
@@ -502,58 +409,54 @@ void Balloon::updateStatePopping() {
} }
} }
// Rama de updateState: globo creándose // Rama de updateState: globo creándose. Manté el chunk pattern original:
void Balloon::updateStateBeingCreated() { // cada CREATION_STEP_S s'aplica un step de drift (equivalent a "cada 10
// frames" del codi original). El drift en X usa vel_x_s_/60 per a obtenir
// el mateix delta px-per-step.
void Balloon::updateStateBeingCreated(float dt_s) {
setStop(true); setStop(true);
setInvulnerable(true); setInvulnerable(true);
// Todavia tiene tiempo en el contador if (creation_counter_s_ > 0.0F) {
if (creation_counter_ > 0) { creation_phase_s_ += dt_s;
// Desplaza lentamente el globo hacia abajo y hacia un lado while (creation_phase_s_ >= CREATION_STEP_S) {
if (creation_counter_ % 10 == 0) { creation_phase_s_ -= CREATION_STEP_S;
pos_y_++; pos_y_ += 1.0F;
pos_x_ += vel_x_; const float DRIFT_X = vel_x_s_ / 60.0F;
pos_x_ += DRIFT_X;
// Comprueba no se salga por los laterales
if ((pos_x_ < PLAY_AREA_LEFT) || (pos_x_ > (PLAY_AREA_RIGHT - width_))) { if ((pos_x_ < PLAY_AREA_LEFT) || (pos_x_ > (PLAY_AREA_RIGHT - width_))) {
// Corrige y cambia el sentido de la velocidad pos_x_ -= DRIFT_X;
pos_x_ -= vel_x_; vel_x_s_ = -vel_x_s_;
vel_x_ = -vel_x_;
} }
// Actualiza la posición del sprite
sprite_->setPosX(getPosX()); sprite_->setPosX(getPosX());
sprite_->setPosY(getPosY()); sprite_->setPosY(getPosY());
// Actualiza la posición del circulo de colisión
updateColliders(); updateColliders();
} }
creation_counter_--; creation_counter_s_ = std::max(0.0F, creation_counter_s_ - dt_s);
} creation_counter_ = static_cast<Uint16>(creation_counter_s_ * 60.0F);
// El contador ha llegado a cero } else {
else {
setBeingCreated(false); setBeingCreated(false);
setStop(false); // reactiva la rotació de la PowerBall si escau setStop(false);
setVisible(true); setVisible(true);
setInvulnerable(false); setInvulnerable(false);
} }
} }
// Rama de updateState: globo detenido (no creándose) // Rama de updateState: globo detenido (no creándose)
void Balloon::updateStateStopped() { void Balloon::updateStateStopped(float dt_s) {
// Reduce el contador if (stopped_counter_s_ > 0.0F) {
if (stopped_counter_ > 0) { stopped_counter_s_ = std::max(0.0F, stopped_counter_s_ - dt_s);
stopped_counter_--; stopped_counter_ = static_cast<Uint16>(stopped_counter_s_ * 60.0F);
} } else if (!isPopping()) {
// Quitarles el estado "detenido" si no estan explosionando setStop(false);
else if (!isPopping()) {
setStop(false); // reactiva la rotació de la PowerBall si escau
} }
} }
// Establece la animación correspondiente al estado // Establece la animación correspondiente al estado
void Balloon::updateAnimation() { void Balloon::updateAnimation(float dt_s) {
std::string creating_animation = "blue"; std::string creating_animation = "blue";
std::string normal_animation = "orange"; std::string normal_animation = "orange";
@@ -565,7 +468,6 @@ void Balloon::updateAnimation() {
normal_animation = "green"; normal_animation = "green";
} }
// Establece el frame de animación
if (isPopping()) { if (isPopping()) {
sprite_->setCurrentAnimation("pop"); sprite_->setCurrentAnimation("pop");
} else if (isBeingCreated()) { } else if (isBeingCreated()) {
@@ -574,7 +476,7 @@ void Balloon::updateAnimation() {
sprite_->setCurrentAnimation(normal_animation); sprite_->setCurrentAnimation(normal_animation);
} }
sprite_->animate(); sprite_->animate(dt_s);
} }
// Comprueba si el globo está habilitado // Comprueba si el globo está habilitado
@@ -592,11 +494,6 @@ auto Balloon::getPosY() const -> float {
return pos_y_; return pos_y_;
} }
// Obtiene del valor de la variable
auto Balloon::getVelY() const -> float {
return vel_y_;
}
// Obtiene del valor de la variable // Obtiene del valor de la variable
auto Balloon::getWidth() const -> int { auto Balloon::getWidth() const -> int {
return width_; return width_;
@@ -607,9 +504,9 @@ auto Balloon::getHeight() const -> int {
return height_; return height_;
} }
// Establece el valor de la variable // Establece el valor de la variable (px/s)
void Balloon::setVelY(float vel_y) { void Balloon::setVelY(float vel_y) {
this->vel_y_ = vel_y; this->vel_y_s_ = vel_y;
} }
// Establece el valor de la variable // Establece el valor de la variable
@@ -709,6 +606,7 @@ auto Balloon::isPopping() const -> bool {
// Establece el valor de la variable // Establece el valor de la variable
void Balloon::setStoppedTimer(Uint16 time) { void Balloon::setStoppedTimer(Uint16 time) {
stopped_counter_ = time; stopped_counter_ = time;
stopped_counter_s_ = static_cast<float>(time) / 60.0F;
} }
// Obtiene del valor de la variable // Obtiene del valor de la variable
@@ -766,17 +664,27 @@ void Balloon::bounceStop() {
bouncing_.desp_y = 0.0F; bouncing_.desp_y = 0.0F;
} }
void Balloon::updateBounce() { void Balloon::updateBounce(float dt_s) {
if (bouncing_.enabled) { if (!bouncing_.enabled) { return; }
bouncing_.zoom_width = bouncing_.w[bouncing_.counter / bouncing_.speed];
bouncing_.zoom_height = bouncing_.h[bouncing_.counter / bouncing_.speed]; bounce_phase_s_ += dt_s;
sprite_->setZoomW(bouncing_.zoom_width); const float STEP_S = static_cast<float>(bouncing_.speed) * BOUNCE_STEP_S;
sprite_->setZoomH(bouncing_.zoom_height); while (bounce_phase_s_ >= STEP_S) {
bouncing_.desp_x = (sprite_->getSpriteClip().w - (sprite_->getSpriteClip().w * bouncing_.zoom_width)); bounce_phase_s_ -= STEP_S;
bouncing_.desp_y = (sprite_->getSpriteClip().h - (sprite_->getSpriteClip().h * bouncing_.zoom_height));
bouncing_.counter++; bouncing_.counter++;
if ((bouncing_.counter / bouncing_.speed) > (MAX_BOUNCE - 1)) {
bounceStop();
}
} }
}
const int IDX = bouncing_.counter / bouncing_.speed;
if (IDX > (MAX_BOUNCE - 1)) {
bounceStop();
bounce_phase_s_ = 0.0F;
return;
}
bouncing_.zoom_width = bouncing_.w[IDX];
bouncing_.zoom_height = bouncing_.h[IDX];
sprite_->setZoomW(bouncing_.zoom_width);
sprite_->setZoomH(bouncing_.zoom_height);
bouncing_.desp_x = (sprite_->getSpriteClip().w - (sprite_->getSpriteClip().w * bouncing_.zoom_width));
bouncing_.desp_y = (sprite_->getSpriteClip().h - (sprite_->getSpriteClip().h * bouncing_.zoom_height));
}
+51 -44
View File
@@ -39,9 +39,9 @@ class Balloon {
static constexpr int BALLOON_CLASS = 0; static constexpr int BALLOON_CLASS = 0;
static constexpr int HEXAGON_CLASS = 1; static constexpr int HEXAGON_CLASS = 1;
// Velocidad del globo // Velocitat horitzontal en px/s (era 0.7 px/frame * 60).
static constexpr float VELX_POSITIVE = 0.7F; static constexpr float VELX_POSITIVE = 42.0F;
static constexpr float VELX_NEGATIVE = -0.7F; static constexpr float VELX_NEGATIVE = -42.0F;
// Velocidades a las que se mueven los globos // Velocidades a las que se mueven los globos
static constexpr float SPEED_1 = 0.60F; static constexpr float SPEED_1 = 0.60F;
@@ -66,12 +66,12 @@ class Balloon {
Balloon(const Balloon &) = delete; Balloon(const Balloon &) = delete;
auto operator=(const Balloon &) -> Balloon & = delete; auto operator=(const Balloon &) -> Balloon & = delete;
void allignTo(int x); // Centra el globo en la posición X void allignTo(int x); // Centra el globo en la posición X
void render(); // Pinta el globo en la pantalla void render(); // Pinta el globo en la pantalla
void move(); // Actualiza la posición y estados del globo void move(float dt_s); // Actualiza la posición y estados del globo
void disable(); // Deshabilita el globo y pone a cero todos los valores void disable(); // Deshabilita el globo y pone a cero todos los valores
void pop(); // Explosiona el globo void pop(); // Explosiona el globo
void update(); // Actualiza al globo a su posicion, animación y controla los contadores void update(float dt_s); // Actualiza al globo
[[nodiscard]] auto isEnabled() const -> bool; // Comprueba si el globo está habilitado [[nodiscard]] auto isEnabled() const -> bool; // Comprueba si el globo está habilitado
[[nodiscard]] auto isStopped() const -> bool; // Obtiene del valor de la variable [[nodiscard]] auto isStopped() const -> bool; // Obtiene del valor de la variable
@@ -83,7 +83,6 @@ class Balloon {
[[nodiscard]] auto getPosX() const -> float; // Obtiene del valor de la variable [[nodiscard]] auto getPosX() const -> float; // Obtiene del valor de la variable
[[nodiscard]] auto getPosY() const -> float; // Obtiene del valor de la variable [[nodiscard]] auto getPosY() const -> float; // Obtiene del valor de la variable
[[nodiscard]] auto getVelY() const -> float; // Obtiene del valor de la variable
[[nodiscard]] auto getWidth() const -> int; // Obtiene del valor de la variable [[nodiscard]] auto getWidth() const -> int; // Obtiene del valor de la variable
[[nodiscard]] auto getHeight() const -> int; // Obtiene del valor de la variable [[nodiscard]] auto getHeight() const -> int; // Obtiene del valor de la variable
[[nodiscard]] auto getKind() const -> int; // Obtiene del valor de la variable [[nodiscard]] auto getKind() const -> int; // Obtiene del valor de la variable
@@ -109,6 +108,11 @@ class Balloon {
// Cantidad de elementos del vector con los valores de la deformación del globo al rebotar // Cantidad de elementos del vector con los valores de la deformación del globo al rebotar
static constexpr int MAX_BOUNCE = 10; static constexpr int MAX_BOUNCE = 10;
// Time-based: la creació "desplaça" el globus cada 10 frames a 60Hz ⇒ 1/6 s.
static constexpr float CREATION_STEP_S = 10.0F / 60.0F;
// Time-based: el bounce avança w/h cada `speed_` frames a 60Hz; mantenim el mateix temps per pas.
static constexpr float BOUNCE_STEP_S = 1.0F / 60.0F; // 1 frame
// Estructura para las variables para el efecto de los rebotes // Estructura para las variables para el efecto de los rebotes
struct Bouncing { struct Bouncing {
bool enabled; // Si el efecto está activo bool enabled; // Si el efecto está activo
@@ -126,47 +130,50 @@ class Balloon {
AnimatedSprite *sprite_; // Sprite del objeto globo AnimatedSprite *sprite_; // Sprite del objeto globo
// Variables // Variables
float pos_x_; // Posición en el eje X float pos_x_; // Posición en el eje X
float pos_y_; // Posición en el eje Y float pos_y_; // Posición en el eje Y
Uint8 width_; // Ancho Uint8 width_; // Ancho
Uint8 height_; // Alto Uint8 height_; // Alto
float vel_x_; // Velocidad en el eje X. Cantidad de pixeles a desplazarse float vel_x_s_{0.0F}; // Velocidad en X (px/s)
float vel_y_; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse float vel_y_s_{0.0F}; // Velocidad en Y (px/s)
float gravity_; // Aceleración en el eje Y. Modifica la velocidad float gravity_s_{0.0F}; // Aceleración Y (px/s²)
float default_vel_y_; // Velocidad inicial que tienen al rebotar contra el suelo float default_vel_y_s_{0.0F}; // Velocitat inicial al rebotar (px/s)
float max_vel_y_; // Máxima velocidad que puede alcanzar el objeto en el eje Y float max_vel_y_s_{0.0F}; // Velocitat màxima en Y (px/s, no aplicada en time-based actual)
bool being_created_; // Indica si el globo se está creando bool being_created_; // Indica si el globo se está creando
bool blinking_; // Indica si el globo está intermitente bool blinking_; // Indica si el globo está intermitente
bool enabled_; // Indica si el globo esta activo bool enabled_; // Indica si el globo esta activo
bool invulnerable_; // Indica si el globo es invulnerable bool invulnerable_; // Indica si el globo es invulnerable
bool popping_; // Indica si el globo está explotando bool popping_; // Indica si el globo está explotando
bool stopped_; // Indica si el globo está parado bool stopped_; // Indica si el globo está parado
bool visible_; // Indica si el globo es visible bool visible_; // Indica si el globo es visible
Circle collider_; // Circulo de colisión del objeto Circle collider_; // Circulo de colisión del objeto
Uint16 creation_counter_; // Temporizador para controlar el estado "creandose" Uint16 creation_counter_; // Temporizador (frames, derivat de creation_counter_s_ per a render alpha)
Uint16 creation_counter_ini_; // Valor inicial para el temporizador para controlar el estado "creandose" Uint16 creation_counter_ini_; // Valor inicial del temporizador (frames)
Uint16 score_; // Puntos que da el globo al ser destruido float creation_counter_s_{0.0F}; // Temporizador (font de veritat, segons)
Uint16 stopped_counter_; // Contador para controlar el estado "parado" float creation_counter_ini_s_{0.0F}; // Valor inicial del temporizador (segons)
Uint8 kind_; // Tipo de globo float creation_phase_s_{0.0F}; // Acumulador de fase per als steps de creació
Uint8 menace_; // Cantidad de amenaza que genera el globo Uint16 score_; // Puntos que da el globo al ser destruido
Uint32 counter_; // Contador interno Uint16 stopped_counter_; // Contador (frames, derivat de stopped_counter_s_)
float travel_y_; // Distancia que ha de recorrer el globo en el eje Y antes de que se le aplique la gravedad float stopped_counter_s_{0.0F}; // Contador (font de veritat, segons)
float speed_; // Velocidad a la que se mueven los globos Uint8 kind_; // Tipo de globo
Uint8 size_; // Tamaño del globo Uint8 menace_; // Cantidad de amenaza que genera el globo
Uint8 power_; // Cantidad de poder que alberga el globo float speed_; // Tempo del joc (multiplicador adimensional)
Bouncing bouncing_; // Contiene las variables para el efecto de rebote Uint8 size_; // Tamaño del globo
Uint8 power_; // Cantidad de poder que alberga el globo
Bouncing bouncing_; // Contiene las variables para el efecto de rebote
float bounce_phase_s_{0.0F}; // Fase del bounce
void updateColliders(); // Alinea el circulo de colisión con la posición del objeto globo void updateColliders(); // Alinea el circulo de colisión con la posición del objeto globo
void bounceStart(); // Activa el efecto void bounceStart(); // Activa el efecto
void bounceStop(); // Detiene el efecto void bounceStop(); // Detiene el efecto
void updateBounce(); // Aplica el efecto void updateBounce(float dt_s); // Aplica el efecto
void updateAnimation(); // Establece la animación correspondiente void updateAnimation(float dt_s); // Establece la animación correspondiente
void setBeingCreated(bool value); // Establece el valor de la variable void setBeingCreated(bool value); // Establece el valor de la variable
void updateState(); // Actualiza los estados del globo void updateState(float dt_s); // Actualiza los estados del globo
// Helpers de updateState, uno por cada rama de estado // Helpers de updateState, uno por cada rama de estado
void updateStatePopping(); void updateStatePopping();
void updateStateBeingCreated(); void updateStateBeingCreated(float dt_s);
void updateStateStopped(); void updateStateStopped(float dt_s);
}; };
+14 -26
View File
@@ -11,13 +11,15 @@ Bullet::Bullet(int x, int y, Bullet::Kind kind, bool powered_up, int owner, Text
// Posición inicial del objeto // Posición inicial del objeto
pos_x_ = x; pos_x_ = x;
pos_y_ = y; pos_y_ = y;
pos_x_f_ = static_cast<float>(x);
pos_y_f_ = static_cast<float>(y);
// Alto y ancho del objeto // Alto y ancho del objeto
width_ = 10; width_ = 10;
height_ = 10; height_ = 10;
// Velocidad inicial en el eje Y // Velocidad inicial en el eje Y
vel_y_ = -3; vel_y_s_ = VEL_Y_PX_PER_S;
// Tipo de bala // Tipo de bala
this->kind_ = kind; this->kind_ = kind;
@@ -29,7 +31,7 @@ Bullet::Bullet(int x, int y, Bullet::Kind kind, bool powered_up, int owner, Text
switch (kind) { switch (kind) {
case Bullet::Kind::UP: case Bullet::Kind::UP:
// Establece la velocidad inicial // Establece la velocidad inicial
vel_x_ = 0; vel_x_s_ = 0.0F;
// Rectangulo con los gráficos del objeto // Rectangulo con los gráficos del objeto
if (!powered_up) { if (!powered_up) {
@@ -41,7 +43,7 @@ Bullet::Bullet(int x, int y, Bullet::Kind kind, bool powered_up, int owner, Text
case Bullet::Kind::LEFT: case Bullet::Kind::LEFT:
// Establece la velocidad inicial // Establece la velocidad inicial
vel_x_ = -2; vel_x_s_ = VEL_X_LEFT_PX_PER_S;
// Rectangulo con los gráficos del objeto // Rectangulo con los gráficos del objeto
if (!powered_up) { if (!powered_up) {
@@ -53,7 +55,7 @@ Bullet::Bullet(int x, int y, Bullet::Kind kind, bool powered_up, int owner, Text
case Bullet::Kind::RIGHT: case Bullet::Kind::RIGHT:
// Establece la velocidad inicial // Establece la velocidad inicial
vel_x_ = 2; vel_x_s_ = VEL_X_RIGHT_PX_PER_S;
// Rectangulo con los gráficos del objeto // Rectangulo con los gráficos del objeto
if (!powered_up) { if (!powered_up) {
@@ -84,40 +86,29 @@ void Bullet::render() {
sprite_->render(); sprite_->render();
} }
// Actualiza la posición y estado del objeto en horizontal // Actualiza la posición y estado del objeto
auto Bullet::move() -> MoveResult { auto Bullet::move(float dt_s) -> MoveResult {
// Variable con el valor de retorno
MoveResult msg = MoveResult::OK; MoveResult msg = MoveResult::OK;
// Mueve el objeto a su nueva posición pos_x_f_ += vel_x_s_ * dt_s;
pos_x_ += vel_x_; pos_x_ = static_cast<int>(pos_x_f_);
// Si el objeto se sale del area de juego por los laterales
if ((pos_x_ < PLAY_AREA_LEFT - width_) || (pos_x_ > PLAY_AREA_RIGHT)) { if ((pos_x_ < PLAY_AREA_LEFT - width_) || (pos_x_ > PLAY_AREA_RIGHT)) {
// Se deshabilita
kind_ = Bullet::Kind::NONE; kind_ = Bullet::Kind::NONE;
// Mensaje de salida
msg = MoveResult::OUT; msg = MoveResult::OUT;
} }
// Mueve el objeto a su nueva posición en vertical pos_y_f_ += vel_y_s_ * dt_s;
pos_y_ += vel_y_; pos_y_ = static_cast<int>(pos_y_f_);
// Si el objeto se sale del area de juego por la parte superior
if (pos_y_ < PLAY_AREA_TOP - height_) { if (pos_y_ < PLAY_AREA_TOP - height_) {
// Se deshabilita
kind_ = Bullet::Kind::NONE; kind_ = Bullet::Kind::NONE;
// Mensaje de salida
msg = MoveResult::OUT; msg = MoveResult::OUT;
} }
// Actualiza la posición del sprite
sprite_->setPosX(pos_x_); sprite_->setPosX(pos_x_);
sprite_->setPosY(pos_y_); sprite_->setPosY(pos_y_);
// Alinea el circulo de colisión con el objeto
shiftColliders(); shiftColliders();
return msg; return msg;
@@ -146,16 +137,13 @@ auto Bullet::getPosY() const -> int {
// Establece el valor de la variable // Establece el valor de la variable
void Bullet::setPosX(int x) { void Bullet::setPosX(int x) {
pos_x_ = x; pos_x_ = x;
pos_x_f_ = static_cast<float>(x);
} }
// Establece el valor de la variable // Establece el valor de la variable
void Bullet::setPosY(int y) { void Bullet::setPosY(int y) {
pos_y_ = y; pos_y_ = y;
} pos_y_f_ = static_cast<float>(y);
// Obtiene el valor de la variable
auto Bullet::getVelY() const -> int {
return vel_y_;
} }
// Obtiene el valor de la variable // Obtiene el valor de la variable
+19 -13
View File
@@ -32,14 +32,13 @@ class Bullet {
Bullet(const Bullet &) = delete; Bullet(const Bullet &) = delete;
auto operator=(const Bullet &) -> Bullet & = delete; auto operator=(const Bullet &) -> Bullet & = delete;
void render(); // Pinta el objeto en pantalla void render(); // Pinta el objeto en pantalla
auto move() -> MoveResult; // Actualiza la posición y estado del objeto auto move(float dt_s) -> MoveResult; // Actualiza la posición y estado del objeto
void disable(); // Deshabilita el objeto void disable(); // Deshabilita el objeto
[[nodiscard]] auto isEnabled() const -> bool; // Comprueba si el objeto está habilitado [[nodiscard]] auto isEnabled() const -> bool; // Comprueba si el objeto está habilitado
[[nodiscard]] auto getPosX() const -> int; // Obtiene el valor de la variable [[nodiscard]] auto getPosX() const -> int; // Obtiene el valor de la variable
[[nodiscard]] auto getPosY() const -> int; // Obtiene el valor de la variable [[nodiscard]] auto getPosY() const -> int; // Obtiene el valor de la variable
[[nodiscard]] auto getVelY() const -> int; // Obtiene el valor de la variable
[[nodiscard]] auto getKind() const -> Kind; // Obtiene el valor de la variable [[nodiscard]] auto getKind() const -> Kind; // Obtiene el valor de la variable
[[nodiscard]] auto getOwner() const -> int; // Obtiene el valor de la variable [[nodiscard]] auto getOwner() const -> int; // Obtiene el valor de la variable
@@ -49,19 +48,26 @@ class Bullet {
auto getCollider() -> Circle &; // Obtiene el circulo de colisión auto getCollider() -> Circle &; // Obtiene el circulo de colisión
private: private:
// Velocitats en px/s (derivades de les antigues px/frame * 60).
static constexpr float VEL_Y_PX_PER_S = -180.0F; // Era -3 px/frame
static constexpr float VEL_X_LEFT_PX_PER_S = -120.0F; // Era -2 px/frame
static constexpr float VEL_X_RIGHT_PX_PER_S = 120.0F; // Era +2 px/frame
// Objetos y punteros // Objetos y punteros
Sprite *sprite_; // Sprite con los graficos y métodos de pintado Sprite *sprite_; // Sprite con los graficos y métodos de pintado
// Variables // Variables
int pos_x_; // Posición en el eje X int pos_x_; // Posición en el eje X (px enters per al sprite/collider)
int pos_y_; // Posición en el eje Y int pos_y_; // Posición en el eje Y
Uint8 width_; // Ancho del objeto float pos_x_f_{0}; // Acumulador subpíxel
Uint8 height_; // Alto del objeto float pos_y_f_{0}; // Acumulador subpíxel
int vel_x_; // Velocidad en el eje X Uint8 width_; // Ancho del objeto
int vel_y_; // Velocidad en el eje Y Uint8 height_; // Alto del objeto
Kind kind_; // Tipo de objeto float vel_x_s_{0}; // Velocidad en el eje X (px/s)
int owner_; // Identificador del dueño del objeto float vel_y_s_{0}; // Velocidad en el eje Y (px/s)
Circle collider_; // Circulo de colisión del objeto Kind kind_; // Tipo de objeto
int owner_; // Identificador del dueño del objeto
Circle collider_; // Circulo de colisión del objeto
void shiftColliders(); // Alinea el circulo de colisión con el objeto void shiftColliders(); // Alinea el circulo de colisión con el objeto
}; };
+42 -42
View File
@@ -1,6 +1,7 @@
#include "game/entities/item.h" #include "game/entities/item.h"
#include <cstdlib> // for rand #include <algorithm> // for max
#include <cstdlib> // for rand
#include "core/rendering/animatedsprite.h" // for AnimatedSprite #include "core/rendering/animatedsprite.h" // for AnimatedSprite
#include "game/defaults.hpp" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_AR... #include "game/defaults.hpp" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_AR...
@@ -13,7 +14,8 @@ Item::Item(Id id, float x, float y, Texture *texture, const std::vector<std::str
this->id_ = id; this->id_ = id;
enabled_ = true; enabled_ = true;
time_to_live_ = 600; time_to_live_ = 600;
accel_x_ = 0.0F; time_to_live_s_ = TIME_TO_LIVE_S;
accel_x_s_ = 0.0F;
floor_collision_ = false; floor_collision_ = false;
if (id == Item::Id::COFFEE_MACHINE) { if (id == Item::Id::COFFEE_MACHINE) {
@@ -21,18 +23,20 @@ Item::Item(Id id, float x, float y, Texture *texture, const std::vector<std::str
height_ = 29; height_ = 29;
pos_x_ = (((int)x + (PLAY_AREA_WIDTH / 2)) % (PLAY_AREA_WIDTH - width_ - 5)) + 2; pos_x_ = (((int)x + (PLAY_AREA_WIDTH / 2)) % (PLAY_AREA_WIDTH - width_ - 5)) + 2;
pos_y_ = PLAY_AREA_TOP - height_; pos_y_ = PLAY_AREA_TOP - height_;
vel_x_ = 0.0F; vel_x_s_ = 0.0F;
vel_y_ = -0.1F; vel_y_s_ = COFFEE_VEL_Y_PX_PER_S;
accel_y_ = 0.1F; accel_y_s_ = COFFEE_ACCEL_Y_PX_PER_S2;
collider_.r = 10; collider_.r = 10;
} else { } else {
width_ = 16; width_ = 16;
height_ = 16; height_ = 16;
pos_x_ = x; pos_x_ = x;
pos_y_ = y; pos_y_ = y;
vel_x_ = -1.0F + ((rand() % 5) * 0.5F); // Distribució original: -1.0, -0.5, 0.0, 0.5, 1.0 px/frame ⇒ -60..60 px/s en passos de 30.
vel_y_ = -4.0F; const int RAND_STEP = rand() % 5;
accel_y_ = 0.2F; vel_x_s_ = (-2.0F + static_cast<float>(RAND_STEP)) * ITEM_VEL_X_STEP_PX_PER_S;
vel_y_s_ = ITEM_VEL_Y_PX_PER_S;
accel_y_s_ = ITEM_ACCEL_Y_PX_PER_S2;
collider_.r = width_ / 2; collider_.r = width_ / 2;
} }
@@ -74,49 +78,41 @@ void Item::render() {
} }
// Actualiza la posición y estados del objeto // Actualiza la posición y estados del objeto
void Item::move() { void Item::move(float dt_s) {
floor_collision_ = false; floor_collision_ = false;
// Calcula la nueva posición // Posició
pos_x_ += vel_x_; pos_x_ += vel_x_s_ * dt_s;
pos_y_ += vel_y_; pos_y_ += vel_y_s_ * dt_s;
// Aplica las aceleraciones a la velocidad // Acceleració
vel_x_ += accel_x_; vel_x_s_ += accel_x_s_ * dt_s;
vel_y_ += accel_y_; vel_y_s_ += accel_y_s_ * dt_s;
// Si queda fuera de pantalla, corregimos su posición y cambiamos su sentido // Si surt per laterals, corregeix i inverteix
if ((pos_x_ < PLAY_AREA_LEFT) || (pos_x_ + width_ > PLAY_AREA_RIGHT)) { if ((pos_x_ < PLAY_AREA_LEFT) || (pos_x_ + width_ > PLAY_AREA_RIGHT)) {
// Corregir posición pos_x_ -= vel_x_s_ * dt_s;
pos_x_ -= vel_x_; vel_x_s_ = -vel_x_s_;
// Invertir sentido
vel_x_ = -vel_x_;
} }
// Si se sale por arriba rebota (excepto la maquina de café) // Rebot per dalt (excepte la màquina de cafè)
if ((pos_y_ < PLAY_AREA_TOP) && !(id_ == Item::Id::COFFEE_MACHINE)) { if ((pos_y_ < PLAY_AREA_TOP) && !(id_ == Item::Id::COFFEE_MACHINE)) {
// Corrige pos_y_ -= vel_y_s_ * dt_s;
pos_y_ -= vel_y_; vel_y_s_ = -vel_y_s_;
// Invierte el sentido
vel_y_ = -vel_y_;
} }
// Si el objeto se sale por la parte inferior // Topa amb el terra
if (pos_y_ + height_ > PLAY_AREA_BOTTOM) { if (pos_y_ + height_ > PLAY_AREA_BOTTOM) {
// Detiene el objeto vel_y_s_ = 0;
vel_y_ = 0; vel_x_s_ = 0;
vel_x_ = 0; accel_x_s_ = 0;
accel_x_ = 0; accel_y_s_ = 0;
accel_y_ = 0;
pos_y_ = PLAY_AREA_BOTTOM - height_; pos_y_ = PLAY_AREA_BOTTOM - height_;
if (id_ == Item::Id::COFFEE_MACHINE) { if (id_ == Item::Id::COFFEE_MACHINE) {
floor_collision_ = true; floor_collision_ = true;
} }
} }
// Actualiza la posición del sprite
sprite_->setPosX(int(pos_x_)); sprite_->setPosX(int(pos_x_));
sprite_->setPosY(int(pos_y_)); sprite_->setPosY(int(pos_y_));
shiftColliders(); shiftColliders();
@@ -128,18 +124,22 @@ void Item::disable() {
} }
// Actualiza el objeto a su posicion, animación y controla los contadores // Actualiza el objeto a su posicion, animación y controla los contadores
void Item::update() { void Item::update(float dt_s) {
move(); move(dt_s);
sprite_->animate(); sprite_->animate(dt_s);
updateTimeToLive(); updateTimeToLive(dt_s);
checkTimeToLive(); checkTimeToLive();
} }
// Actualiza el contador // Actualiza el contador. Manté time_to_live_ (frames) sincronitzat amb el
void Item::updateTimeToLive() { // segons per a que render() segueixi funcionant amb la mateixa condició de
if (time_to_live_ > 0) { // parpelleig.
time_to_live_--; void Item::updateTimeToLive(float dt_s) {
if (time_to_live_s_ > 0.0F) {
time_to_live_s_ = std::max(0.0F, time_to_live_s_ - dt_s);
} }
constexpr float FRAMES_PER_S = 60.0F;
time_to_live_ = static_cast<Uint16>(time_to_live_s_ * FRAMES_PER_S);
} }
// Comprueba si el objeto sigue vivo // Comprueba si el objeto sigue vivo
+32 -20
View File
@@ -29,12 +29,12 @@ class Item {
Item(const Item &) = delete; Item(const Item &) = delete;
auto operator=(const Item &) -> Item & = delete; auto operator=(const Item &) -> Item & = delete;
void allignTo(int x); // Centra el objeto en la posición X void allignTo(int x); // Centra el objeto en la posición X
void render(); // Pinta el objeto en la pantalla void render(); // Pinta el objeto en la pantalla
void disable(); // Pone a cero todos los valores del objeto void disable(); // Pone a cero todos los valores del objeto
void update(); // Actualiza al objeto a su posicion, animación y controla los contadores void update(float dt_s); // Actualiza al objeto
void updateTimeToLive(); // Actualiza el contador void updateTimeToLive(float dt_s); // Actualiza el contador
void checkTimeToLive(); // Comprueba si el objeto sigue vivo void checkTimeToLive(); // Comprueba si el objeto sigue vivo
[[nodiscard]] auto getPosX() const -> float; // Obtiene del valor de la variable [[nodiscard]] auto getPosX() const -> float; // Obtiene del valor de la variable
[[nodiscard]] auto getPosY() const -> float; // Obtiene del valor de la variable [[nodiscard]] auto getPosY() const -> float; // Obtiene del valor de la variable
@@ -47,24 +47,36 @@ class Item {
auto getCollider() -> Circle &; // Obtiene el circulo de colisión auto getCollider() -> Circle &; // Obtiene el circulo de colisión
private: private:
// Time-based: equivalents en unitats físiques precalculades a 60Hz.
static constexpr float ITEM_VEL_Y_PX_PER_S = -240.0F; // Era -4.0 px/frame
static constexpr float ITEM_ACCEL_Y_PX_PER_S2 = 720.0F; // Era +0.2 px/frame²
static constexpr float ITEM_VEL_X_STEP_PX_PER_S = 30.0F; // Era 0.5 px/frame, 5 valors en [-1.0, 1.0]
static constexpr float COFFEE_VEL_Y_PX_PER_S = -6.0F; // Era -0.1 px/frame
static constexpr float COFFEE_ACCEL_Y_PX_PER_S2 = 360.0F; // Era +0.1 px/frame²
static constexpr float TIME_TO_LIVE_S = 10.0F; // Era 600 frames
static constexpr float BLINK_START_S = TIME_TO_LIVE_S - (200.0F / 60.0F); // Era time_to_live_ > 200
static constexpr float BLINK_PERIOD_S = 20.0F / 60.0F; // Era % 20
static constexpr float BLINK_OFF_S = 10.0F / 60.0F; // Era % 20 <= 10
// Objetos y punteros // Objetos y punteros
AnimatedSprite *sprite_; // Sprite con los graficos del objeto AnimatedSprite *sprite_; // Sprite con los graficos del objeto
// Variables // Variables
float pos_x_; // Posición X del objeto float pos_x_; // Posición X del objeto
float pos_y_; // Posición Y del objeto float pos_y_; // Posición Y del objeto
Uint8 width_; // Ancho del objeto Uint8 width_; // Ancho del objeto
Uint8 height_; // Alto del objeto Uint8 height_; // Alto del objeto
float vel_x_; // Velocidad en el eje X float vel_x_s_{0.0F}; // Velocidad en el eje X (px/s)
float vel_y_; // Velocidad en el eje Y float vel_y_s_{0.0F}; // Velocidad en el eje Y (px/s)
float accel_x_; // Aceleración en el eje X float accel_x_s_{0.0F}; // Aceleración en el eje X (px/s²)
float accel_y_; // Aceleración en el eje Y float accel_y_s_{0.0F}; // Aceleración en el eje Y (px/s²)
bool floor_collision_; // Indica si el objeto colisiona con el suelo bool floor_collision_; // Indica si el objeto colisiona con el suelo
Id id_; // Especifica el tipo de objeto que es Id id_; // Especifica el tipo de objeto que es
bool enabled_; // Especifica si el objeto está habilitado bool enabled_; // Especifica si el objeto está habilitado
Uint16 time_to_live_; // Temporizador con el tiempo que el objeto está presente Uint16 time_to_live_; // Temporizador (frames, derivat de time_to_live_s_ per a render())
Circle collider_; // Circulo de colisión del objeto float time_to_live_s_{0.0F}; // Temporizador (font de veritat, segons)
Circle collider_; // Circulo de colisión del objeto
void shiftColliders(); // Alinea el circulo de colisión con la posición del objeto void shiftColliders(); // Alinea el circulo de colisión con la posición del objeto
void move(); // Actualiza la posición y estados del objeto void move(float dt_s); // Actualiza la posición y estados del objeto
}; };
+80 -75
View File
@@ -1,6 +1,7 @@
#include "game/entities/player.h" #include "game/entities/player.h"
#include <algorithm> #include <algorithm>
#include <cmath> // for fmod
#include <cstdlib> // for rand #include <cstdlib> // for rand
#include "core/input/input.h" // for InputAction #include "core/input/input.h" // for InputAction
@@ -42,12 +43,15 @@ void Player::init() {
// Inicializa variables de estado // Inicializa variables de estado
alive_ = true; alive_ = true;
death_counter_ = DEATH_COUNTER; death_counter_ = DEATH_COUNTER;
death_counter_s_ = DEATH_DURATION_S;
status_walking_ = STATUS_WALKING_STOP; status_walking_ = STATUS_WALKING_STOP;
status_firing_ = STATUS_FIRING_NO; status_firing_ = STATUS_FIRING_NO;
invulnerable_ = false; invulnerable_ = false;
invulnerable_counter_ = INVULNERABLE_COUNTER; invulnerable_counter_ = INVULNERABLE_COUNTER;
invulnerable_counter_s_ = INVULNERABLE_DURATION_S;
power_up_ = false; power_up_ = false;
power_up_counter_ = POWERUP_COUNTER; power_up_counter_ = POWERUP_COUNTER;
power_up_counter_s_ = POWERUP_DURATION_S;
extra_hit_ = false; extra_hit_ = false;
coffees_ = 0; coffees_ = 0;
input_ = true; input_ = true;
@@ -63,11 +67,10 @@ void Player::init() {
shiftColliders(); shiftColliders();
// Establece la velocidad inicial // Establece la velocidad inicial
vel_x_ = 0; vel_x_s_ = 0.0F;
vel_y_ = 0;
// Establece la velocidad base // Establece la velocidad base
base_speed_ = 1.5; base_speed_s_ = BASE_SPEED_PX_PER_S;
// Establece la puntuación inicial // Establece la puntuación inicial
score_ = 0; score_ = 0;
@@ -77,6 +80,7 @@ void Player::init() {
// Inicia el contador para la cadencia de disparo // Inicia el contador para la cadencia de disparo
cooldown_ = 10; cooldown_ = 10;
cooldown_s_ = COOLDOWN_S;
// Establece la posición del sprite // Establece la posición del sprite
legs_sprite_->setPosX(pos_x_); legs_sprite_->setPosX(pos_x_);
@@ -98,12 +102,12 @@ void Player::init() {
void Player::setInput(Input::Action input) { void Player::setInput(Input::Action input) {
switch (input) { switch (input) {
case Input::Action::LEFT: case Input::Action::LEFT:
vel_x_ = -base_speed_; vel_x_s_ = -base_speed_s_;
setWalkingStatus(STATUS_WALKING_LEFT); setWalkingStatus(STATUS_WALKING_LEFT);
break; break;
case Input::Action::RIGHT: case Input::Action::RIGHT:
vel_x_ = base_speed_; vel_x_s_ = base_speed_s_;
setWalkingStatus(STATUS_WALKING_RIGHT); setWalkingStatus(STATUS_WALKING_RIGHT);
break; break;
@@ -120,24 +124,21 @@ void Player::setInput(Input::Action input) {
break; break;
default: default:
vel_x_ = 0; vel_x_s_ = 0.0F;
setWalkingStatus(STATUS_WALKING_STOP); setWalkingStatus(STATUS_WALKING_STOP);
break; break;
} }
} }
// Mueve el jugador a la posición y animación que le corresponde // Mueve el jugador a la posición y animación que le corresponde
void Player::move() { void Player::move(float dt_s) {
if (isAlive()) { if (isAlive()) {
// Mueve el jugador a derecha o izquierda pos_x_ += vel_x_s_ * dt_s;
pos_x_ += vel_x_;
// Si el jugador abandona el area de juego por los laterales if ((pos_x_ < PLAY_AREA_LEFT - 5) || (pos_x_ + width_ > PLAY_AREA_RIGHT + 5)) {
if ((pos_x_ < PLAY_AREA_LEFT - 5) || (pos_x_ + width_ > PLAY_AREA_RIGHT + 5)) { // Restaura su posición pos_x_ -= vel_x_s_ * dt_s;
pos_x_ -= vel_x_;
} }
// Actualiza la posición del sprite
legs_sprite_->setPosX(getPosX()); legs_sprite_->setPosX(getPosX());
legs_sprite_->setPosY(pos_y_); legs_sprite_->setPosY(pos_y_);
@@ -150,14 +151,11 @@ void Player::move() {
fire_sprite_->setPosX(getPosX() - 2); fire_sprite_->setPosX(getPosX() - 2);
fire_sprite_->setPosY(pos_y_ - 8); fire_sprite_->setPosY(pos_y_ - 8);
} else { } else {
death_sprite_->update(); death_sprite_->update(dt_s);
// Si el cadaver abandona el area de juego por los laterales if ((death_sprite_->getPosX() < PLAY_AREA_LEFT) || (death_sprite_->getPosX() + width_ > PLAY_AREA_RIGHT)) {
if ((death_sprite_->getPosX() < PLAY_AREA_LEFT) || (death_sprite_->getPosX() + width_ > PLAY_AREA_RIGHT)) { // Restaura su posición
const float VX = death_sprite_->getVelX(); const float VX = death_sprite_->getVelX();
death_sprite_->setPosX(death_sprite_->getPosX() - VX); death_sprite_->setPosX(death_sprite_->getPosX() - (VX * dt_s));
// Rebota
death_sprite_->setVelX(-VX); death_sprite_->setVelX(-VX);
} }
} }
@@ -199,8 +197,7 @@ void Player::setFiringStatus(Uint8 status) {
} }
// Establece la animación correspondiente al estado // Establece la animación correspondiente al estado
void Player::setAnimation() { void Player::setAnimation(float dt_s) {
// Crea cadenas de texto para componer el nombre de la animación
std::string body_coffees; std::string body_coffees;
std::string head_coffees; std::string head_coffees;
if (coffees_ > 0) { if (coffees_ > 0) {
@@ -215,27 +212,25 @@ void Player::setAnimation() {
const SDL_FlipMode FLIP_WALK = status_walking_ == STATUS_WALKING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE; const SDL_FlipMode FLIP_WALK = status_walking_ == STATUS_WALKING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
const SDL_FlipMode FLIP_FIRE = status_firing_ == STATUS_FIRING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE; const SDL_FlipMode FLIP_FIRE = status_firing_ == STATUS_FIRING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
// Establece la animación a partir de las cadenas
legs_sprite_->setCurrentAnimation(WALKING); legs_sprite_->setCurrentAnimation(WALKING);
legs_sprite_->setFlip(FLIP_WALK); legs_sprite_->setFlip(FLIP_WALK);
if (status_firing_ == STATUS_FIRING_NO) { // No esta disparando if (status_firing_ == STATUS_FIRING_NO) {
body_sprite_->setCurrentAnimation(WALKING + body_coffees + POWER_UP); body_sprite_->setCurrentAnimation(WALKING + body_coffees + POWER_UP);
body_sprite_->setFlip(FLIP_WALK); body_sprite_->setFlip(FLIP_WALK);
head_sprite_->setCurrentAnimation(WALKING + head_coffees + POWER_UP); head_sprite_->setCurrentAnimation(WALKING + head_coffees + POWER_UP);
head_sprite_->setFlip(FLIP_WALK); head_sprite_->setFlip(FLIP_WALK);
} else { // Está disparando } else {
body_sprite_->setCurrentAnimation(FIRING + body_coffees + POWER_UP); body_sprite_->setCurrentAnimation(FIRING + body_coffees + POWER_UP);
body_sprite_->setFlip(FLIP_FIRE); body_sprite_->setFlip(FLIP_FIRE);
head_sprite_->setCurrentAnimation(FIRING + head_coffees + POWER_UP); head_sprite_->setCurrentAnimation(FIRING + head_coffees + POWER_UP);
head_sprite_->setFlip(FLIP_FIRE); head_sprite_->setFlip(FLIP_FIRE);
} }
// Actualiza las animaciones de los sprites legs_sprite_->animate(dt_s);
legs_sprite_->animate(); body_sprite_->animate(dt_s);
body_sprite_->animate(); head_sprite_->animate(dt_s);
head_sprite_->animate();
fire_sprite_->animate(); fire_sprite_->animate(dt_s);
fire_sprite_->setFlip(FLIP_WALK); fire_sprite_->setFlip(FLIP_WALK);
} }
@@ -268,30 +263,37 @@ auto Player::canFire() const -> bool {
// Establece el valor de la variable // Establece el valor de la variable
void Player::setFireCooldown(int time) { void Player::setFireCooldown(int time) {
cooldown_ = time; cooldown_ = time;
cooldown_s_ = static_cast<float>(time) / 60.0F;
} }
// Actualiza el valor de la variable // Establece el valor del cooldown en segons (time-based)
void Player::updateCooldown() { void Player::setFireCooldownS(float seconds) {
if (cooldown_ > 0) { cooldown_s_ = seconds;
cooldown_--; cooldown_ = static_cast<int>(seconds * 60.0F);
if (power_up_) { }
cooldown_--;
} // Actualiza el cooldown. Quan està en mode PowerUp, el cooldown
// es consumeix el doble de ràpid (equivalent a decrementar 2 frames per tick).
void Player::updateCooldown(float dt_s) {
if (cooldown_s_ > 0.0F) {
const float RATE = power_up_ ? 2.0F : 1.0F;
cooldown_s_ = std::max(0.0F, cooldown_s_ - (dt_s * RATE));
cooldown_ = static_cast<int>(cooldown_s_ * 60.0F);
} else { } else {
setFiringStatus(STATUS_FIRING_NO); setFiringStatus(STATUS_FIRING_NO);
} }
} }
// Actualiza al jugador a su posicion, animación y controla los contadores // Actualiza al jugador a su posicion, animación y controla los contadores
void Player::update() { void Player::update(float dt_s) {
move(); move(dt_s);
setAnimation(); setAnimation(dt_s);
shiftColliders(); shiftColliders();
updateCooldown(); updateCooldown(dt_s);
updatePowerUpCounter(); updatePowerUpCounter(dt_s);
updateInvulnerableCounter(); updateInvulnerableCounter(dt_s);
updateDeathCounter(); updateDeathCounter(dt_s);
updatePowerUpHeadOffset(); updatePowerUpHeadOffset(dt_s);
} }
// Obtiene la puntuación del jugador // Obtiene la puntuación del jugador
@@ -321,11 +323,13 @@ void Player::setAlive(bool value) {
if (!value) { if (!value) {
death_sprite_->setPosX(head_sprite_->getRect().x); death_sprite_->setPosX(head_sprite_->getRect().x);
death_sprite_->setPosY(head_sprite_->getRect().y); death_sprite_->setPosY(head_sprite_->getRect().y);
death_sprite_->setAccelY(0.2F); // Física del cadàver en px/s i px/s² — Game crida Player::update(dt_s)
death_sprite_->setVelY(-6.6F); // que delega a death_sprite_->update(dt_s) (time-based).
death_sprite_->setVelX(3.3F); death_sprite_->setAccelY(DEATH_ACCEL_Y_PX_PER_S2);
death_sprite_->setVelY(DEATH_VEL_Y_PX_PER_S);
death_sprite_->setVelX(DEATH_VEL_X_PX_PER_S);
if (rand() % 2 == 0) { if (rand() % 2 == 0) {
death_sprite_->setVelX(-3.3F); death_sprite_->setVelX(-DEATH_VEL_X_PX_PER_S);
} }
} }
} }
@@ -376,25 +380,30 @@ auto Player::getInvulnerableCounter() const -> Uint16 {
// Establece el valor de la variable // Establece el valor de la variable
void Player::setInvulnerableCounter(Uint16 value) { void Player::setInvulnerableCounter(Uint16 value) {
invulnerable_counter_ = value; invulnerable_counter_ = value;
invulnerable_counter_s_ = static_cast<float>(value) / 60.0F;
} }
// Actualiza el valor de la variable // Actualiza el contador d'invulnerabilitat. Manté el counter enter
void Player::updateInvulnerableCounter() { // sincronitzat perquè render() segueixi parpellejant igual.
void Player::updateInvulnerableCounter(float dt_s) {
if (invulnerable_) { if (invulnerable_) {
if (invulnerable_counter_ > 0) { if (invulnerable_counter_s_ > 0.0F) {
invulnerable_counter_--; invulnerable_counter_s_ = std::max(0.0F, invulnerable_counter_s_ - dt_s);
invulnerable_counter_ = static_cast<Uint16>(invulnerable_counter_s_ * 60.0F);
} else { } else {
invulnerable_ = false; invulnerable_ = false;
invulnerable_counter_ = INVULNERABLE_COUNTER; invulnerable_counter_ = INVULNERABLE_COUNTER;
invulnerable_counter_s_ = INVULNERABLE_DURATION_S;
} }
} }
} }
// Actualiza el valor de la variable // Actualiza el comptador de mort
void Player::updateDeathCounter() { void Player::updateDeathCounter(float dt_s) {
if (!alive_) { if (!alive_) {
if (death_counter_ > 0) { if (death_counter_s_ > 0.0F) {
death_counter_--; death_counter_s_ = std::max(0.0F, death_counter_s_ - dt_s);
death_counter_ = static_cast<Uint16>(death_counter_s_ * 60.0F);
} }
} }
} }
@@ -417,15 +426,18 @@ auto Player::getPowerUpCounter() const -> Uint16 {
// Establece el valor de la variable // Establece el valor de la variable
void Player::setPowerUpCounter(Uint16 value) { void Player::setPowerUpCounter(Uint16 value) {
power_up_counter_ = value; power_up_counter_ = value;
power_up_counter_s_ = static_cast<float>(value) / 60.0F;
} }
// Actualiza el valor de la variable // Actualiza el comptador de PowerUp
void Player::updatePowerUpCounter() { void Player::updatePowerUpCounter(float dt_s) {
if ((power_up_counter_ > 0) && (power_up_)) { if ((power_up_counter_s_ > 0.0F) && (power_up_)) {
power_up_counter_--; power_up_counter_s_ = std::max(0.0F, power_up_counter_s_ - dt_s);
power_up_counter_ = static_cast<Uint16>(power_up_counter_s_ * 60.0F);
} else { } else {
power_up_ = false; power_up_ = false;
power_up_counter_ = POWERUP_COUNTER; power_up_counter_ = POWERUP_COUNTER;
power_up_counter_s_ = POWERUP_DURATION_S;
} }
} }
@@ -451,6 +463,7 @@ void Player::removeExtraHit() {
} }
invulnerable_ = true; invulnerable_ = true;
invulnerable_counter_ = INVULNERABLE_COUNTER; invulnerable_counter_ = INVULNERABLE_COUNTER;
invulnerable_counter_s_ = INVULNERABLE_DURATION_S;
} }
// Habilita la entrada de ordenes // Habilita la entrada de ordenes
@@ -490,21 +503,13 @@ auto Player::getDeathCounter() const -> Uint16 {
return death_counter_; return death_counter_;
} }
// Actualiza el valor de la variable // Actualiza l'offset. dt_s no s'usa directament: el blink final depèn de
void Player::updatePowerUpHeadOffset() { // power_up_counter_s_ que ja s'està actualitzant a updatePowerUpCounter.
if (!power_up_) { void Player::updatePowerUpHeadOffset([[maybe_unused]] float dt_s) {
// powerUpHeadOffset = 0; if (!power_up_) { return; }
} else { if (power_up_counter_s_ < POWERUP_BLINK_THRESHOLD_S) {
// powerUpHeadOffset = 96; const float PHASE = std::fmod(power_up_counter_s_, BLINK_PERIOD_S);
if (power_up_counter_ < 300) { fire_sprite_->setEnabled(PHASE <= BLINK_OFF_S);
if (power_up_counter_ % 10 > 4) {
// powerUpHeadOffset = 96;
fire_sprite_->setEnabled(false);
} else {
// powerUpHeadOffset = 0;
fire_sprite_->setEnabled(true);
}
}
} }
} }
+45 -27
View File
@@ -5,8 +5,8 @@
#include <string> // for string #include <string> // for string
#include <vector> // for vector #include <vector> // for vector
#include "utils/utils.h" // for Circle
#include "core/input/input.h" // for Input::Action #include "core/input/input.h" // for Input::Action
#include "utils/utils.h" // for Circle
class AnimatedSprite; class AnimatedSprite;
class Texture; class Texture;
@@ -21,14 +21,14 @@ class Player {
Player(const Player &) = delete; Player(const Player &) = delete;
auto operator=(const Player &) -> Player & = delete; auto operator=(const Player &) -> Player & = delete;
void init(); // Iniciador void init(); // Iniciador
void update(); // Actualiza al jugador a su posicion, animación y controla los contadores void update(float dt_s); // Actualiza al jugador
void render(); // Pinta el jugador en pantalla void render(); // Pinta el jugador en pantalla
void move(); // Mueve el jugador a la posición y animación que le corresponde void move(float dt_s); // Mueve el jugador
void setPlayerTextures(const std::vector<Texture *> &texture); // Pone las texturas del jugador void setPlayerTextures(const std::vector<Texture *> &texture); // Pone las texturas del jugador
void setInput(Input::Action input); // Actua en consecuencia de la entrada recibida void setInput(Input::Action input); // Actua en consecuencia de la entrada recibida
void setAnimation(); // Establece la animación correspondiente al estado void setAnimation(float dt_s); // Establece la animación correspondiente al estado
[[nodiscard]] auto getPosX() const -> int; // Obtiene el valor de la variable [[nodiscard]] auto getPosX() const -> int; // Obtiene el valor de la variable
[[nodiscard]] auto getPosY() const -> int; // Obtiene el valor de la variable [[nodiscard]] auto getPosY() const -> int; // Obtiene el valor de la variable
@@ -36,8 +36,9 @@ class Player {
[[nodiscard]] auto getHeight() const -> int; // Obtiene el valor de la variable [[nodiscard]] auto getHeight() const -> int; // Obtiene el valor de la variable
[[nodiscard]] auto canFire() const -> bool; // Indica si el jugador puede disparar [[nodiscard]] auto canFire() const -> bool; // Indica si el jugador puede disparar
void setFireCooldown(int time); // Establece el valor de la variable void setFireCooldown(int time); // Establece el valor de la variable (frames)
void updateCooldown(); // Actualiza el valor de la variable void setFireCooldownS(float seconds); // Establece el valor de la variable (segons)
void updateCooldown(float dt_s); // Actualiza el valor de la variable
[[nodiscard]] auto getScore() const -> Uint32; // Obtiene la puntuación del jugador [[nodiscard]] auto getScore() const -> Uint32; // Obtiene la puntuación del jugador
void setScore(Uint32 score); // Asigna un valor a la puntuación del jugador void setScore(Uint32 score); // Asigna un valor a la puntuación del jugador
@@ -60,7 +61,7 @@ class Player {
void setPowerUp(bool value); // Establece el valor de la variable void setPowerUp(bool value); // Establece el valor de la variable
[[nodiscard]] auto getPowerUpCounter() const -> Uint16; // Obtiene el valor de la variable [[nodiscard]] auto getPowerUpCounter() const -> Uint16; // Obtiene el valor de la variable
void setPowerUpCounter(Uint16 value); // Establece el valor de la variable void setPowerUpCounter(Uint16 value); // Establece el valor de la variable
void updatePowerUpCounter(); // Actualiza el valor de la variable void updatePowerUpCounter(float dt_s); // Actualiza el valor de la variable
[[nodiscard]] auto hasExtraHit() const -> bool; // Obtiene el valor de la variable [[nodiscard]] auto hasExtraHit() const -> bool; // Obtiene el valor de la variable
void giveExtraHit(); // Concede un toque extra al jugador void giveExtraHit(); // Concede un toque extra al jugador
@@ -89,6 +90,20 @@ class Player {
static constexpr int INVULNERABLE_COUNTER = 200; static constexpr int INVULNERABLE_COUNTER = 200;
static constexpr int POWERUP_COUNTER = 1500; static constexpr int POWERUP_COUNTER = 1500;
// Time-based: equivalents en segons/px·s a 60Hz (font de veritat).
static constexpr float BASE_SPEED_PX_PER_S = 90.0F; // Era 1.5 px/frame
static constexpr float COOLDOWN_S = 10.0F / 60.0F; // Era 10 frames
static constexpr float INVULNERABLE_DURATION_S = 200.0F / 60.0F;
static constexpr float POWERUP_DURATION_S = 1500.0F / 60.0F;
static constexpr float DEATH_DURATION_S = 350.0F / 60.0F;
static constexpr float POWERUP_BLINK_THRESHOLD_S = 300.0F / 60.0F; // Era power_up_counter_ < 300
static constexpr float BLINK_PERIOD_S = 10.0F / 60.0F; // Era % 10
static constexpr float BLINK_OFF_S = 4.0F / 60.0F; // Era % 10 > 4
// Death sprite (cadàver) — physics convertides de px/frame a px/s.
static constexpr float DEATH_ACCEL_Y_PX_PER_S2 = 0.2F * 60.0F * 60.0F; // = 720
static constexpr float DEATH_VEL_Y_PX_PER_S = -6.6F * 60.0F; // = -396
static constexpr float DEATH_VEL_X_PX_PER_S = 3.3F * 60.0F; // = 198
// Objetos y punteros // Objetos y punteros
SDL_Renderer *renderer_; // El renderizador de la ventana SDL_Renderer *renderer_; // El renderizador de la ventana
AnimatedSprite *head_sprite_; // Sprite para dibujar la cabeza AnimatedSprite *head_sprite_; // Sprite para dibujar la cabeza
@@ -104,11 +119,11 @@ class Player {
Uint8 width_; // Anchura Uint8 width_; // Anchura
Uint8 height_; // Altura Uint8 height_; // Altura
float vel_x_; // Cantidad de pixeles a desplazarse en el eje X float vel_x_s_{0.0F}; // Velocidad en el eje X (px/s)
int vel_y_; // Cantidad de pixeles a desplazarse en el eje Y
float base_speed_; // Velocidad base del jugador float base_speed_s_{0.0F}; // Velocidad base del jugador (px/s)
int cooldown_; // Contador durante el cual no puede disparar int cooldown_; // Contador durante el cual no puede disparar (frames, derivat de cooldown_s_)
float cooldown_s_{0.0F}; // Contador durante el cual no puede disparar (font de veritat, segons)
Uint32 score_; // Puntos del jugador Uint32 score_; // Puntos del jugador
float score_multiplier_; // Multiplicador de puntos float score_multiplier_; // Multiplicador de puntos
@@ -116,22 +131,25 @@ class Player {
Uint8 status_walking_; // Estado del jugador Uint8 status_walking_; // Estado del jugador
Uint8 status_firing_; // Estado del jugador Uint8 status_firing_; // Estado del jugador
bool alive_; // Indica si el jugador está vivo bool alive_; // Indica si el jugador está vivo
Uint16 death_counter_; // Contador para la animación de morirse Uint16 death_counter_; // Contador (frame-based)
bool invulnerable_; // Indica si el jugador es invulnerable float death_counter_s_{0.0F}; // Contador (time-based)
Uint16 invulnerable_counter_; // Contador para la invulnerabilidad bool invulnerable_; // Indica si el jugador es invulnerable
bool extra_hit_; // Indica si el jugador tiene un toque extra Uint16 invulnerable_counter_; // Contador (frame-based)
Uint8 coffees_; // Indica cuantos cafes lleva acumulados float invulnerable_counter_s_{0.0F}; // Contador (time-based)
bool power_up_; // Indica si el jugador tiene activo el modo PowerUp bool extra_hit_; // Indica si el jugador tiene un toque extra
Uint16 power_up_counter_; // Temporizador para el modo PowerUp Uint8 coffees_; // Indica cuantos cafes lleva acumulados
bool input_; // Indica si puede recibir ordenes de entrada bool power_up_; // Indica si el jugador tiene activo el modo PowerUp
Circle collider_; // Circulo de colisión del jugador Uint16 power_up_counter_; // Temporizador (frame-based)
float power_up_counter_s_{0.0F}; // Temporizador (time-based)
bool input_; // Indica si puede recibir ordenes de entrada
Circle collider_; // Circulo de colisión del jugador
void setWalkingStatus(Uint8 status); // Establece el estado del jugador void setWalkingStatus(Uint8 status); // Establece el estado del jugador
void setFiringStatus(Uint8 status); // Establece el estado del jugador void setFiringStatus(Uint8 status); // Establece el estado del jugador
void shiftColliders(); // Actualiza el circulo de colisión a la posición del jugador void shiftColliders(); // Actualiza el circulo de colisión a la posición del jugador
void updateInvulnerableCounter(); // Actualiza el valor de la variable void updateInvulnerableCounter(float dt_s); // Actualiza el valor de la variable
void updateDeathCounter(); // Actualiza el valor de la variable void updateDeathCounter(float dt_s); // Actualiza el valor de la variable
void updatePowerUpHeadOffset(); // Actualiza el valor de la variable void updatePowerUpHeadOffset(float dt_s); // Actualiza el valor de la variable
}; };
+285 -313
View File
@@ -20,13 +20,14 @@
#include "core/rendering/texture.h" // for Texture #include "core/rendering/texture.h" // for Texture
#include "core/resources/asset.h" // for Asset #include "core/resources/asset.h" // for Asset
#include "core/resources/resource.h" #include "core/resources/resource.h"
#include "game/defaults.hpp" // for PLAY_AREA_CENTER_X, BLOCK, PLAY_AREA_CEN... #include "core/system/delta_time.hpp" // for DeltaTime
#include "game/entities/balloon.h" // for Balloon, Balloon::VELX_NEGATIVE, BALLOON_... #include "game/defaults.hpp" // for PLAY_AREA_CENTER_X, BLOCK, PLAY_AREA_CEN...
#include "game/entities/bullet.h" // for Bullet, Bullet::Kind::LEFT, Bullet::Kind::RIGHT, BULLE... #include "game/entities/balloon.h" // for Balloon, Balloon::VELX_NEGATIVE, BALLOON_...
#include "game/entities/item.h" // for Item #include "game/entities/bullet.h" // for Bullet, Bullet::Kind::LEFT, Bullet::Kind::RIGHT, BULLE...
#include "game/entities/player.h" // for Player #include "game/entities/item.h" // for Item
#include "game/options.hpp" // for Options #include "game/entities/player.h" // for Player
#include "game/ui/menu.h" // for Menu #include "game/options.hpp" // for Options
#include "game/ui/menu.h" // for Menu
namespace Ja { namespace Ja {
struct Sound; struct Sound;
} // namespace Ja } // namespace Ja
@@ -102,6 +103,9 @@ Game::Game(int num_players, int current_stage, SDL_Renderer *renderer, bool demo
// Inicializa las variables necesarias para la sección 'Game' // Inicializa las variables necesarias para la sección 'Game'
init(); init();
// Reset del rellotge perquè el primer dt_s no inclogui el temps de càrrega.
DeltaTime::reset();
} }
Game::~Game() { Game::~Game() {
@@ -146,9 +150,6 @@ Game::~Game() {
// Inicializa las variables necesarias para la sección 'Game' // Inicializa las variables necesarias para la sección 'Game'
void Game::init() { void Game::init() {
ticks_ = 0;
ticks_speed_ = 15;
// Elimina qualquier jugador que hubiese antes de crear los nuevos // Elimina qualquier jugador que hubiese antes de crear los nuevos
for (auto *player : players_) { for (auto *player : players_) {
delete player; delete player;
@@ -200,18 +201,27 @@ void Game::init() {
game_completed_ = false; game_completed_ = false;
game_completed_counter_ = 0; game_completed_counter_ = 0;
game_completed_counter_s_ = 0.0F;
section_->name = SECTION_PROG_GAME; section_->name = SECTION_PROG_GAME;
section_->subsection = SUBSECTION_GAME_PLAY_1P; section_->subsection = SUBSECTION_GAME_PLAY_1P;
menace_current_ = 0; menace_current_ = 0;
menace_threshold_ = 0; menace_threshold_ = 0;
hi_score_achieved_ = false; hi_score_achieved_ = false;
stage_bitmap_counter_ = STAGE_COUNTER; stage_bitmap_counter_ = STAGE_COUNTER;
stage_bitmap_counter_s_ = STAGE_COUNTER / 60.0F;
death_counter_ = Player::DEATH_COUNTER; death_counter_ = Player::DEATH_COUNTER;
death_counter_s_ = Player::DEATH_COUNTER / 60.0F;
time_stopped_ = false; time_stopped_ = false;
time_stopped_counter_ = 0; time_stopped_counter_ = 0;
time_stopped_counter_s_ = 0.0F;
counter_ = 0; counter_ = 0;
elapsed_s_ = 0.0F;
last_enemy_deploy_ = 0; last_enemy_deploy_ = 0;
enemy_deploy_counter_ = 0; enemy_deploy_counter_ = 0;
enemy_deploy_counter_s_ = 0.0F;
enemy_deploy_phase_s_ = 0.0F;
shake_phase_s_ = 0.0F;
helper_counter_s_ = 0.0F;
enemy_speed_ = default_enemy_speed_; enemy_speed_ = default_enemy_speed_;
effect_.flash = false; effect_.flash = false;
effect_.shake = false; effect_.shake = false;
@@ -284,15 +294,15 @@ void Game::init() {
// Con los globos creados, calcula el nivel de amenaza // Con los globos creados, calcula el nivel de amenaza
evaluateAndSetMenace(); evaluateAndSetMenace();
// Inicializa el bitmap de 1000 puntos // Inicializa el bitmap de 1000 puntos (px/s i px/s²; -0.5 px/frame → -30 px/s)
n1000_sprite_->setPosX(0); n1000_sprite_->setPosX(0);
n1000_sprite_->setPosY(0); n1000_sprite_->setPosY(0);
n1000_sprite_->setWidth(26); n1000_sprite_->setWidth(26);
n1000_sprite_->setHeight(9); n1000_sprite_->setHeight(9);
n1000_sprite_->setVelX(0.0F); n1000_sprite_->setVelX(0.0F);
n1000_sprite_->setVelY(-0.5F); n1000_sprite_->setVelY(-30.0F);
n1000_sprite_->setAccelX(0.0F); n1000_sprite_->setAccelX(0.0F);
n1000_sprite_->setAccelY(-0.1F); n1000_sprite_->setAccelY(-360.0F);
n1000_sprite_->setSpriteClip(0, 0, 26, 9); n1000_sprite_->setSpriteClip(0, 0, 26, 9);
n1000_sprite_->setEnabled(false); n1000_sprite_->setEnabled(false);
n1000_sprite_->setEnabledCounter(0); n1000_sprite_->setEnabledCounter(0);
@@ -305,9 +315,9 @@ void Game::init() {
n2500_sprite_->setWidth(28); n2500_sprite_->setWidth(28);
n2500_sprite_->setHeight(9); n2500_sprite_->setHeight(9);
n2500_sprite_->setVelX(0.0F); n2500_sprite_->setVelX(0.0F);
n2500_sprite_->setVelY(-0.5F); n2500_sprite_->setVelY(-30.0F);
n2500_sprite_->setAccelX(0.0F); n2500_sprite_->setAccelX(0.0F);
n2500_sprite_->setAccelY(-0.1F); n2500_sprite_->setAccelY(-360.0F);
n2500_sprite_->setSpriteClip(26, 0, 28, 9); n2500_sprite_->setSpriteClip(26, 0, 28, 9);
n2500_sprite_->setEnabled(false); n2500_sprite_->setEnabled(false);
n2500_sprite_->setEnabledCounter(0); n2500_sprite_->setEnabledCounter(0);
@@ -320,9 +330,9 @@ void Game::init() {
n5000_sprite_->setWidth(28); n5000_sprite_->setWidth(28);
n5000_sprite_->setHeight(9); n5000_sprite_->setHeight(9);
n5000_sprite_->setVelX(0.0F); n5000_sprite_->setVelX(0.0F);
n5000_sprite_->setVelY(-0.5F); n5000_sprite_->setVelY(-30.0F);
n5000_sprite_->setAccelX(0.0F); n5000_sprite_->setAccelX(0.0F);
n5000_sprite_->setAccelY(-0.1F); n5000_sprite_->setAccelY(-360.0F);
n5000_sprite_->setSpriteClip(54, 0, 28, 9); n5000_sprite_->setSpriteClip(54, 0, 28, 9);
n5000_sprite_->setEnabled(false); n5000_sprite_->setEnabled(false);
n5000_sprite_->setEnabledCounter(0); n5000_sprite_->setEnabledCounter(0);
@@ -1429,11 +1439,10 @@ void Game::renderScoreBoard() {
} }
// Actualiza las variables del jugador // Actualiza las variables del jugador
void Game::updatePlayers() { void Game::updatePlayers(float dt_s) {
for (auto *player : players_) { for (auto *player : players_) {
player->update(); player->update(dt_s);
// Comprueba la colisión entre el jugador y los globos
if (checkPlayerBalloonCollision(player)) { if (checkPlayerBalloonCollision(player)) {
if (player->isAlive()) { if (player->isAlive()) {
if (demo_.enabled) { if (demo_.enabled) {
@@ -1445,7 +1454,6 @@ void Game::updatePlayers() {
} }
} }
// Comprueba las colisiones entre el jugador y los items
checkPlayerItemCollision(player); checkPlayerItemCollision(player);
} }
} }
@@ -1458,19 +1466,18 @@ void Game::renderPlayers() {
} }
// Actualiza las variables de la fase // Actualiza las variables de la fase
void Game::updateStage() { void Game::updateStage(float dt_s) {
if (stage_[current_stage_].current_power >= stage_[current_stage_].power_to_complete) { if (stage_[current_stage_].current_power >= stage_[current_stage_].power_to_complete) {
// Cambio de fase
current_stage_++; current_stage_++;
last_stage_reached_ = current_stage_; last_stage_reached_ = current_stage_;
if (current_stage_ == 10) { // Ha llegado al final el juego if (current_stage_ == 10) {
game_completed_ = true; // Marca el juego como completado game_completed_ = true;
current_stage_ = 9; // Deja el valor dentro de los limites current_stage_ = 9;
stage_[current_stage_].current_power = 0; // Deja el poder a cero para que no vuelva a entrar en esta condición stage_[current_stage_].current_power = 0;
destroyAllBalloons(); // Destruye a todos los enemigos destroyAllBalloons();
stage_[current_stage_].current_power = 0; // Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos lo globos stage_[current_stage_].current_power = 0;
menace_current_ = 255; // Sube el nivel de amenaza para que no cree mas globos menace_current_ = 255;
for (auto *player : players_) { // Añade un millon de puntos a los jugadores que queden vivos for (auto *player : players_) {
if (player->isAlive()) { if (player->isAlive()) {
player->addScore(1000000); player->addScore(1000000);
} }
@@ -1480,45 +1487,52 @@ void Game::updateStage() {
} }
Audio::get()->playSound(stage_change_sound_); Audio::get()->playSound(stage_change_sound_);
stage_bitmap_counter_ = 0; stage_bitmap_counter_ = 0;
stage_bitmap_counter_s_ = 0.0F;
enemy_speed_ = default_enemy_speed_; enemy_speed_ = default_enemy_speed_;
setBalloonSpeed(enemy_speed_); setBalloonSpeed(enemy_speed_);
effect_.flash = true; effect_.flash = true;
effect_.shake = true; effect_.shake = true;
} }
// Incrementa el contador del bitmap que aparece mostrando el cambio de fase if (stage_bitmap_counter_s_ < (STAGE_COUNTER / 60.0F)) {
if (stage_bitmap_counter_ < STAGE_COUNTER) { stage_bitmap_counter_s_ += dt_s;
stage_bitmap_counter_++;
} }
stage_bitmap_counter_ = std::min<int>(STAGE_COUNTER, static_cast<int>(stage_bitmap_counter_s_ * 60.0F));
// Si el juego se ha completado, el bitmap se detiene en el centro de la pantalla
if (game_completed_) { if (game_completed_) {
stage_bitmap_counter_ = std::min<int>(stage_bitmap_counter_, 100); stage_bitmap_counter_ = std::min<int>(stage_bitmap_counter_, 100);
} }
} }
// Actualiza el estado de muerte // Actualiza el estado de muerte. Detecta el creuament dels llindars 250/200/180/120/60
void Game::updateDeath() { // (en frames) per a reproduir els bubbles als mateixos moments.
// Comprueba si todos los jugadores estan muertos void Game::updateDeath(float dt_s) {
bool all_dead = true; bool all_dead = true;
for (const auto *player : players_) { for (const auto *player : players_) {
all_dead &= (!player->isAlive()); all_dead &= (!player->isAlive());
} }
if (all_dead) { if (!all_dead) { return; }
if (death_counter_ > 0) {
death_counter_--;
if ((death_counter_ == 250) || (death_counter_ == 200) || (death_counter_ == 180) || (death_counter_ == 120) || (death_counter_ == 60)) { if (death_counter_s_ <= 0.0F) {
// Hace sonar aleatoriamente uno de los 4 sonidos de burbujas section_->subsection = SUBSECTION_GAME_GAMEOVER;
if (!demo_.enabled) { return;
const Uint8 INDEX = rand() % 4; }
Ja::Sound *sound[4] = {bubble1_sound_, bubble2_sound_, bubble3_sound_, bubble4_sound_};
Audio::get()->playSound(sound[INDEX]); const float PREV_S = death_counter_s_;
} death_counter_s_ = std::max(0.0F, death_counter_s_ - dt_s);
} death_counter_ = static_cast<Uint16>(death_counter_s_ * 60.0F);
} else {
section_->subsection = SUBSECTION_GAME_GAMEOVER; auto crossed = [&](float threshold_frames) {
const float TS = threshold_frames / 60.0F;
return (PREV_S > TS) && (death_counter_s_ <= TS);
};
if (crossed(250.0F) || crossed(200.0F) || crossed(180.0F) || crossed(120.0F) || crossed(60.0F)) {
if (!demo_.enabled) {
const Uint8 INDEX = rand() % 4;
Ja::Sound *sound[4] = {bubble1_sound_, bubble2_sound_, bubble3_sound_, bubble4_sound_};
Audio::get()->playSound(sound[INDEX]);
} }
} }
} }
@@ -1549,9 +1563,9 @@ void Game::renderDeathFade(int counter) { // Counter debe ir de 0 a 150
} }
// Actualiza los globos // Actualiza los globos
void Game::updateBalloons() { void Game::updateBalloons(float dt_s) {
for (auto *balloon : balloons_) { for (auto *balloon : balloons_) {
balloon->update(); balloon->update(dt_s);
} }
} }
@@ -1662,7 +1676,7 @@ void Game::popBalloon(Balloon *balloon) {
const int INDEX = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, Balloon::VELX_NEGATIVE, enemy_speed_, 0); const int INDEX = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, Balloon::VELX_NEGATIVE, enemy_speed_, 0);
balloons_[INDEX]->allignTo(balloon->getPosX() + (balloon->getWidth() / 2)); balloons_[INDEX]->allignTo(balloon->getPosX() + (balloon->getWidth() / 2));
if (balloons_[INDEX]->getClass() == Balloon::BALLOON_CLASS) { if (balloons_[INDEX]->getClass() == Balloon::BALLOON_CLASS) {
balloons_[INDEX]->setVelY(-2.50F); balloons_[INDEX]->setVelY(-150.0F); // -2.5 px/frame ⇒ -150 px/s
} else { } else {
balloons_[INDEX]->setVelY(Balloon::VELX_NEGATIVE); balloons_[INDEX]->setVelY(Balloon::VELX_NEGATIVE);
} }
@@ -1670,7 +1684,7 @@ void Game::popBalloon(Balloon *balloon) {
const int INDEX2 = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, Balloon::VELX_POSITIVE, enemy_speed_, 0); const int INDEX2 = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, Balloon::VELX_POSITIVE, enemy_speed_, 0);
balloons_[INDEX2]->allignTo(balloon->getPosX() + (balloon->getWidth() / 2)); balloons_[INDEX2]->allignTo(balloon->getPosX() + (balloon->getWidth() / 2));
if (balloons_[INDEX2]->getClass() == Balloon::BALLOON_CLASS) { if (balloons_[INDEX2]->getClass() == Balloon::BALLOON_CLASS) {
balloons_[INDEX2]->setVelY(-2.50F); balloons_[INDEX2]->setVelY(-150.0F);
} else { } else {
balloons_[INDEX2]->setVelY(Balloon::VELX_NEGATIVE); balloons_[INDEX2]->setVelY(Balloon::VELX_NEGATIVE);
} }
@@ -1904,10 +1918,10 @@ void Game::resolveBulletBalloonHit(Bullet *bullet, Balloon *balloon) {
} }
// Mueve las balas activas // Mueve las balas activas
void Game::moveBullets() { void Game::moveBullets(float dt_s) {
for (auto *bullet : bullets_) { for (auto *bullet : bullets_) {
if (bullet->isEnabled()) { if (bullet->isEnabled()) {
if (bullet->move() == Bullet::MoveResult::OUT) { if (bullet->move(dt_s) == Bullet::MoveResult::OUT) {
players_[bullet->getOwner()]->decScoreMultiplier(); players_[bullet->getOwner()]->decScoreMultiplier();
} }
} }
@@ -1942,10 +1956,10 @@ void Game::freeBullets() {
} }
// Actualiza los items // Actualiza los items
void Game::updateItems() { void Game::updateItems(float dt_s) {
for (auto *item : items_) { for (auto *item : items_) {
if (item->isEnabled()) { if (item->isEnabled()) {
item->update(); item->update(dt_s);
if (item->isOnFloor()) { if (item->isOnFloor()) {
Audio::get()->playSound(coffee_machine_sound_); Audio::get()->playSound(coffee_machine_sound_);
effect_.shake = true; effect_.shake = true;
@@ -2067,14 +2081,26 @@ void Game::renderFlashEffect() {
} }
} }
// Actualiza el efecto de agitar la pantalla // Actualiza el efecto de agitar la pantalla. Decrementa `shake_counter` a
void Game::updateShakeEffect() { // cadència fixa de 60Hz amb un acumulador de fase (independent del framerate)
if (effect_.shake) { // — el render de `updateBackground` segueix llegint la paritat del counter
// per fer vibrar els edificis.
void Game::updateShakeEffect(float dt_s) {
if (!effect_.shake) {
shake_phase_s_ = 0.0F;
return;
}
constexpr float STEP_S = 1.0F / 60.0F;
shake_phase_s_ += dt_s;
while (shake_phase_s_ >= STEP_S) {
shake_phase_s_ -= STEP_S;
if (effect_.shake_counter > 0) { if (effect_.shake_counter > 0) {
effect_.shake_counter--; effect_.shake_counter--;
} else { } else {
effect_.shake = false; effect_.shake = false;
effect_.shake_counter = SHAKE_COUNTER; effect_.shake_counter = SHAKE_COUNTER;
shake_phase_s_ = 0.0F;
break;
} }
} }
} }
@@ -2088,10 +2114,12 @@ void Game::throwCoffee(int x, int y) {
ss->setPosY(y - 8); ss->setPosY(y - 8);
ss->setWidth(16); ss->setWidth(16);
ss->setHeight(16); ss->setHeight(16);
ss->setVelX(-1.0F + ((rand() % 5) * 0.5F)); // Conversió a px/s i px/s² (era -1..1 px/frame i 0.2 px/frame²)
ss->setVelY(-4.0F); const float VX_PX_PER_S = (-1.0F + (static_cast<float>(rand() % 5) * 0.5F)) * 60.0F;
ss->setVelX(VX_PX_PER_S);
ss->setVelY(-240.0F);
ss->setAccelX(0.0F); ss->setAccelX(0.0F);
ss->setAccelY(0.2F); ss->setAccelY(720.0F);
ss->setDestX(x + (ss->getVelX() * 50)); ss->setDestX(x + (ss->getVelX() * 50));
ss->setDestY(GAMECANVAS_HEIGHT + 1); ss->setDestY(GAMECANVAS_HEIGHT + 1);
ss->setEnabled(true); ss->setEnabled(true);
@@ -2103,9 +2131,9 @@ void Game::throwCoffee(int x, int y) {
} }
// Actualiza los SmartSprites // Actualiza los SmartSprites
void Game::updateSmartSprites() { void Game::updateSmartSprites(float dt_s) {
for (auto *ss : smart_sprites_) { for (auto *ss : smart_sprites_) {
ss->update(); ss->update(dt_s);
} }
} }
@@ -2196,114 +2224,87 @@ auto Game::isTimeStopped() const -> bool {
// Establece el valor de la variable // Establece el valor de la variable
void Game::setTimeStoppedCounter(Uint16 value) { void Game::setTimeStoppedCounter(Uint16 value) {
time_stopped_counter_ = value; time_stopped_counter_ = value;
time_stopped_counter_s_ = static_cast<float>(value) / 60.0F;
} }
// Incrementa el valor de la variable // Incrementa el valor de la variable
void Game::incTimeStoppedCounter(Uint16 value) { void Game::incTimeStoppedCounter(Uint16 value) {
time_stopped_counter_ += value; time_stopped_counter_ += value;
time_stopped_counter_s_ += static_cast<float>(value) / 60.0F;
} }
// Actualiza y comprueba el valor de la variable // Actualiza y comprueba el valor de la variable
void Game::updateTimeStoppedCounter() { void Game::updateTimeStoppedCounter(float dt_s) {
if (isTimeStopped()) { if (!isTimeStopped()) { return; }
if (time_stopped_counter_ > 0) { if (time_stopped_counter_s_ > 0.0F) {
time_stopped_counter_--; time_stopped_counter_s_ = std::max(0.0F, time_stopped_counter_s_ - dt_s);
stopAllBalloons(TIME_STOPPED_COUNTER); time_stopped_counter_ = static_cast<Uint16>(time_stopped_counter_s_ * 60.0F);
} else { stopAllBalloons(TIME_STOPPED_COUNTER);
disableTimeStopItem(); } else {
} disableTimeStopItem();
} }
} }
// Actualiza la variable enemyDeployCounter // Actualiza la variable enemyDeployCounter. Decrementa a 60Hz fixe amb
void Game::updateEnemyDeployCounter() { // acumulador de fase — és un comptador discret consultat per
if (enemy_deploy_counter_ > 0) { // `canPowerBallBeCreated()` i altres.
void Game::updateEnemyDeployCounter(float dt_s) {
if (enemy_deploy_counter_ <= 0) { return; }
constexpr float STEP_S = 1.0F / 60.0F;
enemy_deploy_phase_s_ += dt_s;
while (enemy_deploy_phase_s_ >= STEP_S && enemy_deploy_counter_ > 0) {
enemy_deploy_phase_s_ -= STEP_S;
enemy_deploy_counter_--; enemy_deploy_counter_--;
} }
} }
// Actualiza el juego // Actualiza el juego. La cadència la dicta dt_s, propagat des de iterate()
void Game::update() { // via DeltaTime::tick(). El comptador global `counter_` queda derivat de
// Actualiza el audio // `elapsed_s_*60` perquè els lectors existents (render de l'herba, paths del
// get_ready, etc.) segueixin valuant.
void Game::update(float dt_s) {
Audio::update(); Audio::update();
// Actualiza los efectos basados en tiempo real (no en el throttle del juego)
updateDeathShake(); updateDeathShake();
updateDeathSequence(); updateDeathSequence();
// Durante la secuencia de muerte, congela el resto del juego
if (death_sequence_.phase == DeathPhase::SHAKING || death_sequence_.phase == DeathPhase::WAITING) { if (death_sequence_.phase == DeathPhase::SHAKING || death_sequence_.phase == DeathPhase::WAITING) {
return; return;
} }
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego // Acumulador i derivació del comptador legacy
if (SDL_GetTicks() - ticks_ > ticks_speed_) { elapsed_s_ += dt_s;
// Actualiza el contador de ticks counter_ = static_cast<Uint32>(elapsed_s_ * 60.0F);
ticks_ = SDL_GetTicks();
// Actualiza el contador de juego checkGameInput();
counter_++; updatePlayers(dt_s);
updateBackground(dt_s);
updateBalloons(dt_s);
moveBullets(dt_s);
updateItems(dt_s);
updateStage(dt_s);
updateDeath(dt_s);
updateSmartSprites(dt_s);
updateTimeStoppedCounter(dt_s);
updateEnemyDeployCounter(dt_s);
updateShakeEffect(dt_s);
updateHelper(dt_s);
checkBulletBalloonCollision();
updateMenace();
updateBalloonSpeed();
updateGameCompleted(dt_s);
// Comprueba el teclado/mando freeBullets();
checkGameInput(); freeBalloons();
freeItems();
// Actualiza las variables del jugador freeSmartSprites();
updatePlayers();
// Actualiza el fondo
updateBackground();
// Mueve los globos
updateBalloons();
// Mueve las balas
moveBullets();
// Actualiza los items
updateItems();
// Actualiza el valor de currentStage
updateStage();
// Actualiza el estado de muerte
updateDeath();
// Actualiza los SmartSprites
updateSmartSprites();
// Actualiza los contadores de estado y efectos
updateTimeStoppedCounter();
updateEnemyDeployCounter();
updateShakeEffect();
// Actualiza el ayudante
updateHelper();
// Comprueba las colisiones entre globos y balas
checkBulletBalloonCollision();
// Comprueba el nivel de amenaza para ver si se han de crear nuevos enemigos
updateMenace();
// Actualiza la velocidad de los enemigos
updateBalloonSpeed();
// Actualiza el tramo final de juego, una vez completado
updateGameCompleted();
// Vacia los vectores
freeBullets();
freeBalloons();
freeItems();
freeSmartSprites();
}
} }
// Actualiza el fondo // Actualiza el fondo. Velocitats dels núvols expressades com a px/s.
void Game::updateBackground() { void Game::updateBackground(float dt_s) {
if (!game_completed_) { // Si el juego no esta completo, la velocidad de las nubes es igual a los globos explotados if (!game_completed_) {
clouds_speed_ = balloons_popped_; clouds_speed_ = balloons_popped_;
} else { // Si el juego está completado, se reduce la velocidad de las nubes } else {
if (clouds_speed_ > 400) { if (clouds_speed_ > 400) {
clouds_speed_ -= 25; clouds_speed_ -= 25;
} else { } else {
@@ -2311,42 +2312,28 @@ void Game::updateBackground() {
} }
} }
// Calcula la velocidad en función de los globos explotados y el total de globos a explotar para acabar el juego // Velocitat per frame (mateixa fórmula); en time-based la passem com a px/s.
const float SPEED = (-0.2F) + (-3.00F * ((float)clouds_speed_ / (float)total_power_to_complete_game_)); const float SPEED_PX_PER_FRAME = (-0.2F) + (-3.00F * ((float)clouds_speed_ / (float)total_power_to_complete_game_));
const float SPEED_PX_PER_S = SPEED_PX_PER_FRAME * 60.0F;
// Aplica la velocidad calculada a las nubes clouds1_a_->setVelX(SPEED_PX_PER_S);
clouds1_a_->setVelX(SPEED); clouds1_b_->setVelX(SPEED_PX_PER_S);
clouds1_b_->setVelX(SPEED); clouds2_a_->setVelX(SPEED_PX_PER_S / 2.0F);
clouds2_a_->setVelX(SPEED / 2); clouds2_b_->setVelX(SPEED_PX_PER_S / 2.0F);
clouds2_b_->setVelX(SPEED / 2);
// Mueve las nubes clouds1_a_->move(dt_s);
clouds1_a_->move(); clouds1_b_->move(dt_s);
clouds1_b_->move(); clouds2_a_->move(dt_s);
clouds2_a_->move(); clouds2_b_->move(dt_s);
clouds2_b_->move();
// Calcula el offset de las nubes if (clouds1_a_->getPosX() < -clouds1_a_->getWidth()) { clouds1_a_->setPosX(clouds1_a_->getWidth()); }
if (clouds1_a_->getPosX() < -clouds1_a_->getWidth()) { if (clouds1_b_->getPosX() < -clouds1_b_->getWidth()) { clouds1_b_->setPosX(clouds1_b_->getWidth()); }
clouds1_a_->setPosX(clouds1_a_->getWidth()); if (clouds2_a_->getPosX() < -clouds2_a_->getWidth()) { clouds2_a_->setPosX(clouds2_a_->getWidth()); }
} if (clouds2_b_->getPosX() < -clouds2_b_->getWidth()) { clouds2_b_->setPosX(clouds2_b_->getWidth()); }
if (clouds1_b_->getPosX() < -clouds1_b_->getWidth()) { // Herba: `counter_` derivat de `elapsed_s_*60` ja oscil·la a 60Hz.
clouds1_b_->setPosX(clouds1_b_->getWidth());
}
if (clouds2_a_->getPosX() < -clouds2_a_->getWidth()) {
clouds2_a_->setPosX(clouds2_a_->getWidth());
}
if (clouds2_b_->getPosX() < -clouds2_b_->getWidth()) {
clouds2_b_->setPosX(clouds2_b_->getWidth());
}
// Calcula el frame de la hierba
grass_sprite_->setSpriteClip(0, (6 * (counter_ / 20 % 2)), 256, 6); grass_sprite_->setSpriteClip(0, (6 * (counter_ / 20 % 2)), 256, 6);
// Mueve los edificios en funcion de si está activo el efecto de agitarlos
if (death_shake_.active) { if (death_shake_.active) {
const int V[] = {-1, 1, -1, 1, -1, 1, -1, 0}; const int V[] = {-1, 1, -1, 1, -1, 1, -1, 0};
buildings_sprite_->setPosX(V[death_shake_.step]); buildings_sprite_->setPosX(V[death_shake_.step]);
@@ -2679,55 +2666,62 @@ auto Game::isDeathShaking() const -> bool {
// Ejecuta un frame del juego // Ejecuta un frame del juego
void Game::iterate() { void Game::iterate() {
// En modo demo, no hay pausa ni game over // Consum del temps real des de l'última iteració. Sempre s'ha de cridar
if (demo_.enabled) { // perquè el rellotge no acumuli a través de transicions/sub-estats.
if (section_->subsection == SUBSECTION_GAME_PAUSE || section_->subsection == SUBSECTION_GAME_GAMEOVER) { const float DELTA_TIME_S = DeltaTime::tick();
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_INSTRUCTIONS; // En modo demo, ni pause ni game over: torna immediatament al títol
return; if (demo_.enabled && (section_->subsection == SUBSECTION_GAME_PAUSE || section_->subsection == SUBSECTION_GAME_GAMEOVER)) {
} section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_INSTRUCTIONS;
return;
} }
// Sección juego en pausa switch (section_->subsection) {
if (section_->subsection == SUBSECTION_GAME_PAUSE) { case SUBSECTION_GAME_PAUSE:
if (!pause_initialized_) { iteratePaused(DELTA_TIME_S);
enterPausedGame(); break;
} case SUBSECTION_GAME_GAMEOVER:
updatePausedGame(); iterateGameOver(DELTA_TIME_S);
renderPausedGame(); break;
case SUBSECTION_GAME_PLAY_1P:
case SUBSECTION_GAME_PLAY_2P:
iteratePlaying(DELTA_TIME_S);
break;
default:
break;
}
}
// Rama de iterate(): pause
void Game::iteratePaused(float dt_s) {
if (!pause_initialized_) { enterPausedGame(); }
updatePausedGame(dt_s);
renderPausedGame();
}
// Rama de iterate(): game over
void Game::iterateGameOver(float dt_s) {
if (!game_over_initialized_) { enterGameOverScreen(); }
updateGameOverScreen(dt_s);
renderGameOverScreen();
}
// Rama de iterate(): joc actiu
void Game::iteratePlaying(float dt_s) {
// Si veníem de Pause/GameOver, el dt acumulat seria enorme; descarta'l.
if (pause_initialized_ || game_over_initialized_) {
DeltaTime::reset();
}
pause_initialized_ = false;
game_over_initialized_ = false;
if (Audio::getRealMusicState() == Audio::MusicState::STOPPED && !game_completed_ && !demo_.enabled && players_[0]->isAlive()) {
Audio::get()->playMusic(game_music_);
} }
// Sección Game Over update(dt_s);
else if (section_->subsection == SUBSECTION_GAME_GAMEOVER) { render();
if (!game_over_initialized_) {
enterGameOverScreen();
}
updateGameOverScreen();
renderGameOverScreen();
}
// Sección juego jugando
else if ((section_->subsection == SUBSECTION_GAME_PLAY_1P) || (section_->subsection == SUBSECTION_GAME_PLAY_2P)) {
// Resetea los flags de inicialización de sub-estados
pause_initialized_ = false;
game_over_initialized_ = false;
// Si la música no está sonando
if ((Audio::getRealMusicState() == Audio::MusicState::STOPPED) || (Audio::getRealMusicState() == Audio::MusicState::STOPPED)) {
// Reproduce la música (nunca en modo demo: deja sonar la del título)
if (!game_completed_ && !demo_.enabled) {
if (players_[0]->isAlive()) {
Audio::get()->playMusic(game_music_);
}
}
}
// Actualiza la lógica del juego
update();
// Dibuja los objetos
render();
}
} }
// Indica si el juego ha terminado // Indica si el juego ha terminado
@@ -2765,54 +2759,50 @@ void Game::run() {
} }
} }
// Actualiza las variables del menu de pausa del juego // Actualiza el menu de pausa
void Game::updatePausedGame() { void Game::updatePausedGame(float dt_s) {
if (SDL_GetTicks() - ticks_ <= ticks_speed_) {
return;
}
ticks_ = SDL_GetTicks();
// Atalls globals (zoom finestra, fullscreen, shaders, presets, ...)
GlobalInputs::handle(); GlobalInputs::handle();
if (leaving_pause_menu_) { if (leaving_pause_menu_) {
updateLeavingPauseMenu(); updateLeavingPauseMenu(dt_s);
} else { } else {
updatePauseMenuUI(); updatePauseMenuUI(dt_s);
} }
} }
// Rama de updatePausedGame: cuenta atrás de salida y vuelta al juego // Rama de updatePausedGame: cuenta atrás de salida y vuelta al juego.
void Game::updateLeavingPauseMenu() { // Decrementa pause_counter_ a 60Hz exactes amb un acumulador de fase per a
if (pause_counter_ > 0) { // El contador está descendiendo // mantenir els sons de rellotge als llindars originals (90, 60, 30 frames =
const bool A = pause_counter_ == 90; // 1.5s, 1.0s, 0.5s restants).
const bool B = pause_counter_ == 60; void Game::updateLeavingPauseMenu(float dt_s) {
const bool C = pause_counter_ == 30; if (pause_counter_ <= 0) {
if (A || B || C) { section_->name = SECTION_PROG_GAME;
Audio::get()->playSound(clock_sound_); section_->subsection = num_players_ == 1 ? SUBSECTION_GAME_PLAY_1P : SUBSECTION_GAME_PLAY_2P;
if (Audio::getRealMusicState() == Audio::MusicState::PAUSED) {
Audio::get()->resumeMusic();
} }
pause_counter_--;
return; return;
} }
// Ha finalizado el contador constexpr float STEP_S = 1.0F / 60.0F;
section_->name = SECTION_PROG_GAME; pause_counter_phase_s_ += dt_s;
section_->subsection = num_players_ == 1 ? SUBSECTION_GAME_PLAY_1P : SUBSECTION_GAME_PLAY_2P; while (pause_counter_phase_s_ >= STEP_S && pause_counter_ > 0) {
pause_counter_phase_s_ -= STEP_S;
if (Audio::getRealMusicState() == Audio::MusicState::PAUSED) { if (pause_counter_ == 90 || pause_counter_ == 60 || pause_counter_ == 30) {
Audio::get()->resumeMusic(); Audio::get()->playSound(clock_sound_);
}
pause_counter_--;
} }
} }
// Rama de updatePausedGame: lógica del menú de pausa // Lógica del menú de pausa (time-based)
void Game::updatePauseMenuUI() { void Game::updatePauseMenuUI(float dt_s) {
pause_menu_->update(); pause_menu_->update();
pause_menu_->checkInput(); pause_menu_->checkInput();
// F12 (Action::PAUSE) també tanca el menú de pausa — mateix comportament
// que seleccionar "Continue" / cancel·lar amb BACKSPACE.
if (Input::get()->checkInput(Input::Action::PAUSE, Input::Repeat::OFF)) { if (Input::get()->checkInput(Input::Action::PAUSE, Input::Repeat::OFF)) {
leaving_pause_menu_ = true; leaving_pause_menu_ = true;
pause_counter_phase_s_ = 0.0F;
if (!Options::gameplay.pause_countdown) { if (!Options::gameplay.pause_countdown) {
pause_counter_ = 0; pause_counter_ = 0;
} }
@@ -2822,21 +2812,20 @@ void Game::updatePauseMenuUI() {
switch (pause_menu_->getItemSelected()) { switch (pause_menu_->getItemSelected()) {
case 1: case 1:
leaving_pause_menu_ = true; leaving_pause_menu_ = true;
pause_counter_phase_s_ = 0.0F;
if (!Options::gameplay.pause_countdown) { if (!Options::gameplay.pause_countdown) {
pause_counter_ = 0; // salta el compte enrere de 3 segons pause_counter_ = 0;
} }
break; break;
case 2: case 2:
fade_->setFadeType(Fade::Type::CENTER); fade_->setFadeType(Fade::Type::CENTER);
fade_->activateFade(); fade_->activateFade();
break; break;
default: default:
break; break;
} }
fade_->update(); fade_->update(dt_s);
if (fade_->hasEnded()) { if (fade_->hasEnded()) {
section_->name = SECTION_PROG_TITLE; section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_1; section_->subsection = SUBSECTION_TITLE_1;
@@ -2904,60 +2893,42 @@ void Game::enterPausedGame() {
} }
// Actualiza los elementos de la pantalla de game over // Actualiza los elementos de la pantalla de game over
void Game::updateGameOverScreen() { void Game::updateGameOverScreen(float dt_s) {
// Calcula la lógica de los objetos GlobalInputs::handle();
if (SDL_GetTicks() - ticks_ > ticks_speed_) {
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
// Atalls globals (zoom finestra, fullscreen, shaders, presets, ...) game_over_menu_->update();
GlobalInputs::handle(); fade_->update(dt_s);
// Actualiza la lógica del menu if (fade_->hasEnded()) {
game_over_menu_->update(); switch (game_over_post_fade_) {
case 0: // YES
// Actualiza el fade section_->name = SECTION_PROG_GAME;
fade_->update(); deleteAllVectorObjects();
init();
// Si ha terminado el fade, actua segun se haya operado section_->subsection = num_players_ == 1 ? SUBSECTION_GAME_PLAY_1P : SUBSECTION_GAME_PLAY_2P;
if (fade_->hasEnded()) { break;
switch (game_over_post_fade_) { case 1: // NO
case 0: // YES section_->name = SECTION_PROG_TITLE;
section_->name = SECTION_PROG_GAME; section_->subsection = SUBSECTION_TITLE_1;
deleteAllVectorObjects(); break;
init(); default:
section_->subsection = num_players_ == 1 ? SUBSECTION_GAME_PLAY_1P : SUBSECTION_GAME_PLAY_2P; break;
break;
case 1: // NO
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_1;
break;
default:
break;
}
} }
}
// Comprueba las entradas para el menu solo si no esta el juego completo if (!game_completed_) {
if (!game_completed_) { game_over_menu_->checkInput();
game_over_menu_->checkInput(); switch (game_over_menu_->getItemSelected()) {
case 0: // YES
// Comprueba si se ha seleccionado algún item del menú game_over_post_fade_ = 0;
switch (game_over_menu_->getItemSelected()) { fade_->activateFade();
case 0: // YES break;
game_over_post_fade_ = 0; case 1: // NO
fade_->activateFade(); game_over_post_fade_ = 1;
break; fade_->activateFade();
break;
case 1: // NO default:
game_over_post_fade_ = 1; break;
fade_->activateFade();
break;
default:
break;
}
} }
} }
} }
@@ -3099,23 +3070,24 @@ void Game::initPaths() {
} }
// Actualiza el tramo final de juego, una vez completado // Actualiza el tramo final de juego, una vez completado
void Game::updateGameCompleted() { void Game::updateGameCompleted(float dt_s) {
if (game_completed_) { if (!game_completed_) { return; }
game_completed_counter_++;
}
if (game_completed_counter_ == GAME_COMPLETED_END) { const int PREV = game_completed_counter_;
game_completed_counter_s_ += dt_s;
game_completed_counter_ = static_cast<int>(game_completed_counter_s_ * 60.0F);
if (PREV < GAME_COMPLETED_END && game_completed_counter_ >= GAME_COMPLETED_END) {
section_->subsection = SUBSECTION_GAME_GAMEOVER; section_->subsection = SUBSECTION_GAME_GAMEOVER;
} }
} }
// Actualiza las variables de ayuda // Actualiza las variables de ayuda. De moment cap timer real dins helper_;
void Game::updateHelper() { // el dt_s queda reservat per a futurs comptadors d'helper.
// Solo ofrece ayuda cuando la amenaza es elevada void Game::updateHelper([[maybe_unused]] float dt_s) {
if (menace_current_ > 15) { if (menace_current_ > 15) {
for (const auto *player : players_) { for (const auto *player : players_) {
helper_.need_coffee = player->getCoffees() == 0; helper_.need_coffee = player->getCoffees() == 0;
helper_.need_coffee_machine = !player->isPowerUp(); helper_.need_coffee_machine = !player->isPowerUp();
} }
} else { } else {
+53 -40
View File
@@ -38,6 +38,11 @@ class Game {
void handleEvent(const SDL_Event *event); // Procesa un evento void handleEvent(const SDL_Event *event); // Procesa un evento
private: private:
// Branques de iterate() — separades per a reduir la complexitat cognitiva
void iteratePaused(float dt_s);
void iterateGameOver(float dt_s);
void iteratePlaying(float dt_s);
// Cantidad de elementos a escribir en los ficheros de datos // Cantidad de elementos a escribir en los ficheros de datos
static constexpr int TOTAL_SCORE_DATA = 3; static constexpr int TOTAL_SCORE_DATA = 3;
static constexpr int TOTAL_DEMO_DATA = 2000; static constexpr int TOTAL_DEMO_DATA = 2000;
@@ -141,10 +146,10 @@ class Game {
DemoKeys data_file[TOTAL_DEMO_DATA]; // Datos del fichero con los movimientos para la demo DemoKeys data_file[TOTAL_DEMO_DATA]; // Datos del fichero con los movimientos para la demo
}; };
void update(); // Actualiza el juego void update(float dt_s); // Actualiza el juego
void render(); // Dibuja el juego void render(); // Dibuja el juego
void init(); // Inicializa las variables necesarias para la sección 'Game' void init(); // Inicializa las variables necesarias para la sección 'Game'
void loadMedia(); // Carga los recursos necesarios para la sección 'Game' void loadMedia(); // Carga los recursos necesarios para la sección 'Game'
auto loadScoreFile() -> bool; // Carga el fichero de puntos auto loadScoreFile() -> bool; // Carga el fichero de puntos
auto loadDemoFile() -> bool; // Carga el fichero de datos para la demo auto loadDemoFile() -> bool; // Carga el fichero de datos para la demo
@@ -167,14 +172,14 @@ class Game {
static auto updateScoreText(Uint32 num) -> std::string; // Transforma un valor numérico en una cadena de 6 cifras static auto updateScoreText(Uint32 num) -> std::string; // Transforma un valor numérico en una cadena de 6 cifras
void renderScoreBoard(); // Pinta el marcador en pantalla usando un objeto texto void renderScoreBoard(); // Pinta el marcador en pantalla usando un objeto texto
void updatePlayers(); // Actualiza las variables del jugador void updatePlayers(float dt_s); // Actualiza las variables del jugador
void renderPlayers(); // Dibuja a los jugadores void renderPlayers(); // Dibuja a los jugadores
void updateStage(); // Actualiza las variables de la fase void updateStage(float dt_s); // Actualiza las variables de la fase
void updateDeath(); // Actualiza el estado de muerte void updateDeath(float dt_s); // Actualiza el estado de muerte
void renderDeathFade(int counter); // Renderiza el fade final cuando se acaba la partida void renderDeathFade(int counter); // Renderiza el fade final cuando se acaba la partida
void updateBalloons(); // Actualiza los globos void updateBalloons(float dt_s); // Actualiza los globos
void renderBalloons(); // Pinta en pantalla todos los globos activos void renderBalloons(); // Pinta en pantalla todos los globos activos
auto createBalloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 creationtimer) -> Uint8; // Crea un globo nuevo en el vector de globos auto createBalloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 creationtimer) -> Uint8; // Crea un globo nuevo en el vector de globos
void createPowerBall(); // Crea una PowerBall void createPowerBall(); // Crea una PowerBall
@@ -193,12 +198,12 @@ class Game {
void checkBulletBalloonCollision(); // Comprueba la colisión entre las balas y los globos void checkBulletBalloonCollision(); // Comprueba la colisión entre las balas y los globos
void resolveBulletBalloonHit(Bullet *bullet, Balloon *balloon); // Resuelve un impacto bala-globo (helper de checkBulletBalloonCollision) void resolveBulletBalloonHit(Bullet *bullet, Balloon *balloon); // Resuelve un impacto bala-globo (helper de checkBulletBalloonCollision)
void moveBullets(); // Mueve las balas activas void moveBullets(float dt_s); // Mueve las balas activas
void renderBullets(); // Pinta las balas activas void renderBullets(); // Pinta las balas activas
void createBullet(int x, int y, Bullet::Kind kind, bool powered_up, int owner); // Crea un objeto bala void createBullet(int x, int y, Bullet::Kind kind, bool powered_up, int owner); // Crea un objeto bala
void freeBullets(); // Vacia el vector de balas void freeBullets(); // Vacia el vector de balas
void updateItems(); // Actualiza los items void updateItems(float dt_s); // Actualiza los items
void renderItems(); // Pinta los items activos void renderItems(); // Pinta los items activos
auto dropItem() -> Item::Id; // Devuelve un item en función del azar auto dropItem() -> Item::Id; // Devuelve un item en función del azar
void createItem(Item::Id kind, float x, float y); // Crea un objeto item void createItem(Item::Id kind, float x, float y); // Crea un objeto item
@@ -207,11 +212,11 @@ class Game {
void createItemScoreSprite(int x, int y, const SmartSprite *sprite); // Crea un objeto SmartSprite void createItemScoreSprite(int x, int y, const SmartSprite *sprite); // Crea un objeto SmartSprite
void freeSmartSprites(); // Vacia el vector de smartsprites void freeSmartSprites(); // Vacia el vector de smartsprites
void renderFlashEffect(); // Dibuja el efecto de flash void renderFlashEffect(); // Dibuja el efecto de flash
void updateShakeEffect(); // Actualiza el efecto de agitar la pantalla void updateShakeEffect(float dt_s); // Actualiza el efecto de agitar la pantalla
void throwCoffee(int x, int y); // Crea un SmartSprite para arrojar el item café al recibir un impacto void throwCoffee(int x, int y); // Crea un SmartSprite para arrojar el item café al recibir un impacto
void updateSmartSprites(); // Actualiza los SmartSprites void updateSmartSprites(float dt_s); // Actualiza los SmartSprites
void renderSmartSprites(); // Pinta los SmartSprites activos void renderSmartSprites(); // Pinta los SmartSprites activos
void killPlayer(Player *player); // Acciones a realizar cuando el jugador muere void killPlayer(Player *player); // Acciones a realizar cuando el jugador muere
void evaluateAndSetMenace(); // Calcula y establece el valor de amenaza en funcion de los globos activos void evaluateAndSetMenace(); // Calcula y establece el valor de amenaza en funcion de los globos activos
@@ -222,11 +227,11 @@ class Game {
void setTimeStoppedCounter(Uint16 value); // Establece el valor de la variable void setTimeStoppedCounter(Uint16 value); // Establece el valor de la variable
void incTimeStoppedCounter(Uint16 value); // Incrementa el valor de la variable void incTimeStoppedCounter(Uint16 value); // Incrementa el valor de la variable
void updateEnemyDeployCounter(); // Actualiza la variable EnemyDeployCounter void updateEnemyDeployCounter(float dt_s); // Actualiza la variable EnemyDeployCounter
void updateTimeStoppedCounter(); // Actualiza y comprueba el valor de la variable void updateTimeStoppedCounter(float dt_s); // Actualiza y comprueba el valor de la variable
void updateMenace(); // Gestiona el nivel de amenaza void updateMenace(); // Gestiona el nivel de amenaza
void updateBackground(); // Actualiza el fondo void updateBackground(float dt_s); // Actualiza el fondo
void renderBackground(); // Dibuja el fondo void renderBackground(); // Dibuja el fondo
void checkGameInput(); // Gestiona la entrada durante el juego void checkGameInput(); // Gestiona la entrada durante el juego
void processDemoInput(); // Helper de checkGameInput void processDemoInput(); // Helper de checkGameInput
@@ -241,21 +246,21 @@ class Game {
[[nodiscard]] auto isDeathShaking() const -> bool; // Indica si el efecto de agitación intensa está activo [[nodiscard]] auto isDeathShaking() const -> bool; // Indica si el efecto de agitación intensa está activo
void updateDeathSequence(); // Actualiza la secuencia de muerte del jugador void updateDeathSequence(); // Actualiza la secuencia de muerte del jugador
void updatePausedGame(); // Actualiza las variables del menu de pausa del juego void updatePausedGame(float dt_s); // Actualiza el menu de pausa
void updateLeavingPauseMenu(); // Helper de updatePausedGame void updateLeavingPauseMenu(float dt_s); // Helper
void updatePauseMenuUI(); // Helper de updatePausedGame void updatePauseMenuUI(float dt_s); // Helper
void renderPausedGame(); // Dibuja el menu de pausa del juego void renderPausedGame(); // Dibuja el menu de pausa del juego
void enterPausedGame(); // Inicializa el estado de pausa del juego void enterPausedGame(); // Inicializa el estado de pausa del juego
void updateGameOverScreen(); // Actualiza los elementos de la pantalla de game over void updateGameOverScreen(float dt_s); // Actualiza game over
void renderGameOverScreen(); // Dibuja los elementos de la pantalla de game over void renderGameOverScreen(); // Dibuja los elementos de la pantalla de game over
void enterGameOverScreen(); // Inicializa el estado de game over void enterGameOverScreen(); // Inicializa el estado de game over
auto canPowerBallBeCreated() -> bool; // Indica si se puede crear una powerball auto canPowerBallBeCreated() -> bool; // Indica si se puede crear una powerball
auto calculateScreenPower() -> int; // Calcula el poder actual de los globos en pantalla auto calculateScreenPower() -> int; // Calcula el poder actual de los globos en pantalla
void initPaths(); // Inicializa las variables que contienen puntos de ruta para mover objetos void initPaths(); // Inicializa las variables que contienen puntos de ruta para mover objetos
void updateGameCompleted(); // Actualiza el tramo final de juego, una vez completado void updateGameCompleted(float dt_s); // Actualiza el tramo final de juego
void updateHelper(); // Actualiza las variables de ayuda void updateHelper(float dt_s); // Actualiza las variables de ayuda
auto allPlayersAreDead() -> bool; // Comprueba si todos los jugadores han muerto auto allPlayersAreDead() -> bool; // Comprueba si todos los jugadores han muerto
void deleteAllVectorObjects(); // Elimina todos los objetos contenidos en vectores void deleteAllVectorObjects(); // Elimina todos los objetos contenidos en vectores
void setHiScore(); // Establece la máxima puntuación desde fichero o desde las puntuaciones online void setHiScore(); // Establece la máxima puntuación desde fichero o desde las puntuaciones online
@@ -337,30 +342,36 @@ class Game {
// Variables // Variables
int num_players_; // Numero de jugadores int num_players_; // Numero de jugadores
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa float elapsed_s_{0.0F}; // Acumulador global de temps de joc
Uint8 ticks_speed_; // Velocidad a la que se repiten los bucles del programa
Uint32 hi_score_; // Puntuación máxima Uint32 hi_score_; // Puntuación máxima
bool hi_score_achieved_; // Indica si se ha superado la puntuación máxima bool hi_score_achieved_; // Indica si se ha superado la puntuación máxima
std::string hi_score_name_; // Nombre del jugador que ostenta la máxima puntuación std::string hi_score_name_; // Nombre del jugador que ostenta la máxima puntuación
Stage stage_[10]; // Variable con los datos de cada pantalla Stage stage_[10]; // Variable con los datos de cada pantalla
Uint8 current_stage_; // Indica la fase actual Uint8 current_stage_; // Indica la fase actual
Uint8 stage_bitmap_counter_; // Contador para el tiempo visible del texto de Stage Uint8 stage_bitmap_counter_; // Contador para el tiempo visible del texto de Stage (frame-based)
float stage_bitmap_counter_s_{0.0F}; // Contador (time-based)
float stage_bitmap_path_[STAGE_COUNTER]; // Vector con los puntos Y por donde se desplaza el texto float stage_bitmap_path_[STAGE_COUNTER]; // Vector con los puntos Y por donde se desplaza el texto
float get_ready_bitmap_path_[STAGE_COUNTER]; // Vector con los puntos X por donde se desplaza el texto float get_ready_bitmap_path_[STAGE_COUNTER]; // Vector con los puntos X por donde se desplaza el texto
Uint16 death_counter_; // Contador para la animación de muerte del jugador Uint16 death_counter_; // Contador para la animación de muerte del jugador (frame-based)
float death_counter_s_{0.0F}; // Contador (time-based)
Uint8 menace_current_; // Nivel de amenaza actual Uint8 menace_current_; // Nivel de amenaza actual
Uint8 menace_threshold_; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el numero de globos Uint8 menace_threshold_; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el numero de globos
bool time_stopped_; // Indica si el tiempo está detenido bool time_stopped_; // Indica si el tiempo está detenido
Uint16 time_stopped_counter_; // Temporizador para llevar la cuenta del tiempo detenido Uint16 time_stopped_counter_; // Temporizador (frame-based)
Uint32 counter_; // Contador para el juego float time_stopped_counter_s_{0.0F}; // Temporizador (time-based)
Uint32 counter_; // Contador para el juego (frame-based, derivat de elapsed_s_*60 en time-based)
Uint32 score_data_file_[TOTAL_SCORE_DATA]; // Datos del fichero de puntos Uint32 score_data_file_[TOTAL_SCORE_DATA]; // Datos del fichero de puntos
SDL_Rect sky_colors_rect_[4]; // Vector con las coordenadas de los 4 colores de cielo SDL_Rect sky_colors_rect_[4]; // Vector con las coordenadas de los 4 colores de cielo
Uint16 balloons_popped_; // Lleva la cuenta de los globos explotados Uint16 balloons_popped_; // Lleva la cuenta de los globos explotados
Uint8 last_enemy_deploy_; // Guarda cual ha sido la última formación desplegada para no repetir; Uint8 last_enemy_deploy_; // Guarda cual ha sido la última formación desplegada para no repetir;
int enemy_deploy_counter_; // Cuando se lanza una formación, se le da un valor y no sale otra hasta que llegue a cero int enemy_deploy_counter_; // Cuando se lanza una formación, se le da un valor y no sale otra hasta que llegue a cero (frame-based)
float enemy_deploy_counter_s_{0.0F}; // Comptador (time-based)
float enemy_speed_; // Velocidad a la que se mueven los enemigos float enemy_speed_; // Velocidad a la que se mueven los enemigos
float default_enemy_speed_; // Velocidad base de los enemigos, sin incrementar float default_enemy_speed_; // Velocidad base de los enemigos, sin incrementar
Effect effect_; // Variable para gestionar los efectos visuales Effect effect_; // Variable para gestionar los efectos visuales
float shake_phase_s_{0.0F}; // Acumulador per decrementar shake_counter a 60Hz (time-based)
float helper_counter_s_{0.0F}; // Acumulador per al comptador helper_.counter (time-based)
float enemy_deploy_phase_s_{0.0F}; // Acumulador per al decrement de enemy_deploy_counter_ (time-based)
DeathShake death_shake_; // Variable para gestionar el efecto de agitación intensa DeathShake death_shake_; // Variable para gestionar el efecto de agitación intensa
DeathSequence death_sequence_; // Variable para gestionar la secuencia de muerte DeathSequence death_sequence_; // Variable para gestionar la secuencia de muerte
Helper helper_; // Variable para gestionar las ayudas Helper helper_; // Variable para gestionar las ayudas
@@ -368,7 +379,8 @@ class Game {
Uint8 power_ball_counter_; // Contador de formaciones enemigas entre la aparicion de una PowerBall y otra Uint8 power_ball_counter_; // Contador de formaciones enemigas entre la aparicion de una PowerBall y otra
bool coffee_machine_enabled_; // Indica si hay una máquina de café en el terreno de juego bool coffee_machine_enabled_; // Indica si hay una máquina de café en el terreno de juego
bool game_completed_; // Indica si se ha completado la partida, llegando al final de la ultima pantalla bool game_completed_; // Indica si se ha completado la partida, llegando al final de la ultima pantalla
int game_completed_counter_; // Contador para el tramo final, cuando se ha completado la partida y ya no aparecen más enemigos int game_completed_counter_; // Contador per al tram final (frame-based)
float game_completed_counter_s_{0.0F}; // Comptador (time-based)
Uint8 difficulty_; // Dificultad del juego Uint8 difficulty_; // Dificultad del juego
float difficulty_score_multiplier_; // Multiplicador de puntos en función de la dificultad float difficulty_score_multiplier_; // Multiplicador de puntos en función de la dificultad
Color difficulty_color_; // Color asociado a la dificultad Color difficulty_color_; // Color asociado a la dificultad
@@ -379,7 +391,8 @@ class Game {
Demo demo_; // Variable con todas las variables relacionadas con el modo demo Demo demo_; // Variable con todas las variables relacionadas con el modo demo
int total_power_to_complete_game_; // La suma del poder necesario para completar todas las fases int total_power_to_complete_game_; // La suma del poder necesario para completar todas las fases
int clouds_speed_{0}; // Velocidad a la que se desplazan las nubes int clouds_speed_{0}; // Velocidad a la que se desplazan las nubes
int pause_counter_; // Contador para salir del menu de pausa y volver al juego int pause_counter_; // Contador per a sortir del menu de pausa (frame-based, frames)
float pause_counter_phase_s_{0.0F}; // Acumulador de fase per decrementar pause_counter_ a 60Hz (time-based)
bool leaving_pause_menu_; // Indica si esta saliendo del menu de pausa para volver al juego bool leaving_pause_menu_; // Indica si esta saliendo del menu de pausa para volver al juego
bool pause_initialized_; // Indica si la pausa ha sido inicializada bool pause_initialized_; // Indica si la pausa ha sido inicializada
bool game_over_initialized_; // Indica si el game over ha sido inicializado bool game_over_initialized_; // Indica si el game over ha sido inicializado
+26
View File
@@ -97,6 +97,31 @@ void Instructions::update() {
} }
} }
// Time-based. counter_ es deriva de elapsed_s_*60 (cadència de referència 60Hz)
// per a no haver de refactoritzar render() — la geometria del scroll, la
// pulsació dels sprites i el blink de manual segueixen llegint counter_.
void Instructions::update(float dt_s) {
Audio::update();
checkInput();
elapsed_s_ += dt_s;
constexpr float FRAMES_PER_S = 60.0F;
if (mode_ == Mode::AUTO) {
counter_ = static_cast<Uint16>(elapsed_s_ * FRAMES_PER_S);
if (elapsed_s_ >= SCENE_DURATION_S) {
finished_ = true;
}
} else {
// counter_ acotat al rang original 0..59999 per no trencar les expressions
// de blink i sprite-cycling que en depenen.
counter_ = static_cast<Uint16>(static_cast<int>(elapsed_s_ * FRAMES_PER_S) % 60000);
if (manual_quit_) {
finished_ = true;
}
}
}
// Pinta en pantalla // Pinta en pantalla
void Instructions::render() { void Instructions::render() {
// Pinta en pantalla // Pinta en pantalla
@@ -252,6 +277,7 @@ void Instructions::start(Mode mode) {
manual_quit_ = false; manual_quit_ = false;
counter_ = 0; counter_ = 0;
ticks_ = 0; ticks_ = 0;
elapsed_s_ = 0.0F;
} }
// Indica si las instrucciones han terminado // Indica si las instrucciones han terminado
+15 -8
View File
@@ -23,11 +23,12 @@ class Instructions {
Instructions(const Instructions &) = delete; Instructions(const Instructions &) = delete;
auto operator=(const Instructions &) -> Instructions & = delete; auto operator=(const Instructions &) -> Instructions & = delete;
void run(Mode mode); // Bucle principal void run(Mode mode); // Bucle principal
void start(Mode mode); // Inicia las instrucciones (sin bucle) void start(Mode mode); // Inicia las instrucciones (sin bucle)
void update(); // Actualiza las variables void update(); // Actualiza las variables (frame-based)
void render(); // Pinta en pantalla void update(float dt_s); // Actualiza las variables (time-based)
void checkEvents(); // Comprueba los eventos void render(); // Pinta en pantalla
void checkEvents(); // Comprueba los eventos
[[nodiscard]] auto hasFinished() const -> bool; // Indica si las instrucciones han terminado [[nodiscard]] auto hasFinished() const -> bool; // Indica si las instrucciones han terminado
[[nodiscard]] auto isQuitRequested() const -> bool; // Indica si se ha solicitado salir de la aplicación [[nodiscard]] auto isQuitRequested() const -> bool; // Indica si se ha solicitado salir de la aplicación
@@ -43,14 +44,20 @@ class Instructions {
Section *section_; // Estado del bucle principal para saber si continua o se sale Section *section_; // Estado del bucle principal para saber si continua o se sale
// Variables // Variables
Uint16 counter_; // Contador Uint16 counter_; // Contador (derivat de elapsed_s_ * 60 en mode time-based)
Uint16 counter_end_; // Valor final para el contador Uint16 counter_end_; // Valor final para el contador
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa (frame-based)
Uint32 ticks_speed_; // Velocidad a la que se repiten los bucles del programa Uint32 ticks_speed_; // Velocidad a la que se repiten los bucles del programa (frame-based)
float elapsed_s_{0.0F}; // Acumulador de temps (time-based)
bool manual_quit_; // Indica si se quiere salir del modo manual bool manual_quit_; // Indica si se quiere salir del modo manual
Mode mode_{Instructions::Mode::AUTO}; // Modo en el que se van a ejecutar las instrucciones Mode mode_{Instructions::Mode::AUTO}; // Modo en el que se van a ejecutar las instrucciones
bool finished_; // Indica si las instrucciones han terminado bool finished_; // Indica si las instrucciones han terminado
bool quit_requested_; // Indica si se ha solicitado salir de la aplicación bool quit_requested_; // Indica si se ha solicitado salir de la aplicación
// Time-based: durada total de la escena en mode AUTO (600 frames a 60Hz).
static constexpr float SCENE_DURATION_S = 10.0F;
// Time-based: temps mínim al mode MANUAL abans de poder sortir (30 frames a 60Hz).
static constexpr float MANUAL_QUIT_DELAY_S = 0.5F;
void checkInput(); // Comprueba las entradas void checkInput(); // Comprueba las entradas
}; };
+64 -43
View File
@@ -12,9 +12,29 @@
#include "core/rendering/smartsprite.h" // for SmartSprite #include "core/rendering/smartsprite.h" // for SmartSprite
#include "core/rendering/writer.h" // for Writer #include "core/rendering/writer.h" // for Writer
#include "core/resources/resource.h" #include "core/resources/resource.h"
#include "core/system/delta_time.hpp"
#include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, GAMECANVAS_FIRST_QU... #include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, GAMECANVAS_FIRST_QU...
#include "utils/utils.h" // for Section, Color #include "utils/utils.h" // for Section, Color
// ===========================================================================
// Time-based. Tots els valors precalculats: velocitats en px/s, acceleracions
// en px/s^2, durades en segons. La cadència real de la versió frame-based era
// empíricament ~60 Hz (el gate `> 15 ms` esperava al següent múltiple del
// refresh del SO, típicament 16.67 ms). Per això la conversió és:
// vel px/tick → vel * 60 = px/s
// acc px/tick² → acc * 3600 = px/s²
// counter frames → counter/60 = segons
// ===========================================================================
namespace {
// Durades comunes (segons). Arrodonides amunt respecte a la conversió
// exacta des de frames per a tenir valors més "bonics" i una mica més
// de respir visual.
constexpr float BITMAP_REMAINING_TIME_S = 0.5F; // de 0.333 (20/60) ⇒ 0.5
constexpr float BITMAP_FALLING_REMAINING_TIME_S = 5.0F; // de 4.167 (250/60) ⇒ 5.0
constexpr float TEXT_REMAINING_TIME_S = 3.0F; // 180/60 ⇒ ja és exacte
} // namespace
// Constructor // Constructor
Intro::Intro(SDL_Renderer *renderer, Section *section) { Intro::Intro(SDL_Renderer *renderer, Section *section) {
// Copia los punteros // Copia los punteros
@@ -30,8 +50,6 @@ Intro::Intro(SDL_Renderer *renderer, Section *section) {
// Inicializa variables // Inicializa variables
section->name = SECTION_PROG_INTRO; section->name = SECTION_PROG_INTRO;
section->subsection = 0; section->subsection = 0;
ticks_ = 0;
ticks_speed_ = 15;
scene_ = 1; scene_ = 1;
// Inicializa los bitmaps de la intro // Inicializa los bitmaps de la intro
@@ -40,62 +58,69 @@ Intro::Intro(SDL_Renderer *renderer, Section *section) {
auto *ss = new SmartSprite(texture_, renderer); auto *ss = new SmartSprite(texture_, renderer);
ss->setWidth(128); ss->setWidth(128);
ss->setHeight(96); ss->setHeight(96);
ss->setEnabledCounter(20); ss->setRemainingTime(BITMAP_REMAINING_TIME_S);
ss->setDestX(GAMECANVAS_CENTER_X - 64); ss->setDestX(GAMECANVAS_CENTER_X - 64);
ss->setDestY(GAMECANVAS_FIRST_QUARTER_Y - 24); ss->setDestY(GAMECANVAS_FIRST_QUARTER_Y - 24);
bitmaps_.push_back(ss); bitmaps_.push_back(ss);
} }
// bitmap 0: entra des de l'esquerra, accelerant cap a la dreta
bitmaps_[0]->setPosX(-128); bitmaps_[0]->setPosX(-128);
bitmaps_[0]->setPosY(GAMECANVAS_FIRST_QUARTER_Y - 24); bitmaps_[0]->setPosY(GAMECANVAS_FIRST_QUARTER_Y - 24);
bitmaps_[0]->setVelX(0.0F); bitmaps_[0]->setVelX(0.0F);
bitmaps_[0]->setVelY(0.0F); bitmaps_[0]->setVelY(0.0F);
bitmaps_[0]->setAccelX(0.6F); bitmaps_[0]->setAccelX(2160.0F); // 0.6 px/tick² ⇒ 0.6 * 3600 px/s²
bitmaps_[0]->setAccelY(0.0F); bitmaps_[0]->setAccelY(0.0F);
bitmaps_[0]->setSpriteClip(0, 0, 128, 96); bitmaps_[0]->setSpriteClip(0, 0, 128, 96);
// bitmap 1: entra des de la dreta amb velocitat negativa i accelera més
bitmaps_[1]->setPosX(GAMECANVAS_WIDTH); bitmaps_[1]->setPosX(GAMECANVAS_WIDTH);
bitmaps_[1]->setPosY(GAMECANVAS_FIRST_QUARTER_Y - 24); bitmaps_[1]->setPosY(GAMECANVAS_FIRST_QUARTER_Y - 24);
bitmaps_[1]->setVelX(-1.0F); bitmaps_[1]->setVelX(-60.0F); // -1 px/tick ⇒ -60 px/s
bitmaps_[1]->setVelY(0.0F); bitmaps_[1]->setVelY(0.0F);
bitmaps_[1]->setAccelX(-0.3F); bitmaps_[1]->setAccelX(-1080.0F); // -0.3 px/tick² ⇒ -1080 px/s²
bitmaps_[1]->setAccelY(0.0F); bitmaps_[1]->setAccelY(0.0F);
bitmaps_[1]->setSpriteClip(128, 0, 128, 96); bitmaps_[1]->setSpriteClip(128, 0, 128, 96);
// bitmap 2: cau des de dalt; queda visible més temps (escena "GRITO")
bitmaps_[2]->setPosX(GAMECANVAS_CENTER_X - 64); bitmaps_[2]->setPosX(GAMECANVAS_CENTER_X - 64);
bitmaps_[2]->setPosY(-96); bitmaps_[2]->setPosY(-96);
bitmaps_[2]->setVelX(0.0F); bitmaps_[2]->setVelX(0.0F);
bitmaps_[2]->setVelY(3.0F); bitmaps_[2]->setVelY(180.0F); // 3 px/tick ⇒ 180 px/s
bitmaps_[2]->setAccelX(0.1F); bitmaps_[2]->setAccelX(360.0F); // 0.1 px/tick² ⇒ 360 px/s²
bitmaps_[2]->setAccelY(0.3F); bitmaps_[2]->setAccelY(1080.0F); // 0.3 px/tick² ⇒ 1080 px/s²
bitmaps_[2]->setSpriteClip(0, 96, 128, 96); bitmaps_[2]->setSpriteClip(0, 96, 128, 96);
bitmaps_[2]->setEnabledCounter(250); bitmaps_[2]->setRemainingTime(BITMAP_FALLING_REMAINING_TIME_S);
// bitmap 3: puja lentament des de baix (reflexió)
bitmaps_[3]->setPosX(GAMECANVAS_CENTER_X - 64); bitmaps_[3]->setPosX(GAMECANVAS_CENTER_X - 64);
bitmaps_[3]->setPosY(GAMECANVAS_HEIGHT); bitmaps_[3]->setPosY(GAMECANVAS_HEIGHT);
bitmaps_[3]->setVelX(0.0F); bitmaps_[3]->setVelX(0.0F);
bitmaps_[3]->setVelY(-0.7F); bitmaps_[3]->setVelY(-42.0F); // -0.7 px/tick ⇒ -42 px/s
bitmaps_[3]->setAccelX(0.0F); bitmaps_[3]->setAccelX(0.0F);
bitmaps_[3]->setAccelY(0.0F); bitmaps_[3]->setAccelY(0.0F);
bitmaps_[3]->setSpriteClip(128, 96, 128, 96); bitmaps_[3]->setSpriteClip(128, 96, 128, 96);
// bitmap 4: cau des de dalt (mateix que bitmap 2, sense temps allargat)
bitmaps_[4]->setPosX(GAMECANVAS_CENTER_X - 64); bitmaps_[4]->setPosX(GAMECANVAS_CENTER_X - 64);
bitmaps_[4]->setPosY(-96); bitmaps_[4]->setPosY(-96);
bitmaps_[4]->setVelX(0.0F); bitmaps_[4]->setVelX(0.0F);
bitmaps_[4]->setVelY(3.0F); bitmaps_[4]->setVelY(180.0F);
bitmaps_[4]->setAccelX(0.1F); bitmaps_[4]->setAccelX(360.0F);
bitmaps_[4]->setAccelY(0.3F); bitmaps_[4]->setAccelY(1080.0F);
bitmaps_[4]->setSpriteClip(0, 192, 128, 96); bitmaps_[4]->setSpriteClip(0, 192, 128, 96);
// bitmap 5: entra des de la dreta lentament
bitmaps_[5]->setPosX(GAMECANVAS_WIDTH); bitmaps_[5]->setPosX(GAMECANVAS_WIDTH);
bitmaps_[5]->setPosY(GAMECANVAS_FIRST_QUARTER_Y - 24); bitmaps_[5]->setPosY(GAMECANVAS_FIRST_QUARTER_Y - 24);
bitmaps_[5]->setVelX(-0.7F); bitmaps_[5]->setVelX(-42.0F); // -0.7 px/tick ⇒ -42 px/s
bitmaps_[5]->setVelY(0.0F); bitmaps_[5]->setVelY(0.0F);
bitmaps_[5]->setAccelX(0.0F); bitmaps_[5]->setAccelX(0.0F);
bitmaps_[5]->setAccelY(0.0F); bitmaps_[5]->setAccelY(0.0F);
bitmaps_[5]->setSpriteClip(128, 192, 128, 96); bitmaps_[5]->setSpriteClip(128, 192, 128, 96);
// Inicializa los textos de la intro // Inicializa los textos de la intro. Time-based: setSecondsPerChar.
// Conversió: frames_per_char / 60 = segons_per_char.
const int TOTAL_TEXTS = 9; const int TOTAL_TEXTS = 9;
for (int i = 0; i < TOTAL_TEXTS; ++i) { for (int i = 0; i < TOTAL_TEXTS; ++i) {
auto *w = new Writer(text_); auto *w = new Writer(text_);
@@ -103,51 +128,53 @@ Intro::Intro(SDL_Renderer *renderer, Section *section) {
w->setPosY(GAMECANVAS_HEIGHT - (BLOCK * 6)); w->setPosY(GAMECANVAS_HEIGHT - (BLOCK * 6));
w->setKerning(-1); w->setKerning(-1);
w->setEnabled(false); w->setEnabled(false);
w->setEnabledCounter(180); w->setRemainingTime(TEXT_REMAINING_TIME_S);
texts_.push_back(w); texts_.push_back(w);
} }
// Un dia qualsevol de l'any 2000 // Un dia qualsevol de l'any 2000
texts_[0]->setCaption(Lang::get()->getText(27)); texts_[0]->setCaption(Lang::get()->getText(27));
texts_[0]->setSpeed(8); texts_[0]->setSecondsPerChar(0.15F); // de 0.1333 (8/60) ⇒ 0.15
// Tot esta tranquil a la UPV // Tot esta tranquil a la UPV
texts_[1]->setCaption(Lang::get()->getText(28)); texts_[1]->setCaption(Lang::get()->getText(28));
texts_[1]->setSpeed(8); texts_[1]->setSecondsPerChar(0.15F);
// Fins que un desaprensiu... // Fins que un desaprensiu...
texts_[2]->setCaption(Lang::get()->getText(29)); texts_[2]->setCaption(Lang::get()->getText(29));
texts_[2]->setSpeed(12); texts_[2]->setSecondsPerChar(0.2F); // 12/60 ⇒ ja és 0.2
// HEY! ME ANE A FERME UN CORTAET... // HEY! ME ANE A FERME UN CORTAET...
texts_[3]->setCaption(Lang::get()->getText(30)); texts_[3]->setCaption(Lang::get()->getText(30));
texts_[3]->setSpeed(8); texts_[3]->setSecondsPerChar(0.15F);
// UAAAAAAAAAAAAA!!! // UAAAAAAAAAAAAA!!!
texts_[4]->setCaption(Lang::get()->getText(31)); texts_[4]->setCaption(Lang::get()->getText(31));
texts_[4]->setSpeed(1); texts_[4]->setSecondsPerChar(0.02F); // de 0.0167 (1/60) ⇒ 0.02
// Espera un moment... // Espera un moment...
texts_[5]->setCaption(Lang::get()->getText(32)); texts_[5]->setCaption(Lang::get()->getText(32));
texts_[5]->setSpeed(16); texts_[5]->setSecondsPerChar(0.3F); // de 0.2667 (16/60) ⇒ 0.3
// Si resulta que no tinc solt! // Si resulta que no tinc solt!
texts_[6]->setCaption(Lang::get()->getText(33)); texts_[6]->setCaption(Lang::get()->getText(33));
texts_[6]->setSpeed(2); texts_[6]->setSecondsPerChar(0.05F); // de 0.0333 (2/60) ⇒ 0.05
// MERDA DE MAQUINA! // MERDA DE MAQUINA!
texts_[7]->setCaption(Lang::get()->getText(34)); texts_[7]->setCaption(Lang::get()->getText(34));
texts_[7]->setSpeed(3); texts_[7]->setSecondsPerChar(0.05F); // 3/60 ⇒ ja és 0.05
// Blop... blop... blop... // Blop... blop... blop...
texts_[8]->setCaption(Lang::get()->getText(35)); texts_[8]->setCaption(Lang::get()->getText(35));
texts_[8]->setSpeed(16); texts_[8]->setSecondsPerChar(0.3F);
for (auto *t : texts_) { for (auto *t : texts_) {
t->center(GAMECANVAS_CENTER_X); t->center(GAMECANVAS_CENTER_X);
} }
Audio::get()->playMusic(music_, 0); Audio::get()->playMusic(music_, 0);
DeltaTime::reset();
} }
// Destructor // Destructor
@@ -332,26 +359,19 @@ void Intro::updateScene6() {
} }
// Actualiza las variables del objeto // Actualiza las variables del objeto
void Intro::update() { void Intro::update(float dt_s) {
Audio::update(); Audio::update();
checkInput(); checkInput();
if (SDL_GetTicks() - ticks_ > ticks_speed_) { for (auto *bitmap : bitmaps_) {
// Actualiza el contador de ticks bitmap->update(dt_s);
ticks_ = SDL_GetTicks();
// Actualiza los objetos
for (auto *bitmap : bitmaps_) {
bitmap->update();
}
for (auto *t : texts_) {
t->update();
}
// Actualiza las escenas de la intro
updateScenes();
} }
for (auto *t : texts_) {
t->update(dt_s);
}
updateScenes();
} }
// Dibuja el objeto en pantalla // Dibuja el objeto en pantalla
@@ -386,7 +406,8 @@ void Intro::run() {
// Ejecuta un frame // Ejecuta un frame
void Intro::iterate() { void Intro::iterate() {
update(); const float DELTA_TIME_S = DeltaTime::tick();
update(DELTA_TIME_S);
render(); render();
} }
+5 -7
View File
@@ -36,15 +36,13 @@ class Intro {
Section *section_; // Estado del bucle principal para saber si continua o se sale Section *section_; // Estado del bucle principal para saber si continua o se sale
// Variables // Variables
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa
Uint8 ticks_speed_; // Velocidad a la que se repiten los bucles del programa
Ja::Music *music_; // Musica para la intro Ja::Music *music_; // Musica para la intro
int scene_; // Indica que escena está activa int scene_; // Indica que escena está activa
void update(); // Actualiza las variables del objeto void update(float dt_s); // Actualiza las variables del objeto (time-based)
void render(); // Dibuja el objeto en pantalla void render(); // Dibuja el objeto en pantalla
void checkInput(); // Comprueba las entradas void checkInput(); // Comprueba las entradas
void updateScenes(); // Actualiza las escenas de la intro void updateScenes(); // Actualiza las escenas de la intro
// Helpers de updateScenes, uno por cada escena // Helpers de updateScenes, uno por cada escena
void updateScene1(); void updateScene1();
+21 -24
View File
@@ -11,12 +11,16 @@
#include "core/rendering/screen.h" // for Screen #include "core/rendering/screen.h" // for Screen
#include "core/rendering/sprite.h" // for Sprite #include "core/rendering/sprite.h" // for Sprite
#include "core/resources/resource.h" #include "core/resources/resource.h"
#include "game/defaults.hpp" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG... #include "core/system/delta_time.hpp" // for DeltaTime::reset / tick
#include "utils/utils.h" // for Section, Color #include "game/defaults.hpp" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG...
#include "utils/utils.h" // for Section, Color
// Valores de inicialización y fin // Durades de l'escena (segons). Time-based: ja no comptem frames. Valors
constexpr int INIT_FADE = 100; // equivalents al comportament anterior (frame counter a 15ms): 100 i 200
constexpr int END_LOGO = 200; // frames ⇒ 1.5s i 3.0s; fi a 220 frames ⇒ 3.3s.
constexpr float FADE_START_S = 1.5F;
constexpr float FADE_END_S = 3.0F;
constexpr float SCENE_END_S = 3.3F;
// Constructor // Constructor
Logo::Logo(SDL_Renderer *renderer, Section *section) { Logo::Logo(SDL_Renderer *renderer, Section *section) {
@@ -30,13 +34,14 @@ Logo::Logo(SDL_Renderer *renderer, Section *section) {
sprite_ = new Sprite(14, 75, 226, 44, texture_, renderer); sprite_ = new Sprite(14, 75, 226, 44, texture_, renderer);
// Inicializa variables // Inicializa variables
counter_ = 0;
section->name = SECTION_PROG_LOGO; section->name = SECTION_PROG_LOGO;
section->subsection = 0; section->subsection = 0;
ticks_ = 0;
ticks_speed_ = 15;
Audio::get()->stopMusic(); Audio::get()->stopMusic();
// Reset del rellotge: la primera crida a tick() retornarà ~0 i no un
// delta gegant arrossegat des del boot o l'escena anterior.
DeltaTime::reset();
} }
// Destructor // Destructor
@@ -48,7 +53,7 @@ Logo::~Logo() {
// Comprueba si ha terminado el logo // Comprueba si ha terminado el logo
void Logo::checkLogoEnd() { void Logo::checkLogoEnd() {
if (counter_ >= END_LOGO + 20) { if (elapsed_time_s_ >= SCENE_END_S) {
section_->name = SECTION_PROG_INTRO; section_->name = SECTION_PROG_INTRO;
section_->subsection = 0; section_->subsection = 0;
} }
@@ -68,9 +73,8 @@ void Logo::checkInput() {
// Dibuja el fade // Dibuja el fade
void Logo::renderFade() { void Logo::renderFade() {
// Dibuja el fade if (elapsed_time_s_ >= FADE_START_S) {
if (counter_ >= INIT_FADE) { const float STEP = (elapsed_time_s_ - FADE_START_S) / (FADE_END_S - FADE_START_S);
const float STEP = (float)(counter_ - INIT_FADE) / (float)(END_LOGO - INIT_FADE);
const int ALPHA = std::min((int)(255 * STEP), 255); const int ALPHA = std::min((int)(255 * STEP), 255);
SDL_SetRenderDrawColor(renderer_, BG_COLOR.r, BG_COLOR.g, BG_COLOR.b, ALPHA); SDL_SetRenderDrawColor(renderer_, BG_COLOR.r, BG_COLOR.g, BG_COLOR.b, ALPHA);
SDL_RenderFillRect(renderer_, nullptr); SDL_RenderFillRect(renderer_, nullptr);
@@ -78,20 +82,12 @@ void Logo::renderFade() {
} }
// Actualiza las variables del objeto // Actualiza las variables del objeto
void Logo::update() { void Logo::update(float delta_time_s) {
Audio::update(); Audio::update();
checkInput(); checkInput();
if (SDL_GetTicks() - ticks_ > ticks_speed_) { elapsed_time_s_ += delta_time_s;
// Actualiza el contador de ticks checkLogoEnd();
ticks_ = SDL_GetTicks();
// Actualiza el contador
counter_++;
// Comprueba si ha terminado el logo
checkLogoEnd();
}
} }
// Dibuja el objeto en pantalla // Dibuja el objeto en pantalla
@@ -123,7 +119,8 @@ void Logo::run() {
// Ejecuta un frame // Ejecuta un frame
void Logo::iterate() { void Logo::iterate() {
update(); const float DELTA_TIME_S = DeltaTime::tick();
update(DELTA_TIME_S);
render(); render();
} }
+7 -9
View File
@@ -26,14 +26,12 @@ class Logo {
Sprite *sprite_; // Sprite con la textura del logo Sprite *sprite_; // Sprite con la textura del logo
Section *section_; // Estado del bucle principal para saber si continua o se sale Section *section_; // Estado del bucle principal para saber si continua o se sale
// Variables // Temps acumulat de l'escena (segons). Time-based: no comptem frames.
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa float elapsed_time_s_{0.0F};
Uint32 ticks_speed_; // Velocidad a la que se repiten los bucles del programa
int counter_; // Contador
void update(); // Actualiza las variables del objeto void update(float delta_time_s); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla void render(); // Dibuja el objeto en pantalla
void checkLogoEnd(); // Comprueba si ha terminado el logo void checkLogoEnd(); // Comprueba si ha terminado el logo
void checkInput(); // Comprueba las entradas void checkInput(); // Comprueba las entradas
void renderFade(); // Dibuja el fade void renderFade(); // Dibuja el fade
}; };
+87 -62
View File
@@ -2,9 +2,10 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <cstdlib> // for rand #include <algorithm> // for min
#include <iostream> // for basic_ostream, operator<<, basic_ostrea... #include <cstdlib> // for rand
#include <string> // for basic_string, operator+, char_traits #include <iostream> // for basic_ostream, operator<<, basic_ostrea...
#include <string> // for basic_string, operator+, char_traits
#include "core/audio/audio.hpp" // for Audio #include "core/audio/audio.hpp" // for Audio
#include "core/input/global_inputs.hpp" // for GlobalInputs::handle #include "core/input/global_inputs.hpp" // for GlobalInputs::handle
@@ -19,10 +20,11 @@
#include "core/rendering/texture.h" // for Texture #include "core/rendering/texture.h" // for Texture
#include "core/resources/asset.h" // for Asset #include "core/resources/asset.h" // for Asset
#include "core/resources/resource.h" #include "core/resources/resource.h"
#include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT #include "core/system/delta_time.hpp" // for DeltaTime::reset / tick
#include "game/game.h" // for Game #include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT
#include "game/options.hpp" // for Options #include "game/game.h" // for Game
#include "game/ui/menu.h" // for Menu #include "game/options.hpp" // for Options
#include "game/ui/menu.h" // for Menu
// Constructor // Constructor
Title::Title(SDL_Renderer *renderer, Section *section) { Title::Title(SDL_Renderer *renderer, Section *section) {
@@ -70,6 +72,9 @@ Title::Title(SDL_Renderer *renderer, Section *section) {
// Inicializa los valores // Inicializa los valores
init(); init();
// Reset del rellotge: la primera crida a tick() retornarà ~0.
DeltaTime::reset();
} }
// Destructor // Destructor
@@ -92,18 +97,19 @@ Title::~Title() {
void Title::init() { void Title::init() {
// Inicializa variables // Inicializa variables
section_->subsection = SUBSECTION_TITLE_1; section_->subsection = SUBSECTION_TITLE_1;
counter_ = COUNTER; demo_remaining_s_ = DEMO_TIMEOUT_S;
background_counter_ = 0; bg_scroll_x_s_ = 0.0F;
bg_scroll_y_s_ = 0.0F;
bg_phase_s_ = 0.0F;
blink_phase_s_ = 0.0F;
background_mode_ = rand() % 2; background_mode_ = rand() % 2;
menu_visible_ = false; menu_visible_ = false;
menu_.active = menu_.title; menu_.active = menu_.title;
next_section_.name = SECTION_PROG_GAME; next_section_.name = SECTION_PROG_GAME;
post_fade_ = 0; post_fade_ = 0;
ticks_ = 0;
ticks_speed_ = 15;
fade_->init(0x17, 0x17, 0x26); fade_->init(0x17, 0x17, 0x26);
demo_ = true; demo_ = true;
vibration_step_ = 0; vibration_elapsed_s_ = 0.0F;
vibration_initialized_ = false; vibration_initialized_ = false;
instructions_active_ = false; instructions_active_ = false;
demo_game_active_ = false; demo_game_active_ = false;
@@ -148,12 +154,12 @@ void Title::init() {
coffee_bitmap_->setWidth(167); coffee_bitmap_->setWidth(167);
coffee_bitmap_->setHeight(46); coffee_bitmap_->setHeight(46);
coffee_bitmap_->setVelX(0.0F); coffee_bitmap_->setVelX(0.0F);
coffee_bitmap_->setVelY(2.5F); coffee_bitmap_->setVelY(150.0F); // 2.5 px/tick ⇒ 150 px/s
coffee_bitmap_->setAccelX(0.0F); coffee_bitmap_->setAccelX(0.0F);
coffee_bitmap_->setAccelY(0.1F); coffee_bitmap_->setAccelY(360.0F); // 0.1 px/tick² ⇒ 360 px/s²
coffee_bitmap_->setSpriteClip(0, 0, 167, 46); coffee_bitmap_->setSpriteClip(0, 0, 167, 46);
coffee_bitmap_->setEnabled(true); coffee_bitmap_->setEnabled(true);
coffee_bitmap_->setEnabledCounter(0); coffee_bitmap_->setRemainingTime(0.0F);
coffee_bitmap_->setDestX(45); coffee_bitmap_->setDestX(45);
coffee_bitmap_->setDestY(11); coffee_bitmap_->setDestY(11);
@@ -164,12 +170,12 @@ void Title::init() {
crisis_bitmap_->setWidth(137); crisis_bitmap_->setWidth(137);
crisis_bitmap_->setHeight(46); crisis_bitmap_->setHeight(46);
crisis_bitmap_->setVelX(0.0F); crisis_bitmap_->setVelX(0.0F);
crisis_bitmap_->setVelY(-2.5F); crisis_bitmap_->setVelY(-150.0F); // -2.5 px/tick ⇒ -150 px/s
crisis_bitmap_->setAccelX(0.0F); crisis_bitmap_->setAccelX(0.0F);
crisis_bitmap_->setAccelY(-0.1F); crisis_bitmap_->setAccelY(-360.0F); // -0.1 px/tick² ⇒ -360 px/s²
crisis_bitmap_->setSpriteClip(0, 0, 137, 46); crisis_bitmap_->setSpriteClip(0, 0, 137, 46);
crisis_bitmap_->setEnabled(true); crisis_bitmap_->setEnabled(true);
crisis_bitmap_->setEnabledCounter(0); crisis_bitmap_->setRemainingTime(0.0F);
crisis_bitmap_->setDestX(60); crisis_bitmap_->setDestX(60);
crisis_bitmap_->setDestY(57); crisis_bitmap_->setDestY(57);
@@ -209,25 +215,20 @@ void Title::init() {
updateMenuLabels(); updateMenuLabels();
} }
// Actualiza las variables del objeto // Actualiza las variables del objeto (time-based)
void Title::update() { void Title::update(float dt_s) {
Audio::update(); Audio::update();
checkInput(); checkInput();
if (SDL_GetTicks() - ticks_ <= ticks_speed_) {
return;
}
ticks_ = SDL_GetTicks();
switch (section_->subsection) { switch (section_->subsection) {
case SUBSECTION_TITLE_1: case SUBSECTION_TITLE_1:
updateTitle1(); updateTitle1(dt_s);
break; break;
case SUBSECTION_TITLE_2: case SUBSECTION_TITLE_2:
updateTitle2(); updateTitle2(dt_s);
break; break;
case SUBSECTION_TITLE_3: case SUBSECTION_TITLE_3:
updateTitle3(); updateTitle3(dt_s);
break; break;
default: default:
break; break;
@@ -235,9 +236,9 @@ void Title::update() {
} }
// Sección 1 - Titulo desplazandose // Sección 1 - Titulo desplazandose
void Title::updateTitle1() { void Title::updateTitle1(float dt_s) {
coffee_bitmap_->update(); coffee_bitmap_->update(dt_s);
crisis_bitmap_->update(); crisis_bitmap_->update(dt_s);
// Si los objetos han llegado a su destino, cambiamos de Sección // Si los objetos han llegado a su destino, cambiamos de Sección
if (coffee_bitmap_->hasFinished() && crisis_bitmap_->hasFinished()) { if (coffee_bitmap_->hasFinished() && crisis_bitmap_->hasFinished()) {
@@ -257,7 +258,7 @@ void Title::updateTitle1() {
} }
// Sección 2 - Titulo vibrando // Sección 2 - Titulo vibrando
void Title::updateTitle2() { void Title::updateTitle2(float dt_s) {
// Captura las posiciones base la primera vez // Captura las posiciones base la primera vez
if (!vibration_initialized_) { if (!vibration_initialized_) {
vibration_coffee_base_x_ = coffee_bitmap_->getPosX(); vibration_coffee_base_x_ = coffee_bitmap_->getPosX();
@@ -265,37 +266,44 @@ void Title::updateTitle2() {
vibration_initialized_ = true; vibration_initialized_ = true;
} }
// Patró d'offset horitzontal (15 valors, una vegada cadascun cada 3 frames)
const int V[] = {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 0}; const int V[] = {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 0};
coffee_bitmap_->setPosX(vibration_coffee_base_x_ + V[vibration_step_ / 3]); constexpr int V_SIZE = static_cast<int>(sizeof(V) / sizeof(V[0]));
crisis_bitmap_->setPosX(vibration_crisis_base_x_ + V[vibration_step_ / 3]); const int IDX = std::min(static_cast<int>(vibration_elapsed_s_ / VIBRATION_STEP_DURATION_S), V_SIZE - 1);
dust_bitmap_right_->update(); coffee_bitmap_->setPosX(vibration_coffee_base_x_ + V[IDX]);
dust_bitmap_left_->update(); crisis_bitmap_->setPosX(vibration_crisis_base_x_ + V[IDX]);
dust_bitmap_right_->update(dt_s);
dust_bitmap_left_->update(dt_s);
vibration_step_++; vibration_elapsed_s_ += dt_s;
if (vibration_step_ >= 33) { if (vibration_elapsed_s_ >= VIBRATION_DURATION_S) {
section_->subsection = SUBSECTION_TITLE_3; section_->subsection = SUBSECTION_TITLE_3;
vibration_step_ = 0; vibration_elapsed_s_ = 0.0F;
vibration_initialized_ = false; vibration_initialized_ = false;
} }
} }
// Sección 3 - La pantalla de titulo con el menú y la música // Sección 3 - La pantalla de titulo con el menú y la música
void Title::updateTitle3() { void Title::updateTitle3(float dt_s) {
if (counter_ > 0) { if (demo_remaining_s_ > 0.0F) {
if (Audio::getRealMusicState() == Audio::MusicState::STOPPED) { if (Audio::getRealMusicState() == Audio::MusicState::STOPPED) {
Audio::get()->playMusic(title_music_); Audio::get()->playMusic(title_music_);
} }
dust_bitmap_right_->update(); dust_bitmap_right_->update(dt_s);
dust_bitmap_left_->update(); dust_bitmap_left_->update(dt_s);
fade_->update(); fade_->update(dt_s);
if (fade_->hasEnded()) { if (fade_->hasEnded()) {
handlePostFadeAction(); handlePostFadeAction();
} }
updateBG(); updateBG(dt_s);
blink_phase_s_ += dt_s;
if (blink_phase_s_ >= PRESS_ANY_KEY_PERIOD_S) {
blink_phase_s_ -= PRESS_ANY_KEY_PERIOD_S;
}
if (menu_visible_ && !fade_->isEnabled()) { if (menu_visible_ && !fade_->isEnabled()) {
menu_.active->update(); menu_.active->update();
@@ -312,9 +320,9 @@ void Title::updateTitle3() {
} }
if (menu_.active->getName() == "TITLE") { if (menu_.active->getName() == "TITLE") {
counter_--; demo_remaining_s_ -= dt_s;
} }
} else if (counter_ == 0) { } else {
if (demo_) { if (demo_) {
demo_then_instructions_ = true; demo_then_instructions_ = true;
runDemoGame(); runDemoGame();
@@ -351,7 +359,7 @@ void Title::handlePostFadeAction() {
break; break;
case 3: // TIME OUT case 3: // TIME OUT
counter_ = COUNTER; demo_remaining_s_ = DEMO_TIMEOUT_S;
menu_.active->reset(); menu_.active->reset();
if (demo_) { if (demo_) {
demo_then_instructions_ = true; demo_then_instructions_ = true;
@@ -602,8 +610,8 @@ void Title::render() {
dust_bitmap_right_->render(); dust_bitmap_right_->render();
dust_bitmap_left_->render(); dust_bitmap_left_->render();
// PRESS ANY KEY! // PRESS ANY KEY! Blink: visible quan la fase passa l'umbral "off".
if ((counter_ % 50 > 14) && (!menu_visible_)) { if ((blink_phase_s_ > PRESS_ANY_KEY_OFF_S) && (!menu_visible_)) {
text1_->writeDX(Text::FLAG_CENTER | Text::FLAG_SHADOW, GAMECANVAS_CENTER_X, PLAY_AREA_THIRD_QUARTER_Y + BLOCK, Lang::get()->getText(23), 1, NO_COLOR, 1, SHADOW_COLOR); text1_->writeDX(Text::FLAG_CENTER | Text::FLAG_SHADOW, GAMECANVAS_CENTER_X, PLAY_AREA_THIRD_QUARTER_Y + BLOCK, Lang::get()->getText(23), 1, NO_COLOR, 1, SHADOW_COLOR);
} }
@@ -626,15 +634,26 @@ void Title::checkInput() {
GlobalInputs::handle(); GlobalInputs::handle();
} }
// Actualiza el tileado de fondo // Actualiza el tileado de fondo (time-based).
void Title::updateBG() { void Title::updateBG(float dt_s) {
if (background_mode_ == 0) { // El tileado de fondo se desplaza en diagonal if (background_mode_ == 0) {
++background_window_.x %= 64; // Diagonal: 60 px/s a 60Hz ⇒ un cicle de 64 px cada 64/60 = 1.067 s.
++background_window_.y %= 64; // Ancorat a la posició inicial (128, 96): t=0 dona la mateixa vista que
} else { // El tileado de fondo se desplaza en circulo // l'estàtic dels Title1/Title2, sense salt visual a l'entrada del Title3.
++background_counter_ %= 360; constexpr float SCROLL_PERIOD_S = 64.0F / BG_SCROLL_SPEED_PX_PER_S;
background_window_.x = 128 + (int(sin_[(background_counter_ + 270) % 360] * 128)); bg_scroll_x_s_ += dt_s;
background_window_.y = 96 + (int(sin_[(360 - background_counter_) % 360] * 96)); bg_scroll_y_s_ += dt_s;
if (bg_scroll_x_s_ >= SCROLL_PERIOD_S) { bg_scroll_x_s_ -= SCROLL_PERIOD_S; }
if (bg_scroll_y_s_ >= SCROLL_PERIOD_S) { bg_scroll_y_s_ -= SCROLL_PERIOD_S; }
background_window_.x = 128 + static_cast<int>(bg_scroll_x_s_ * BG_SCROLL_SPEED_PX_PER_S);
background_window_.y = 96 + static_cast<int>(bg_scroll_y_s_ * BG_SCROLL_SPEED_PX_PER_S);
} else {
// Cercle: 360 graus en BG_CIRCLE_PERIOD_S segons.
bg_phase_s_ += dt_s;
if (bg_phase_s_ >= BG_CIRCLE_PERIOD_S) { bg_phase_s_ -= BG_CIRCLE_PERIOD_S; }
const int ANGLE = static_cast<int>((bg_phase_s_ / BG_CIRCLE_PERIOD_S) * 360.0F) % 360;
background_window_.x = 128 + (int(sin_[(ANGLE + 270) % 360] * 128));
background_window_.y = 96 + (int(sin_[(360 - ANGLE) % 360] * 96));
} }
} }
@@ -834,9 +853,11 @@ void Title::applyOptions() {
// Ejecuta un frame // Ejecuta un frame
void Title::iterate() { void Title::iterate() {
const float DELTA_TIME_S = DeltaTime::tick();
// Si las instrucciones están activas, delega el frame // Si las instrucciones están activas, delega el frame
if (instructions_active_) { if (instructions_active_) {
instructions_->update(); instructions_->update(DELTA_TIME_S);
instructions_->render(); instructions_->render();
if (instructions_->hasFinished()) { if (instructions_->hasFinished()) {
@@ -855,6 +876,8 @@ void Title::iterate() {
section_->name = SECTION_PROG_TITLE; section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_3; section_->subsection = SUBSECTION_TITLE_3;
} }
// Reset del rellotge per evitar un dt enorme al tornar al Title.
DeltaTime::reset();
} }
return; return;
} }
@@ -884,6 +907,8 @@ void Title::iterate() {
section_->name = SECTION_PROG_TITLE; section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_1; section_->subsection = SUBSECTION_TITLE_1;
} }
// Reset del rellotge per evitar un dt enorme al tornar al Title.
DeltaTime::reset();
} else { } else {
// Restaura section para que Director no transicione fuera de Title // Restaura section para que Director no transicione fuera de Title
section_->name = SECTION_PROG_TITLE; section_->name = SECTION_PROG_TITLE;
@@ -892,7 +917,7 @@ void Title::iterate() {
} }
// Ejecución normal del título // Ejecución normal del título
update(); update(DELTA_TIME_S);
render(); render();
} }
@@ -918,7 +943,7 @@ void Title::handleEvent(const SDL_Event *event) {
if (section_->subsection == SUBSECTION_TITLE_3) { if (section_->subsection == SUBSECTION_TITLE_3) {
if ((event->type == SDL_EVENT_KEY_UP) || (event->type == SDL_EVENT_JOYSTICK_BUTTON_UP)) { if ((event->type == SDL_EVENT_KEY_UP) || (event->type == SDL_EVENT_JOYSTICK_BUTTON_UP)) {
menu_visible_ = true; menu_visible_ = true;
counter_ = COUNTER; demo_remaining_s_ = DEMO_TIMEOUT_S;
} }
} }
} }
+38 -23
View File
@@ -34,7 +34,21 @@ class Title {
private: private:
static constexpr const char *COPYRIGHT = "@2020 JailDesigner (v2.3.4)"; static constexpr const char *COPYRIGHT = "@2020 JailDesigner (v2.3.4)";
static constexpr int COUNTER = 800; // Time-based: temps màxim a la pantalla del títol abans de tornar al
// logo o llançar el demo. 800 frames a 60Hz ⇒ 13.333 s.
static constexpr float DEMO_TIMEOUT_S = 13.333F;
// Període i fracció "off" del blink del text "PRESS ANY KEY".
// Original: counter_ % 50 > 14 ⇒ 50 frames de període, 14 frames off.
static constexpr float PRESS_ANY_KEY_PERIOD_S = 50.0F / 60.0F;
static constexpr float PRESS_ANY_KEY_OFF_S = 14.0F / 60.0F;
// Vibració post-impacte (SUBSECTION_TITLE_2): 33 frames a 60Hz ⇒ 0.55 s,
// 11 valors del patró V[] consumits a `step/3` (3 frames per pas).
static constexpr float VIBRATION_DURATION_S = 33.0F / 60.0F;
static constexpr float VIBRATION_STEP_DURATION_S = 3.0F / 60.0F;
// BG mode 0: scroll diagonal 1 px/tick a 60Hz ⇒ 60 px/s.
static constexpr float BG_SCROLL_SPEED_PX_PER_S = 60.0F;
// BG mode 1: cicle de 360 frames a 60Hz ⇒ 6 s per volta.
static constexpr float BG_CIRCLE_PERIOD_S = 6.0F;
struct MenuData { struct MenuData {
Menu *title; // Menu de la pantalla de título Menu *title; // Menu de la pantalla de título
@@ -72,19 +86,20 @@ class Title {
Fade *fade_; // Objeto para realizar fundidos en pantalla Fade *fade_; // Objeto para realizar fundidos en pantalla
// Variables // Variables
Ja::Music *title_music_; // Musica para el titulo Ja::Music *title_music_; // Musica para el titulo
Ja::Sound *crash_sound_; // Sonido con el impacto del título Ja::Sound *crash_sound_; // Sonido con el impacto del título
int background_counter_; // Temporizador para el fondo de tiles de la pantalla de titulo float bg_scroll_x_s_{0.0F}; // Acumulador d'scroll horitzontal (segons) per al BG mode 0
int counter_; // Temporizador para la pantalla de titulo float bg_scroll_y_s_{0.0F}; // Acumulador d'scroll vertical (segons) per al BG mode 0
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa float bg_phase_s_{0.0F}; // Fase del cicle del BG mode 1 (0..BG_CIRCLE_PERIOD_S)
Uint8 background_mode_; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo float demo_remaining_s_; // Temps que queda al títol abans de tornar al logo / demo
float sin_[360]; // Vector con los valores del seno precalculados float blink_phase_s_{0.0F}; // Fase del blink de "PRESS ANY KEY"
bool menu_visible_; // Indicador para saber si se muestra el menu del titulo o la frase intermitente Uint8 background_mode_; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo
bool demo_; // Indica si el modo demo estará activo float sin_[360]; // Vector con los valores del seno precalculados
Section next_section_; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo bool menu_visible_; // Indicador para saber si se muestra el menu del titulo o la frase intermitente
Uint32 ticks_speed_; // Velocidad a la que se repiten los bucles del programa bool demo_; // Indica si el modo demo estará activo
Uint8 post_fade_; // Opción a realizar cuando termina el fundido Section next_section_; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo
MenuData menu_; // Variable con todos los objetos menus y sus variables Uint8 post_fade_; // Opción a realizar cuando termina el fundido
MenuData menu_; // Variable con todos los objetos menus y sus variables
// Snapshot per a permetre CANCEL al menú d'opcions. // Snapshot per a permetre CANCEL al menú d'opcions.
Options::Video prev_video_; Options::Video prev_video_;
Options::Window prev_window_; Options::Window prev_window_;
@@ -94,10 +109,10 @@ class Title {
std::vector<int> device_index_; // Indice para el jugador [i] del vector de dispositivos de entrada disponibles std::vector<int> device_index_; // Indice para el jugador [i] del vector de dispositivos de entrada disponibles
// Variables para la vibración del título (SUBSECTION_TITLE_2) // Variables para la vibración del título (SUBSECTION_TITLE_2)
int vibration_step_; // Paso actual de la vibración float vibration_elapsed_s_{0.0F}; // Temps transcorregut des de l'inici de la vibració
int vibration_coffee_base_x_{0}; // Posición X base del bitmap Coffee int vibration_coffee_base_x_{0}; // Posición X base del bitmap Coffee
int vibration_crisis_base_x_{0}; // Posición X base del bitmap Crisis int vibration_crisis_base_x_{0}; // Posición X base del bitmap Crisis
bool vibration_initialized_; // Indica si se han capturado las posiciones base bool vibration_initialized_; // Indica si se han capturado las posiciones base
// Variables para sub-estados delegados (instrucciones y demo) // Variables para sub-estados delegados (instrucciones y demo)
bool instructions_active_; // Indica si las instrucciones están activas bool instructions_active_; // Indica si las instrucciones están activas
@@ -106,20 +121,20 @@ class Title {
bool demo_then_instructions_; // Indica si tras la demo hay que mostrar instrucciones bool demo_then_instructions_; // Indica si tras la demo hay que mostrar instrucciones
void init(); // Inicializa los valores void init(); // Inicializa los valores
void update(); // Actualiza las variables del objeto void update(float dt_s); // Actualiza las variables del objeto (time-based)
void render(); // Dibuja el objeto en pantalla void render(); // Dibuja el objeto en pantalla
static void checkInput(); // Comprueba las entradas (només delega a GlobalInputs) static void checkInput(); // Comprueba las entradas (només delega a GlobalInputs)
// Helpers de update, uno por cada subsección y por cada switch dentro del título 3 // Helpers de update, uno por cada subsección y por cada switch dentro del título 3
void updateTitle1(); void updateTitle1(float dt_s);
void updateTitle2(); void updateTitle2(float dt_s);
void updateTitle3(); void updateTitle3(float dt_s);
void handlePostFadeAction(); void handlePostFadeAction();
void handleTitleMenuSelection(); void handleTitleMenuSelection();
void handlePlayerSelectMenuSelection(); void handlePlayerSelectMenuSelection();
void handleOptionsMenuSelection(); void handleOptionsMenuSelection();
void updateBG(); // Actualiza el tileado de fondo void updateBG(float dt_s); // Actualiza el tileado de fondo (time-based)
static void switchFullScreenModeVar(); // Cambia el valor de la variable de modo de pantalla completa static void switchFullScreenModeVar(); // Cambia el valor de la variable de modo de pantalla completa
void updateMenuLabels() const; // Actualiza los elementos de los menus void updateMenuLabels() const; // Actualiza los elementos de los menus
void applyOptions(); // Aplica las opciones de menu seleccionadas void applyOptions(); // Aplica las opciones de menu seleccionadas