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 {
// Normalitza CRLF: fitxers .ani amb terminadors de Windows fan que
// line == "[animation]" no faci match i el parser entri en bucle
// infinit / no carregui cap animació.
void stripCr(std::string &s) {
if (!s.empty() && s.back() == '\r') {
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;
// Normalitza CRLF: fitxers .ani amb terminadors de Windows fan que
// line == "[animation]" no faci match i el parser entri en bucle
// infinit / no carregui cap animació.
void stripCr(std::string &s) {
if (!s.empty() && s.back() == '\r') {
s.pop_back();
}
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);
}
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);
}
} 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;
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);
// 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;
const int H = texture->getHeight() / frame_height;
max_tiles = W * H;
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.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
@@ -204,33 +211,26 @@ auto AnimatedSprite::getIndex(const std::string &name) -> int {
return -1;
}
// Calcula el frame correspondiente a la animación
void AnimatedSprite::animate() {
if (!enabled_ || animation_[current_animation_].speed == 0) {
// Avança l'acumulador i calcula el frame actual a partir de `step_duration_s`.
void AnimatedSprite::animate(float dt_s) {
Animation &anim = animation_[current_animation_];
if (!enabled_ || anim.step_duration_s <= 0.0F) {
return;
}
// Calcula el frame actual a partir del contador
animation_[current_animation_].current_frame = animation_[current_animation_].counter / animation_[current_animation_].speed;
anim.time_accumulator_s += dt_s;
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
// en función de la variable loop y coloca el nuevo frame
if (animation_[current_animation_].current_frame >= (int)animation_[current_animation_].frames.size()) {
if (animation_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
animation_[current_animation_].current_frame = animation_[current_animation_].frames.size();
animation_[current_animation_].completed = true;
} else { // Si hay loop, vuelve al frame indicado
animation_[current_animation_].counter = 0;
animation_[current_animation_].current_frame = animation_[current_animation_].loop;
if (anim.current_frame >= (int)anim.frames.size()) {
if (anim.loop == -1) {
anim.current_frame = anim.frames.size();
anim.completed = true;
} else {
anim.time_accumulator_s = 0.0F;
anim.current_frame = anim.loop;
}
}
// En caso contrario
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++;
} else {
setSpriteClip(anim.frames[anim.current_frame]);
}
}
@@ -344,10 +344,10 @@ void AnimatedSprite::setCurrentAnimation(int index) {
}
}
// Actualiza las variables del objeto
void AnimatedSprite::update() {
animate();
MovingSprite::update();
// animate(dt) + MovingSprite::update(dt) (move + rotació)
void AnimatedSprite::update(float dt_s) {
animate(dt_s);
MovingSprite::update(dt_s);
}
// Establece el rectangulo para un frame de una animación
+7 -5
View File
@@ -12,11 +12,13 @@ class Texture;
struct Animation {
std::string name; // Nombre de la animacion
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
bool completed; // Indica si ha finalizado la animación
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 {
@@ -37,7 +39,7 @@ class AnimatedSprite : public MovingSprite {
~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
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
@@ -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(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
void setCurrentAnimation(const std::string &name = "default"); // Establece la animacion actual
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 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;
finished_ = false;
counter_ = 0;
elapsed_s_ = 0.0F;
r_ = r;
g_ = g;
b_ = b;
@@ -155,11 +156,15 @@ void Fade::renderFadeRandomSquare() {
}
}
// Actualiza las variables internas
void Fade::update() {
if (enabled_) {
counter_++;
}
// Actualiza les variables internes. `counter_` (Uint16, frames a la cadència
// de referència 60Hz) es deriva de `elapsed_s_` perquè els helpers de
// `render()` (renderFadeFullscreen / Center / RandomSquare) segueixin
// 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
@@ -167,6 +172,7 @@ void Fade::activateFade() {
enabled_ = true;
finished_ = false;
counter_ = 0;
elapsed_s_ = 0.0F;
squares_drawn_ = 0;
last_square_ticks_ = 0;
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 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
[[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_Texture *backbuffer_ = nullptr; // Textura para usar como backbuffer
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 finished_ = false; // Indica si ha terminado la transición
Uint8 r_ = 0, g_ = 0, b_ = 0; // Colores para el fade
+16 -25
View File
@@ -34,22 +34,22 @@ void MovingSprite::clear() {
center_ = nullptr;
rotate_speed_ = 0;
rotate_amount_ = 0.0;
counter_ = 0;
current_flip_ = SDL_FLIP_NONE;
}
// Mueve el sprite
void MovingSprite::move() {
// Mueve el sprite. vx_/vy_ en px/s, ax_/ay_ en px/s². Integració d'Euler
// senzilla — suficient per a moviments sense col·lisions sensibles.
void MovingSprite::move(float dt_s) {
if (enabled_) {
x_prev_ = x_;
y_prev_ = y_;
x_ += vx_;
y_ += vy_;
x_ += vx_ * dt_s;
y_ += vy_ * dt_s;
vx_ += ax_;
vy_ += ay_;
vx_ += ax_ * dt_s;
vy_ += ay_ * dt_s;
}
}
@@ -180,17 +180,6 @@ auto MovingSprite::getRotateSpeed() const -> Uint16 {
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
void MovingSprite::setRotate(bool value) {
rotate_enabled_ = value;
@@ -216,13 +205,15 @@ void MovingSprite::disableRotate() {
angle_ = (double)0;
}
// Actualiza las variables internas del objeto
void MovingSprite::update() {
move();
rotate();
if (enabled_) {
++counter_ %= 60000;
// Actualiza les variables internes (move + rotació integrada). La rotació
// frame-based original era `incAngle(rotate_amount_)` cada `rotate_speed_`
// frames a 60Hz, equivalent a velocitat angular constant
// = rotate_amount_ * 60 / rotate_speed_ graus/s.
void MovingSprite::update(float dt_s) {
move(dt_s);
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:
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 rotate(); // Rota el sprite
virtual void update(); // Actualiza las variables internas del objeto
void clear(); // Reinicia todas las variables
void render() override; // Muestra el sprite por pantalla
void move(float dt_s); // Mueve el sprite (vx/vy/ax/ay en px/s i px/s^2)
virtual void update(float dt_s); // Actualiza les variables internes (move + rotació integrada)
void clear(); // Reinicia todas las variables
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
[[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
bool rotate_enabled_{false}; // Indica si ha de rotar
int rotate_speed_{0}; // Velocidad de giro
double rotate_amount_{0.0}; // Cantidad de grados a girar en cada iteración
int counter_{0}; // Contador interno
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 pas
SDL_Point *center_{nullptr}; // Centro de rotación
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;
dest_x_ = 0;
dest_y_ = 0;
counter_ = 0;
finished_ = false;
}
// Actualiza la posición y comprueba si ha llegado a su destino
void SmartSprite::update() {
// La velocitat i acceleració són en px/s i px/s²; el temps de permanència
// després d'arribar al destí ve donat per setRemainingTime().
void SmartSprite::update(float dt_s) {
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())
MovingSprite::update();
MovingSprite::update(dt_s);
// Comprueba el movimiento
checkMove();
// Comprueba si ha terminado
checkFinished();
checkFinished(dt_s);
}
}
@@ -56,6 +53,15 @@ void SmartSprite::setEnabledCounter(int 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
void SmartSprite::setDestX(int x) {
dest_x_ = x;
@@ -129,16 +135,15 @@ void SmartSprite::checkMove() {
}
}
// Comprueba si ha terminado
void SmartSprite::checkFinished() {
// Comprueba si ha llegado a su destino
// Decrementa el temps restant cada crida si està al destí
void SmartSprite::checkFinished(float dt_s) {
on_destination_ = getPosX() == dest_x_ && getPosY() == dest_y_;
if (on_destination_) { // Si esta en el destino comprueba su contador
if (enabled_counter_ == 0) { // Si ha llegado a cero, deshabilita el objeto y lo marca como finalizado
if (on_destination_) {
if (remaining_time_s_ <= 0.0F) {
finished_ = true;
} else { // Si no ha llegado a cero, decrementa el contador
enabled_counter_--;
} else {
remaining_time_s_ -= dt_s;
}
}
}
+21 -18
View File
@@ -10,27 +10,30 @@ class SmartSprite : public AnimatedSprite {
public:
SmartSprite(Texture *texture, SDL_Renderer *renderer); // Constructor
void init(); // Inicializa el objeto
void update() override; // Actualiza la posición y comprueba si ha llegado a su destino
void render() override; // Pinta el objeto en pantalla
void init(); // Inicializa el objeto
void update(float dt_s) override; // Actualiza la posicion
void render() override; // Pinta el objeto en pantalla
[[nodiscard]] auto getEnabledCounter() const -> int; // Obtiene 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 setDestY(int y); // Establece el valor de la variable
[[nodiscard]] auto getDestX() const -> int; // 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
[[nodiscard]] auto getEnabledCounter() const -> int; // Obtiene el valor de la variable
void setEnabledCounter(int value); // Establece el valor de la variable
void setRemainingTime(float seconds); // Time-based: temps que es queda visible despres d'arribar al desti
[[nodiscard]] auto getRemainingTime() const -> float; // Time-based: temps restant
void setDestX(int x); // Establece el valor de la variable
void setDestY(int y); // Establece el valor de la variable
[[nodiscard]] auto getDestX() const -> int; // 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:
// Variables
bool on_destination_; // Indica si está en el destino
int dest_x_; // Posicion de destino en el eje X
int dest_y_; // Posicion de destino en el eje Y
int enabled_counter_; // Contador para deshabilitarlo
bool finished_; // Indica si ya ha terminado
bool on_destination_; // Indica si está en el destino
int dest_x_; // Posicion de destino en el eje X
int dest_y_; // Posicion de destino en el eje Y
int enabled_counter_; // Contador (frames, derivat de remaining_time_s_ * 60)
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 checkFinished(); // Comprueba si ha terminado
void checkMove(); // Comprueba el movimiento
void checkFinished(float dt_s); // Comprueba si ha terminado
};
+27 -31
View File
@@ -7,30 +7,27 @@ Writer::Writer(Text *text)
: text_(text) {
}
// Actualiza el objeto
void Writer::update() {
if (enabled_) {
if (!completed_) { // No completado
if (writing_counter_ > 0) {
writing_counter_--;
}
// Avança un caracter cada `seconds_per_char_` i un cop completat es queda
// visible `remaining_time_s_` segons abans de finalitzar.
void Writer::update(float dt_s) {
if (!enabled_) { return; }
else if (writing_counter_ == 0) {
index_++;
writing_counter_ = speed_;
}
if (index_ == length_) {
completed_ = true;
}
if (!completed_) {
char_timer_s_ += dt_s;
while (char_timer_s_ >= seconds_per_char_ && index_ < length_) {
char_timer_s_ -= seconds_per_char_;
++index_;
}
if (index_ >= length_) {
completed_ = true;
}
}
if (completed_) { // Completado
if (enabled_counter_ > 0) {
enabled_counter_--;
} else if (enabled_counter_ == 0) {
finished_ = true;
}
if (completed_) {
if (remaining_time_s_ <= 0.0F) {
finished_ = true;
} else {
remaining_time_s_ -= dt_s;
}
}
}
@@ -63,10 +60,10 @@ void Writer::setCaption(const std::string &text) {
length_ = text.length();
}
// Establece el valor de la variable
void Writer::setSpeed(int value) {
speed_ = value;
writing_counter_ = value;
// Segons per caracter. Quan s'usa, l'update(dt) avança index.
void Writer::setSecondsPerChar(float seconds) {
seconds_per_char_ = seconds;
char_timer_s_ = 0.0F;
}
// Establece el valor de la variable
@@ -79,14 +76,13 @@ auto Writer::isEnabled() const -> bool {
return enabled_;
}
// Establece el valor de la variable
void Writer::setEnabledCounter(int time) {
enabled_counter_ = time;
// Temps que es mante visible despres de completar el text.
void Writer::setRemainingTime(float seconds) {
remaining_time_s_ = seconds;
}
// Obtiene el valor de la variable
auto Writer::getEnabledCounter() const -> int {
return enabled_counter_;
auto Writer::getRemainingTime() const -> float {
return remaining_time_s_;
}
// Centra la cadena de texto a un punto X
+17 -17
View File
@@ -8,19 +8,19 @@ class Writer {
public:
explicit Writer(Text *text); // Constructor
void update(); // Actualiza el objeto
void render(); // Dibuja el objeto en pantalla
void update(float dt_s); // Actualiza el objeto
void render(); // Dibuja el objeto en pantalla
void setPosX(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 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
[[nodiscard]] auto isEnabled() const -> bool; // Obtiene el valor de la variable
void setEnabledCounter(int time); // Establece el valor de la variable
[[nodiscard]] auto getEnabledCounter() const -> int; // Obtiene el valor de la variable
void setRemainingTime(float seconds); // Temps despres de completar
[[nodiscard]] auto getRemainingTime() const -> float; // Temps restant
void center(int x); // Centra la cadena de texto a un punto X
[[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
// Variables
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 kerning_{0}; // Kerning del texto, es decir, espaciado entre caracteres
std::string caption_; // El texto para escribir
int speed_{0}; // Velocidad de escritura
int writing_counter_{0}; // Temporizador de escritura para cada caracter
int index_{0}; // Posición del texto que se está escribiendo
int length_{0}; // Longitud de la cadena a escribir
bool completed_{false}; // Indica si se ha escrito todo el texto
bool enabled_{false}; // Indica si el objeto está habilitado
int enabled_counter_{0}; // Temporizador para deshabilitar el objeto
bool finished_{false}; // Indica si ya ha terminado
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 kerning_{0}; // Kerning del texto, es decir, espaciado entre caracteres
std::string caption_; // El texto para escribir
float seconds_per_char_{0.0F}; // Segons per caracter
float char_timer_s_{0.0F}; // Acumulador d'avanç de caracter
int index_{0}; // Posición del texto que se está escribiendo
int length_{0}; // Longitud de la cadena a escribir
bool completed_{false}; // Indica si se ha escrito todo el texto
bool enabled_{false}; // Indica si el objeto está habilitado
float remaining_time_s_{0.0F}; // Temps restant per a deshabilitar
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 <cmath> // for std::fabs
#include <algorithm> // for std::max
#include <cmath> // for std::fabs
#include "core/rendering/animatedsprite.h" // for AnimatedSprite
#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) {
case BALLOON_1:
// Alto y ancho del objeto
width_ = WIDTH_1;
height_ = WIDTH_1;
size_ = SIZE_1;
power_ = 1;
// Inicializa los valores de velocidad y gravedad
this->vel_x_ = velx;
vel_y_ = 0;
max_vel_y_ = 3.0F;
gravity_ = 0.09F;
default_vel_y_ = 2.6F;
vel_x_s_ = velx;
vel_y_s_ = 0.0F;
max_vel_y_s_ = 180.0F;
gravity_s_ = 324.0F;
default_vel_y_s_ = 156.0F;
// Puntos que da el globo al ser destruido
score_ = SCORE_1;
// Amenaza que genera el globo
menace_ = 1;
break;
case BALLOON_2:
// Alto y ancho del objeto
width_ = WIDTH_2;
height_ = WIDTH_2;
size_ = SIZE_2;
power_ = 3;
// Inicializa los valores de velocidad y gravedad
this->vel_x_ = velx;
vel_y_ = 0;
max_vel_y_ = 3.0F;
gravity_ = 0.10F;
default_vel_y_ = 3.5F;
vel_x_s_ = velx;
vel_y_s_ = 0.0F;
max_vel_y_s_ = 180.0F;
gravity_s_ = 360.0F;
default_vel_y_s_ = 210.0F;
// Puntos que da el globo al ser destruido
score_ = SCORE_2;
// Amenaza que genera el globo
menace_ = 2;
break;
case BALLOON_3:
// Alto y ancho del objeto
width_ = WIDTH_3;
height_ = WIDTH_3;
size_ = SIZE_3;
power_ = 7;
// Inicializa los valores de velocidad y gravedad
this->vel_x_ = velx;
vel_y_ = 0;
max_vel_y_ = 3.0F;
gravity_ = 0.10F;
default_vel_y_ = 4.50F;
vel_x_s_ = velx;
vel_y_s_ = 0.0F;
max_vel_y_s_ = 180.0F;
gravity_s_ = 360.0F;
default_vel_y_s_ = 270.0F;
// Puntos que da el globo al ser destruido
score_ = SCORE_3;
// Amenaza que genera el globo
menace_ = 4;
break;
case BALLOON_4:
// Alto y ancho del objeto
width_ = WIDTH_4;
height_ = WIDTH_4;
size_ = SIZE_4;
power_ = 15;
// Inicializa los valores de velocidad y gravedad
this->vel_x_ = velx;
vel_y_ = 0;
max_vel_y_ = 3.0F;
gravity_ = 0.10F;
default_vel_y_ = 4.95F;
vel_x_s_ = velx;
vel_y_s_ = 0.0F;
max_vel_y_s_ = 180.0F;
gravity_s_ = 360.0F;
default_vel_y_s_ = 297.0F;
// Puntos que da el globo al ser destruido
score_ = SCORE_4;
// Amenaza que genera el globo
menace_ = 8;
break;
case HEXAGON_1:
// Alto y ancho del objeto
width_ = WIDTH_1;
height_ = WIDTH_1;
size_ = SIZE_1;
power_ = 1;
// Inicializa los valores de velocidad y gravedad
this->vel_x_ = velx;
vel_y_ = std::fabs(velx) * 2;
max_vel_y_ = std::fabs(velx) * 2;
gravity_ = 0.00F;
default_vel_y_ = std::fabs(velx) * 2;
vel_x_s_ = velx;
vel_y_s_ = std::fabs(velx) * 2.0F;
max_vel_y_s_ = std::fabs(velx) * 2.0F;
gravity_s_ = 0.0F;
default_vel_y_s_ = std::fabs(velx) * 2.0F;
// Puntos que da el globo al ser destruido
score_ = SCORE_1;
// Amenaza que genera el globo
menace_ = 1;
break;
case HEXAGON_2:
// Alto y ancho del objeto
width_ = WIDTH_2;
height_ = WIDTH_2;
size_ = SIZE_2;
power_ = 3;
// Inicializa los valores de velocidad y gravedad
this->vel_x_ = velx;
vel_y_ = std::fabs(velx) * 2;
max_vel_y_ = std::fabs(velx) * 2;
gravity_ = 0.00F;
default_vel_y_ = std::fabs(velx) * 2;
vel_x_s_ = velx;
vel_y_s_ = std::fabs(velx) * 2.0F;
max_vel_y_s_ = std::fabs(velx) * 2.0F;
gravity_s_ = 0.0F;
default_vel_y_s_ = std::fabs(velx) * 2.0F;
// Puntos que da el globo al ser destruido
score_ = SCORE_2;
// Amenaza que genera el globo
menace_ = 2;
break;
case HEXAGON_3:
// Alto y ancho del objeto
width_ = WIDTH_3;
height_ = WIDTH_3;
size_ = SIZE_3;
power_ = 7;
// Inicializa los valores de velocidad y gravedad
this->vel_x_ = velx;
vel_y_ = std::fabs(velx) * 2;
max_vel_y_ = std::fabs(velx) * 2;
gravity_ = 0.00F;
default_vel_y_ = std::fabs(velx) * 2;
vel_x_s_ = velx;
vel_y_s_ = std::fabs(velx) * 2.0F;
max_vel_y_s_ = std::fabs(velx) * 2.0F;
gravity_s_ = 0.0F;
default_vel_y_s_ = std::fabs(velx) * 2.0F;
// Puntos que da el globo al ser destruido
score_ = SCORE_3;
// Amenaza que genera el globo
menace_ = 4;
break;
case HEXAGON_4:
// Alto y ancho del objeto
width_ = WIDTH_4;
height_ = WIDTH_4;
size_ = SIZE_4;
power_ = 15;
// Inicializa los valores de velocidad y gravedad
this->vel_x_ = velx;
vel_y_ = std::fabs(velx) * 2;
max_vel_y_ = std::fabs(velx) * 2;
gravity_ = 0.00F;
default_vel_y_ = std::fabs(velx) * 2;
vel_x_s_ = velx;
vel_y_s_ = std::fabs(velx) * 2.0F;
max_vel_y_s_ = std::fabs(velx) * 2.0F;
gravity_s_ = 0.0F;
default_vel_y_s_ = std::fabs(velx) * 2.0F;
// Puntos que da el globo al ser destruido
score_ = SCORE_4;
// Amenaza que genera el globo
menace_ = 8;
break;
case POWER_BALL:
// Alto y ancho del objeto
width_ = WIDTH_4;
height_ = WIDTH_4;
size_ = 4;
power_ = 0;
// Inicializa los valores de velocidad y gravedad
this->vel_x_ = velx;
vel_y_ = 0;
max_vel_y_ = 3.0F;
gravity_ = 0.10F;
default_vel_y_ = 4.95F;
vel_x_s_ = velx;
vel_y_s_ = 0.0F;
max_vel_y_s_ = 180.0F;
gravity_s_ = 360.0F;
default_vel_y_s_ = 297.0F;
// Puntos que da el globo al ser destruido
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_
// % 0` dins MovingSprite::rotate().
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;
@@ -257,18 +208,21 @@ Balloon::Balloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 c
// Inicializa variables
stopped_ = true;
stopped_counter_ = 0;
stopped_counter_s_ = 0.0F;
blinking_ = false;
visible_ = true;
creation_counter_ = 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;
// Valores iniciales dependentes del timer
being_created_ = creation_counter_ != 0;
invulnerable_ = being_created_;
counter_ = 0;
travel_y_ = 1.0F;
this->speed_ = speed;
// Tipo
@@ -334,87 +288,38 @@ void Balloon::render() {
}
}
// Actualiza la posición y estados del globo
void Balloon::move() {
// Comprueba si se puede mover
// Actualiza la posición y estados del globo. Integració contínua: gravetat i
// posició s'apliquen escalades per `speed_` (tempo del joc) cada tick.
void Balloon::move(float dt_s) {
if (!isStopped()) {
// Lo mueve a izquierda o derecha
pos_x_ += (vel_x_ * speed_);
// Eix X
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)) {
// Corrige posición
pos_x_ -= (vel_x_ * speed_);
// Invierte sentido
vel_x_ = -vel_x_;
// Invierte la rotación
pos_x_ -= vel_x_s_ * speed_ * dt_s;
vel_x_s_ = -vel_x_s_;
sprite_->switchRotate();
// Activa el efecto de rebote
if (kind_ != POWER_BALL) {
bounceStart();
}
if (kind_ != POWER_BALL) { bounceStart(); }
}
// Mueve el globo hacia arriba o hacia abajo
pos_y_ += (vel_y_ * speed_);
// Eix Y
pos_y_ += vel_y_s_ * speed_ * dt_s;
// Si se sale por arriba
if (pos_y_ < PLAY_AREA_TOP) {
// Corrige
pos_y_ = PLAY_AREA_TOP;
// Invierte sentido
vel_y_ = -vel_y_;
// Activa el efecto de rebote
if (kind_ != POWER_BALL) {
bounceStart();
}
vel_y_s_ = -vel_y_s_;
if (kind_ != POWER_BALL) { bounceStart(); }
}
// Si el globo se sale por la parte inferior
if (pos_y_ + height_ > PLAY_AREA_BOTTOM) {
// Corrige
pos_y_ = PLAY_AREA_BOTTOM - height_;
// Invierte colocando una velocidad por defecto
vel_y_ = -default_vel_y_;
// Activa el efecto de rebote
if (kind_ != POWER_BALL) {
bounceStart();
}
vel_y_s_ = -default_vel_y_s_;
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_->setPosY(getPosY());
}
@@ -427,16 +332,19 @@ void Balloon::disable() {
collider_.r = 0;
collider_.x = 0;
collider_.y = 0;
counter_ = 0;
creation_counter_ = 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;
gravity_ = 0.0F;
gravity_s_ = 0.0F;
height_ = 0;
invulnerable_ = false;
kind_ = 0;
max_vel_y_ = 0.0F;
max_vel_y_s_ = 0.0F;
menace_ = 0;
popping_ = false;
pos_x_ = 0.0F;
@@ -447,9 +355,9 @@ void Balloon::disable() {
speed_ = 0;
stopped_ = false;
stopped_counter_ = 0;
travel_y_ = 0;
vel_x_ = 0.0F;
vel_y_ = 0.0F;
stopped_counter_s_ = 0.0F;
vel_x_s_ = 0.0F;
vel_y_s_ = 0.0F;
visible_ = false;
width_ = 0;
sprite_->clear();
@@ -466,30 +374,29 @@ void Balloon::pop() {
}
// Actualiza al globo a su posicion, animación y controla los contadores
void Balloon::update() {
void Balloon::update(float dt_s) {
if (enabled_) {
sprite_->MovingSprite::update();
move();
updateAnimation();
// MovingSprite::update(dt_s) avança la rotació (entre altres). La posició
// del sprite la posa move(dt_s) directament des de pos_x_/pos_y_.
sprite_->MovingSprite::update(dt_s);
move(dt_s);
updateAnimation(dt_s);
updateColliders();
updateState();
updateBounce();
counter_++;
updateState(dt_s);
updateBounce(dt_s);
}
}
// Actualiza los estados del globo
void Balloon::updateState() {
void Balloon::updateState(float dt_s) {
if (isPopping()) {
updateStatePopping();
}
if (isBeingCreated()) {
updateStateBeingCreated();
}
// Solo comprueba el estado detenido cuando no se está creando
else if (isStopped()) {
updateStateStopped();
updateStateBeingCreated(dt_s);
} else if (isStopped()) {
updateStateStopped(dt_s);
}
}
@@ -502,58 +409,54 @@ void Balloon::updateStatePopping() {
}
}
// Rama de updateState: globo creándose
void Balloon::updateStateBeingCreated() {
// Rama de updateState: globo creándose. Manté el chunk pattern original:
// 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);
setInvulnerable(true);
// Todavia tiene tiempo en el contador
if (creation_counter_ > 0) {
// Desplaza lentamente el globo hacia abajo y hacia un lado
if (creation_counter_ % 10 == 0) {
pos_y_++;
pos_x_ += vel_x_;
if (creation_counter_s_ > 0.0F) {
creation_phase_s_ += dt_s;
while (creation_phase_s_ >= CREATION_STEP_S) {
creation_phase_s_ -= CREATION_STEP_S;
pos_y_ += 1.0F;
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_))) {
// Corrige y cambia el sentido de la velocidad
pos_x_ -= vel_x_;
vel_x_ = -vel_x_;
pos_x_ -= DRIFT_X;
vel_x_s_ = -vel_x_s_;
}
// Actualiza la posición del sprite
sprite_->setPosX(getPosX());
sprite_->setPosY(getPosY());
// Actualiza la posición del circulo de colisión
updateColliders();
}
creation_counter_--;
}
// El contador ha llegado a cero
else {
creation_counter_s_ = std::max(0.0F, creation_counter_s_ - dt_s);
creation_counter_ = static_cast<Uint16>(creation_counter_s_ * 60.0F);
} else {
setBeingCreated(false);
setStop(false); // reactiva la rotació de la PowerBall si escau
setStop(false);
setVisible(true);
setInvulnerable(false);
}
}
// Rama de updateState: globo detenido (no creándose)
void Balloon::updateStateStopped() {
// Reduce el contador
if (stopped_counter_ > 0) {
stopped_counter_--;
}
// Quitarles el estado "detenido" si no estan explosionando
else if (!isPopping()) {
setStop(false); // reactiva la rotació de la PowerBall si escau
void Balloon::updateStateStopped(float dt_s) {
if (stopped_counter_s_ > 0.0F) {
stopped_counter_s_ = std::max(0.0F, stopped_counter_s_ - dt_s);
stopped_counter_ = static_cast<Uint16>(stopped_counter_s_ * 60.0F);
} else if (!isPopping()) {
setStop(false);
}
}
// Establece la animación correspondiente al estado
void Balloon::updateAnimation() {
void Balloon::updateAnimation(float dt_s) {
std::string creating_animation = "blue";
std::string normal_animation = "orange";
@@ -565,7 +468,6 @@ void Balloon::updateAnimation() {
normal_animation = "green";
}
// Establece el frame de animación
if (isPopping()) {
sprite_->setCurrentAnimation("pop");
} else if (isBeingCreated()) {
@@ -574,7 +476,7 @@ void Balloon::updateAnimation() {
sprite_->setCurrentAnimation(normal_animation);
}
sprite_->animate();
sprite_->animate(dt_s);
}
// Comprueba si el globo está habilitado
@@ -592,11 +494,6 @@ auto Balloon::getPosY() const -> float {
return pos_y_;
}
// Obtiene del valor de la variable
auto Balloon::getVelY() const -> float {
return vel_y_;
}
// Obtiene del valor de la variable
auto Balloon::getWidth() const -> int {
return width_;
@@ -607,9 +504,9 @@ auto Balloon::getHeight() const -> int {
return height_;
}
// Establece el valor de la variable
// Establece el valor de la variable (px/s)
void Balloon::setVelY(float vel_y) {
this->vel_y_ = vel_y;
this->vel_y_s_ = vel_y;
}
// Establece el valor de la variable
@@ -709,6 +606,7 @@ auto Balloon::isPopping() const -> bool {
// Establece el valor de la variable
void Balloon::setStoppedTimer(Uint16 time) {
stopped_counter_ = time;
stopped_counter_s_ = static_cast<float>(time) / 60.0F;
}
// Obtiene del valor de la variable
@@ -766,17 +664,27 @@ void Balloon::bounceStop() {
bouncing_.desp_y = 0.0F;
}
void Balloon::updateBounce() {
if (bouncing_.enabled) {
bouncing_.zoom_width = bouncing_.w[bouncing_.counter / bouncing_.speed];
bouncing_.zoom_height = bouncing_.h[bouncing_.counter / bouncing_.speed];
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));
void Balloon::updateBounce(float dt_s) {
if (!bouncing_.enabled) { return; }
bounce_phase_s_ += dt_s;
const float STEP_S = static_cast<float>(bouncing_.speed) * BOUNCE_STEP_S;
while (bounce_phase_s_ >= STEP_S) {
bounce_phase_s_ -= STEP_S;
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 HEXAGON_CLASS = 1;
// Velocidad del globo
static constexpr float VELX_POSITIVE = 0.7F;
static constexpr float VELX_NEGATIVE = -0.7F;
// Velocitat horitzontal en px/s (era 0.7 px/frame * 60).
static constexpr float VELX_POSITIVE = 42.0F;
static constexpr float VELX_NEGATIVE = -42.0F;
// Velocidades a las que se mueven los globos
static constexpr float SPEED_1 = 0.60F;
@@ -66,12 +66,12 @@ class Balloon {
Balloon(const Balloon &) = delete;
auto operator=(const Balloon &) -> Balloon & = delete;
void allignTo(int x); // Centra el globo en la posición X
void render(); // Pinta el globo en la pantalla
void move(); // Actualiza la posición y estados del globo
void disable(); // Deshabilita el globo y pone a cero todos los valores
void pop(); // Explosiona el globo
void update(); // Actualiza al globo a su posicion, animación y controla los contadores
void allignTo(int x); // Centra el globo en la posición X
void render(); // Pinta el globo en la pantalla
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 pop(); // Explosiona el globo
void update(float dt_s); // Actualiza al globo
[[nodiscard]] auto isEnabled() const -> bool; // Comprueba si el globo está habilitado
[[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 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 getHeight() 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
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
struct Bouncing {
bool enabled; // Si el efecto está activo
@@ -126,47 +130,50 @@ class Balloon {
AnimatedSprite *sprite_; // Sprite del objeto globo
// Variables
float pos_x_; // Posición en el eje X
float pos_y_; // Posición en el eje Y
Uint8 width_; // Ancho
Uint8 height_; // Alto
float vel_x_; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
float vel_y_; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
float gravity_; // Aceleración en el eje Y. Modifica la velocidad
float default_vel_y_; // Velocidad inicial que tienen al rebotar contra el suelo
float max_vel_y_; // Máxima velocidad que puede alcanzar el objeto en el eje Y
bool being_created_; // Indica si el globo se está creando
bool blinking_; // Indica si el globo está intermitente
bool enabled_; // Indica si el globo esta activo
bool invulnerable_; // Indica si el globo es invulnerable
bool popping_; // Indica si el globo está explotando
bool stopped_; // Indica si el globo está parado
bool visible_; // Indica si el globo es visible
Circle collider_; // Circulo de colisión del objeto
Uint16 creation_counter_; // Temporizador para controlar el estado "creandose"
Uint16 creation_counter_ini_; // Valor inicial para el temporizador para controlar el estado "creandose"
Uint16 score_; // Puntos que da el globo al ser destruido
Uint16 stopped_counter_; // Contador para controlar el estado "parado"
Uint8 kind_; // Tipo de globo
Uint8 menace_; // Cantidad de amenaza que genera el globo
Uint32 counter_; // Contador interno
float travel_y_; // Distancia que ha de recorrer el globo en el eje Y antes de que se le aplique la gravedad
float speed_; // Velocidad a la que se mueven los globos
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 pos_x_; // Posición en el eje X
float pos_y_; // Posición en el eje Y
Uint8 width_; // Ancho
Uint8 height_; // Alto
float vel_x_s_{0.0F}; // Velocidad en X (px/s)
float vel_y_s_{0.0F}; // Velocidad en Y (px/s)
float gravity_s_{0.0F}; // Aceleración Y (px/s²)
float default_vel_y_s_{0.0F}; // Velocitat inicial al rebotar (px/s)
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 blinking_; // Indica si el globo está intermitente
bool enabled_; // Indica si el globo esta activo
bool invulnerable_; // Indica si el globo es invulnerable
bool popping_; // Indica si el globo está explotando
bool stopped_; // Indica si el globo está parado
bool visible_; // Indica si el globo es visible
Circle collider_; // Circulo de colisión del objeto
Uint16 creation_counter_; // Temporizador (frames, derivat de creation_counter_s_ per a render alpha)
Uint16 creation_counter_ini_; // Valor inicial del temporizador (frames)
float creation_counter_s_{0.0F}; // Temporizador (font de veritat, segons)
float creation_counter_ini_s_{0.0F}; // Valor inicial del temporizador (segons)
float creation_phase_s_{0.0F}; // Acumulador de fase per als steps de creació
Uint16 score_; // Puntos que da el globo al ser destruido
Uint16 stopped_counter_; // Contador (frames, derivat de stopped_counter_s_)
float stopped_counter_s_{0.0F}; // Contador (font de veritat, segons)
Uint8 kind_; // Tipo de globo
Uint8 menace_; // Cantidad de amenaza que genera el globo
float speed_; // Tempo del joc (multiplicador adimensional)
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 bounceStart(); // Activa el efecto
void bounceStop(); // Detiene el efecto
void updateBounce(); // Aplica el efecto
void updateAnimation(); // Establece la animación correspondiente
void updateBounce(float dt_s); // Aplica el efecto
void updateAnimation(float dt_s); // Establece la animación correspondiente
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
void updateStatePopping();
void updateStateBeingCreated();
void updateStateStopped();
void updateStateBeingCreated(float dt_s);
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
pos_x_ = x;
pos_y_ = y;
pos_x_f_ = static_cast<float>(x);
pos_y_f_ = static_cast<float>(y);
// Alto y ancho del objeto
width_ = 10;
height_ = 10;
// Velocidad inicial en el eje Y
vel_y_ = -3;
vel_y_s_ = VEL_Y_PX_PER_S;
// Tipo de bala
this->kind_ = kind;
@@ -29,7 +31,7 @@ Bullet::Bullet(int x, int y, Bullet::Kind kind, bool powered_up, int owner, Text
switch (kind) {
case Bullet::Kind::UP:
// Establece la velocidad inicial
vel_x_ = 0;
vel_x_s_ = 0.0F;
// Rectangulo con los gráficos del objeto
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:
// Establece la velocidad inicial
vel_x_ = -2;
vel_x_s_ = VEL_X_LEFT_PX_PER_S;
// Rectangulo con los gráficos del objeto
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:
// Establece la velocidad inicial
vel_x_ = 2;
vel_x_s_ = VEL_X_RIGHT_PX_PER_S;
// Rectangulo con los gráficos del objeto
if (!powered_up) {
@@ -84,40 +86,29 @@ void Bullet::render() {
sprite_->render();
}
// Actualiza la posición y estado del objeto en horizontal
auto Bullet::move() -> MoveResult {
// Variable con el valor de retorno
// Actualiza la posición y estado del objeto
auto Bullet::move(float dt_s) -> MoveResult {
MoveResult msg = MoveResult::OK;
// Mueve el objeto a su nueva posición
pos_x_ += vel_x_;
pos_x_f_ += vel_x_s_ * dt_s;
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)) {
// Se deshabilita
kind_ = Bullet::Kind::NONE;
// Mensaje de salida
msg = MoveResult::OUT;
}
// Mueve el objeto a su nueva posición en vertical
pos_y_ += vel_y_;
pos_y_f_ += vel_y_s_ * dt_s;
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_) {
// Se deshabilita
kind_ = Bullet::Kind::NONE;
// Mensaje de salida
msg = MoveResult::OUT;
}
// Actualiza la posición del sprite
sprite_->setPosX(pos_x_);
sprite_->setPosY(pos_y_);
// Alinea el circulo de colisión con el objeto
shiftColliders();
return msg;
@@ -146,16 +137,13 @@ auto Bullet::getPosY() const -> int {
// Establece el valor de la variable
void Bullet::setPosX(int x) {
pos_x_ = x;
pos_x_f_ = static_cast<float>(x);
}
// Establece el valor de la variable
void Bullet::setPosY(int y) {
pos_y_ = y;
}
// Obtiene el valor de la variable
auto Bullet::getVelY() const -> int {
return vel_y_;
pos_y_f_ = static_cast<float>(y);
}
// Obtiene el valor de la variable
+19 -13
View File
@@ -32,14 +32,13 @@ class Bullet {
Bullet(const Bullet &) = delete;
auto operator=(const Bullet &) -> Bullet & = delete;
void render(); // Pinta el objeto en pantalla
auto move() -> MoveResult; // Actualiza la posición y estado del objeto
void disable(); // Deshabilita el objeto
void render(); // Pinta el objeto en pantalla
auto move(float dt_s) -> MoveResult; // Actualiza la posición y estado del objeto
void disable(); // Deshabilita el objeto
[[nodiscard]] auto isEnabled() const -> bool; // Comprueba si el objeto está habilitado
[[nodiscard]] auto getPosX() 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 getOwner() const -> int; // Obtiene el valor de la variable
@@ -49,19 +48,26 @@ class Bullet {
auto getCollider() -> Circle &; // Obtiene el circulo de colisión
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
Sprite *sprite_; // Sprite con los graficos y métodos de pintado
// Variables
int pos_x_; // Posición en el eje X
int pos_y_; // Posición en el eje Y
Uint8 width_; // Ancho del objeto
Uint8 height_; // Alto del objeto
int vel_x_; // Velocidad en el eje X
int vel_y_; // Velocidad en el eje Y
Kind kind_; // Tipo de objeto
int owner_; // Identificador del dueño del objeto
Circle collider_; // Circulo de colisión del objeto
int pos_x_; // Posición en el eje X (px enters per al sprite/collider)
int pos_y_; // Posición en el eje Y
float pos_x_f_{0}; // Acumulador subpíxel
float pos_y_f_{0}; // Acumulador subpíxel
Uint8 width_; // Ancho del objeto
Uint8 height_; // Alto del objeto
float vel_x_s_{0}; // Velocidad en el eje X (px/s)
float vel_y_s_{0}; // Velocidad en el eje Y (px/s)
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
};
+42 -42
View File
@@ -1,6 +1,7 @@
#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 "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;
enabled_ = true;
time_to_live_ = 600;
accel_x_ = 0.0F;
time_to_live_s_ = TIME_TO_LIVE_S;
accel_x_s_ = 0.0F;
floor_collision_ = false;
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;
pos_x_ = (((int)x + (PLAY_AREA_WIDTH / 2)) % (PLAY_AREA_WIDTH - width_ - 5)) + 2;
pos_y_ = PLAY_AREA_TOP - height_;
vel_x_ = 0.0F;
vel_y_ = -0.1F;
accel_y_ = 0.1F;
vel_x_s_ = 0.0F;
vel_y_s_ = COFFEE_VEL_Y_PX_PER_S;
accel_y_s_ = COFFEE_ACCEL_Y_PX_PER_S2;
collider_.r = 10;
} else {
width_ = 16;
height_ = 16;
pos_x_ = x;
pos_y_ = y;
vel_x_ = -1.0F + ((rand() % 5) * 0.5F);
vel_y_ = -4.0F;
accel_y_ = 0.2F;
// Distribució original: -1.0, -0.5, 0.0, 0.5, 1.0 px/frame ⇒ -60..60 px/s en passos de 30.
const int RAND_STEP = rand() % 5;
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;
}
@@ -74,49 +78,41 @@ void Item::render() {
}
// Actualiza la posición y estados del objeto
void Item::move() {
void Item::move(float dt_s) {
floor_collision_ = false;
// Calcula la nueva posición
pos_x_ += vel_x_;
pos_y_ += vel_y_;
// Posició
pos_x_ += vel_x_s_ * dt_s;
pos_y_ += vel_y_s_ * dt_s;
// Aplica las aceleraciones a la velocidad
vel_x_ += accel_x_;
vel_y_ += accel_y_;
// Acceleració
vel_x_s_ += accel_x_s_ * dt_s;
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)) {
// Corregir posición
pos_x_ -= vel_x_;
// Invertir sentido
vel_x_ = -vel_x_;
pos_x_ -= vel_x_s_ * dt_s;
vel_x_s_ = -vel_x_s_;
}
// 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)) {
// Corrige
pos_y_ -= vel_y_;
// Invierte el sentido
vel_y_ = -vel_y_;
pos_y_ -= vel_y_s_ * dt_s;
vel_y_s_ = -vel_y_s_;
}
// Si el objeto se sale por la parte inferior
// Topa amb el terra
if (pos_y_ + height_ > PLAY_AREA_BOTTOM) {
// Detiene el objeto
vel_y_ = 0;
vel_x_ = 0;
accel_x_ = 0;
accel_y_ = 0;
vel_y_s_ = 0;
vel_x_s_ = 0;
accel_x_s_ = 0;
accel_y_s_ = 0;
pos_y_ = PLAY_AREA_BOTTOM - height_;
if (id_ == Item::Id::COFFEE_MACHINE) {
floor_collision_ = true;
}
}
// Actualiza la posición del sprite
sprite_->setPosX(int(pos_x_));
sprite_->setPosY(int(pos_y_));
shiftColliders();
@@ -128,18 +124,22 @@ void Item::disable() {
}
// Actualiza el objeto a su posicion, animación y controla los contadores
void Item::update() {
move();
sprite_->animate();
updateTimeToLive();
void Item::update(float dt_s) {
move(dt_s);
sprite_->animate(dt_s);
updateTimeToLive(dt_s);
checkTimeToLive();
}
// Actualiza el contador
void Item::updateTimeToLive() {
if (time_to_live_ > 0) {
time_to_live_--;
// Actualiza el contador. Manté time_to_live_ (frames) sincronitzat amb el
// segons per a que render() segueixi funcionant amb la mateixa condició de
// parpelleig.
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
+32 -20
View File
@@ -29,12 +29,12 @@ class Item {
Item(const Item &) = delete;
auto operator=(const Item &) -> Item & = delete;
void allignTo(int x); // Centra el objeto en la posición X
void render(); // Pinta el objeto en la pantalla
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 updateTimeToLive(); // Actualiza el contador
void checkTimeToLive(); // Comprueba si el objeto sigue vivo
void allignTo(int x); // Centra el objeto en la posición X
void render(); // Pinta el objeto en la pantalla
void disable(); // Pone a cero todos los valores del objeto
void update(float dt_s); // Actualiza al objeto
void updateTimeToLive(float dt_s); // Actualiza el contador
void checkTimeToLive(); // Comprueba si el objeto sigue vivo
[[nodiscard]] auto getPosX() 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
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
AnimatedSprite *sprite_; // Sprite con los graficos del objeto
// Variables
float pos_x_; // Posición X del objeto
float pos_y_; // Posición Y del objeto
Uint8 width_; // Ancho del objeto
Uint8 height_; // Alto del objeto
float vel_x_; // Velocidad en el eje X
float vel_y_; // Velocidad en el eje Y
float accel_x_; // Aceleración en el eje X
float accel_y_; // Aceleración en el eje Y
bool floor_collision_; // Indica si el objeto colisiona con el suelo
Id id_; // Especifica el tipo de objeto que es
bool enabled_; // Especifica si el objeto está habilitado
Uint16 time_to_live_; // Temporizador con el tiempo que el objeto está presente
Circle collider_; // Circulo de colisión del objeto
float pos_x_; // Posición X del objeto
float pos_y_; // Posición Y del objeto
Uint8 width_; // Ancho del objeto
Uint8 height_; // Alto del objeto
float vel_x_s_{0.0F}; // Velocidad en el eje X (px/s)
float vel_y_s_{0.0F}; // Velocidad en el eje Y (px/s)
float accel_x_s_{0.0F}; // Aceleración en el eje X (px/s²)
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
Id id_; // Especifica el tipo de objeto que es
bool enabled_; // Especifica si el objeto está habilitado
Uint16 time_to_live_; // Temporizador (frames, derivat de time_to_live_s_ per a render())
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 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 <algorithm>
#include <cmath> // for fmod
#include <cstdlib> // for rand
#include "core/input/input.h" // for InputAction
@@ -42,12 +43,15 @@ void Player::init() {
// Inicializa variables de estado
alive_ = true;
death_counter_ = DEATH_COUNTER;
death_counter_s_ = DEATH_DURATION_S;
status_walking_ = STATUS_WALKING_STOP;
status_firing_ = STATUS_FIRING_NO;
invulnerable_ = false;
invulnerable_counter_ = INVULNERABLE_COUNTER;
invulnerable_counter_s_ = INVULNERABLE_DURATION_S;
power_up_ = false;
power_up_counter_ = POWERUP_COUNTER;
power_up_counter_s_ = POWERUP_DURATION_S;
extra_hit_ = false;
coffees_ = 0;
input_ = true;
@@ -63,11 +67,10 @@ void Player::init() {
shiftColliders();
// Establece la velocidad inicial
vel_x_ = 0;
vel_y_ = 0;
vel_x_s_ = 0.0F;
// Establece la velocidad base
base_speed_ = 1.5;
base_speed_s_ = BASE_SPEED_PX_PER_S;
// Establece la puntuación inicial
score_ = 0;
@@ -77,6 +80,7 @@ void Player::init() {
// Inicia el contador para la cadencia de disparo
cooldown_ = 10;
cooldown_s_ = COOLDOWN_S;
// Establece la posición del sprite
legs_sprite_->setPosX(pos_x_);
@@ -98,12 +102,12 @@ void Player::init() {
void Player::setInput(Input::Action input) {
switch (input) {
case Input::Action::LEFT:
vel_x_ = -base_speed_;
vel_x_s_ = -base_speed_s_;
setWalkingStatus(STATUS_WALKING_LEFT);
break;
case Input::Action::RIGHT:
vel_x_ = base_speed_;
vel_x_s_ = base_speed_s_;
setWalkingStatus(STATUS_WALKING_RIGHT);
break;
@@ -120,24 +124,21 @@ void Player::setInput(Input::Action input) {
break;
default:
vel_x_ = 0;
vel_x_s_ = 0.0F;
setWalkingStatus(STATUS_WALKING_STOP);
break;
}
}
// Mueve el jugador a la posición y animación que le corresponde
void Player::move() {
void Player::move(float dt_s) {
if (isAlive()) {
// Mueve el jugador a derecha o izquierda
pos_x_ += vel_x_;
pos_x_ += vel_x_s_ * dt_s;
// 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)) { // Restaura su posición
pos_x_ -= vel_x_;
if ((pos_x_ < PLAY_AREA_LEFT - 5) || (pos_x_ + width_ > PLAY_AREA_RIGHT + 5)) {
pos_x_ -= vel_x_s_ * dt_s;
}
// Actualiza la posición del sprite
legs_sprite_->setPosX(getPosX());
legs_sprite_->setPosY(pos_y_);
@@ -150,14 +151,11 @@ void Player::move() {
fire_sprite_->setPosX(getPosX() - 2);
fire_sprite_->setPosY(pos_y_ - 8);
} 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)) { // Restaura su posición
if ((death_sprite_->getPosX() < PLAY_AREA_LEFT) || (death_sprite_->getPosX() + width_ > PLAY_AREA_RIGHT)) {
const float VX = death_sprite_->getVelX();
death_sprite_->setPosX(death_sprite_->getPosX() - VX);
// Rebota
death_sprite_->setPosX(death_sprite_->getPosX() - (VX * dt_s));
death_sprite_->setVelX(-VX);
}
}
@@ -199,8 +197,7 @@ void Player::setFiringStatus(Uint8 status) {
}
// Establece la animación correspondiente al estado
void Player::setAnimation() {
// Crea cadenas de texto para componer el nombre de la animación
void Player::setAnimation(float dt_s) {
std::string body_coffees;
std::string head_coffees;
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_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_->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_->setFlip(FLIP_WALK);
head_sprite_->setCurrentAnimation(WALKING + head_coffees + POWER_UP);
head_sprite_->setFlip(FLIP_WALK);
} else { // Está disparando
} else {
body_sprite_->setCurrentAnimation(FIRING + body_coffees + POWER_UP);
body_sprite_->setFlip(FLIP_FIRE);
head_sprite_->setCurrentAnimation(FIRING + head_coffees + POWER_UP);
head_sprite_->setFlip(FLIP_FIRE);
}
// Actualiza las animaciones de los sprites
legs_sprite_->animate();
body_sprite_->animate();
head_sprite_->animate();
legs_sprite_->animate(dt_s);
body_sprite_->animate(dt_s);
head_sprite_->animate(dt_s);
fire_sprite_->animate();
fire_sprite_->animate(dt_s);
fire_sprite_->setFlip(FLIP_WALK);
}
@@ -268,30 +263,37 @@ auto Player::canFire() const -> bool {
// Establece el valor de la variable
void Player::setFireCooldown(int time) {
cooldown_ = time;
cooldown_s_ = static_cast<float>(time) / 60.0F;
}
// Actualiza el valor de la variable
void Player::updateCooldown() {
if (cooldown_ > 0) {
cooldown_--;
if (power_up_) {
cooldown_--;
}
// Establece el valor del cooldown en segons (time-based)
void Player::setFireCooldownS(float seconds) {
cooldown_s_ = seconds;
cooldown_ = static_cast<int>(seconds * 60.0F);
}
// 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 {
setFiringStatus(STATUS_FIRING_NO);
}
}
// Actualiza al jugador a su posicion, animación y controla los contadores
void Player::update() {
move();
setAnimation();
void Player::update(float dt_s) {
move(dt_s);
setAnimation(dt_s);
shiftColliders();
updateCooldown();
updatePowerUpCounter();
updateInvulnerableCounter();
updateDeathCounter();
updatePowerUpHeadOffset();
updateCooldown(dt_s);
updatePowerUpCounter(dt_s);
updateInvulnerableCounter(dt_s);
updateDeathCounter(dt_s);
updatePowerUpHeadOffset(dt_s);
}
// Obtiene la puntuación del jugador
@@ -321,11 +323,13 @@ void Player::setAlive(bool value) {
if (!value) {
death_sprite_->setPosX(head_sprite_->getRect().x);
death_sprite_->setPosY(head_sprite_->getRect().y);
death_sprite_->setAccelY(0.2F);
death_sprite_->setVelY(-6.6F);
death_sprite_->setVelX(3.3F);
// Física del cadàver en px/s i px/s² — Game crida Player::update(dt_s)
// que delega a death_sprite_->update(dt_s) (time-based).
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) {
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
void Player::setInvulnerableCounter(Uint16 value) {
invulnerable_counter_ = value;
invulnerable_counter_s_ = static_cast<float>(value) / 60.0F;
}
// Actualiza el valor de la variable
void Player::updateInvulnerableCounter() {
// Actualiza el contador d'invulnerabilitat. Manté el counter enter
// sincronitzat perquè render() segueixi parpellejant igual.
void Player::updateInvulnerableCounter(float dt_s) {
if (invulnerable_) {
if (invulnerable_counter_ > 0) {
invulnerable_counter_--;
if (invulnerable_counter_s_ > 0.0F) {
invulnerable_counter_s_ = std::max(0.0F, invulnerable_counter_s_ - dt_s);
invulnerable_counter_ = static_cast<Uint16>(invulnerable_counter_s_ * 60.0F);
} else {
invulnerable_ = false;
invulnerable_counter_ = INVULNERABLE_COUNTER;
invulnerable_counter_s_ = INVULNERABLE_DURATION_S;
}
}
}
// Actualiza el valor de la variable
void Player::updateDeathCounter() {
// Actualiza el comptador de mort
void Player::updateDeathCounter(float dt_s) {
if (!alive_) {
if (death_counter_ > 0) {
death_counter_--;
if (death_counter_s_ > 0.0F) {
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
void Player::setPowerUpCounter(Uint16 value) {
power_up_counter_ = value;
power_up_counter_s_ = static_cast<float>(value) / 60.0F;
}
// Actualiza el valor de la variable
void Player::updatePowerUpCounter() {
if ((power_up_counter_ > 0) && (power_up_)) {
power_up_counter_--;
// Actualiza el comptador de PowerUp
void Player::updatePowerUpCounter(float dt_s) {
if ((power_up_counter_s_ > 0.0F) && (power_up_)) {
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 {
power_up_ = false;
power_up_counter_ = POWERUP_COUNTER;
power_up_counter_s_ = POWERUP_DURATION_S;
}
}
@@ -451,6 +463,7 @@ void Player::removeExtraHit() {
}
invulnerable_ = true;
invulnerable_counter_ = INVULNERABLE_COUNTER;
invulnerable_counter_s_ = INVULNERABLE_DURATION_S;
}
// Habilita la entrada de ordenes
@@ -490,21 +503,13 @@ auto Player::getDeathCounter() const -> Uint16 {
return death_counter_;
}
// Actualiza el valor de la variable
void Player::updatePowerUpHeadOffset() {
if (!power_up_) {
// powerUpHeadOffset = 0;
} else {
// powerUpHeadOffset = 96;
if (power_up_counter_ < 300) {
if (power_up_counter_ % 10 > 4) {
// powerUpHeadOffset = 96;
fire_sprite_->setEnabled(false);
} else {
// powerUpHeadOffset = 0;
fire_sprite_->setEnabled(true);
}
}
// Actualiza l'offset. dt_s no s'usa directament: el blink final depèn de
// power_up_counter_s_ que ja s'està actualitzant a updatePowerUpCounter.
void Player::updatePowerUpHeadOffset([[maybe_unused]] float dt_s) {
if (!power_up_) { return; }
if (power_up_counter_s_ < POWERUP_BLINK_THRESHOLD_S) {
const float PHASE = std::fmod(power_up_counter_s_, BLINK_PERIOD_S);
fire_sprite_->setEnabled(PHASE <= BLINK_OFF_S);
}
}
+45 -27
View File
@@ -5,8 +5,8 @@
#include <string> // for string
#include <vector> // for vector
#include "utils/utils.h" // for Circle
#include "core/input/input.h" // for Input::Action
#include "utils/utils.h" // for Circle
class AnimatedSprite;
class Texture;
@@ -21,14 +21,14 @@ class Player {
Player(const Player &) = delete;
auto operator=(const Player &) -> Player & = delete;
void init(); // Iniciador
void update(); // Actualiza al jugador a su posicion, animación y controla los contadores
void render(); // Pinta el jugador en pantalla
void move(); // Mueve el jugador a la posición y animación que le corresponde
void init(); // Iniciador
void update(float dt_s); // Actualiza al jugador
void render(); // Pinta el jugador en pantalla
void move(float dt_s); // Mueve el 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 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 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 canFire() const -> bool; // Indica si el jugador puede disparar
void setFireCooldown(int time); // Establece el valor de la variable
void updateCooldown(); // Actualiza el valor de la variable
void setFireCooldown(int time); // Establece el valor de la variable (frames)
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
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
[[nodiscard]] auto getPowerUpCounter() const -> Uint16; // Obtiene 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
void giveExtraHit(); // Concede un toque extra al jugador
@@ -89,6 +90,20 @@ class Player {
static constexpr int INVULNERABLE_COUNTER = 200;
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
SDL_Renderer *renderer_; // El renderizador de la ventana
AnimatedSprite *head_sprite_; // Sprite para dibujar la cabeza
@@ -104,11 +119,11 @@ class Player {
Uint8 width_; // Anchura
Uint8 height_; // Altura
float vel_x_; // Cantidad de pixeles a desplazarse en el eje X
int vel_y_; // Cantidad de pixeles a desplazarse en el eje Y
float vel_x_s_{0.0F}; // Velocidad en el eje X (px/s)
float base_speed_; // Velocidad base del jugador
int cooldown_; // Contador durante el cual no puede disparar
float base_speed_s_{0.0F}; // Velocidad base del jugador (px/s)
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
float score_multiplier_; // Multiplicador de puntos
@@ -116,22 +131,25 @@ class Player {
Uint8 status_walking_; // Estado del jugador
Uint8 status_firing_; // Estado del jugador
bool alive_; // Indica si el jugador está vivo
Uint16 death_counter_; // Contador para la animación de morirse
bool invulnerable_; // Indica si el jugador es invulnerable
Uint16 invulnerable_counter_; // Contador para la invulnerabilidad
bool extra_hit_; // Indica si el jugador tiene un toque extra
Uint8 coffees_; // Indica cuantos cafes lleva acumulados
bool power_up_; // Indica si el jugador tiene activo el modo PowerUp
Uint16 power_up_counter_; // Temporizador para el modo PowerUp
bool input_; // Indica si puede recibir ordenes de entrada
Circle collider_; // Circulo de colisión del jugador
bool alive_; // Indica si el jugador está vivo
Uint16 death_counter_; // Contador (frame-based)
float death_counter_s_{0.0F}; // Contador (time-based)
bool invulnerable_; // Indica si el jugador es invulnerable
Uint16 invulnerable_counter_; // Contador (frame-based)
float invulnerable_counter_s_{0.0F}; // Contador (time-based)
bool extra_hit_; // Indica si el jugador tiene un toque extra
Uint8 coffees_; // Indica cuantos cafes lleva acumulados
bool power_up_; // Indica si el jugador tiene activo el modo PowerUp
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 setFiringStatus(Uint8 status); // Establece el estado 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 updateDeathCounter(); // Actualiza el valor de la variable
void updatePowerUpHeadOffset(); // Actualiza el valor de la variable
void shiftColliders(); // Actualiza el circulo de colisión a la posición del jugador
void updateInvulnerableCounter(float dt_s); // Actualiza el valor de la variable
void updateDeathCounter(float dt_s); // 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/resources/asset.h" // for Asset
#include "core/resources/resource.h"
#include "game/defaults.hpp" // for PLAY_AREA_CENTER_X, BLOCK, PLAY_AREA_CEN...
#include "game/entities/balloon.h" // for Balloon, Balloon::VELX_NEGATIVE, BALLOON_...
#include "game/entities/bullet.h" // for Bullet, Bullet::Kind::LEFT, Bullet::Kind::RIGHT, BULLE...
#include "game/entities/item.h" // for Item
#include "game/entities/player.h" // for Player
#include "game/options.hpp" // for Options
#include "game/ui/menu.h" // for Menu
#include "core/system/delta_time.hpp" // for DeltaTime
#include "game/defaults.hpp" // for PLAY_AREA_CENTER_X, BLOCK, PLAY_AREA_CEN...
#include "game/entities/balloon.h" // for Balloon, Balloon::VELX_NEGATIVE, BALLOON_...
#include "game/entities/bullet.h" // for Bullet, Bullet::Kind::LEFT, Bullet::Kind::RIGHT, BULLE...
#include "game/entities/item.h" // for Item
#include "game/entities/player.h" // for Player
#include "game/options.hpp" // for Options
#include "game/ui/menu.h" // for Menu
namespace Ja {
struct Sound;
} // 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'
init();
// Reset del rellotge perquè el primer dt_s no inclogui el temps de càrrega.
DeltaTime::reset();
}
Game::~Game() {
@@ -146,9 +150,6 @@ Game::~Game() {
// Inicializa las variables necesarias para la sección 'Game'
void Game::init() {
ticks_ = 0;
ticks_speed_ = 15;
// Elimina qualquier jugador que hubiese antes de crear los nuevos
for (auto *player : players_) {
delete player;
@@ -200,18 +201,27 @@ void Game::init() {
game_completed_ = false;
game_completed_counter_ = 0;
game_completed_counter_s_ = 0.0F;
section_->name = SECTION_PROG_GAME;
section_->subsection = SUBSECTION_GAME_PLAY_1P;
menace_current_ = 0;
menace_threshold_ = 0;
hi_score_achieved_ = false;
stage_bitmap_counter_ = STAGE_COUNTER;
stage_bitmap_counter_s_ = STAGE_COUNTER / 60.0F;
death_counter_ = Player::DEATH_COUNTER;
death_counter_s_ = Player::DEATH_COUNTER / 60.0F;
time_stopped_ = false;
time_stopped_counter_ = 0;
time_stopped_counter_s_ = 0.0F;
counter_ = 0;
elapsed_s_ = 0.0F;
last_enemy_deploy_ = 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_;
effect_.flash = false;
effect_.shake = false;
@@ -284,15 +294,15 @@ void Game::init() {
// Con los globos creados, calcula el nivel de amenaza
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_->setPosY(0);
n1000_sprite_->setWidth(26);
n1000_sprite_->setHeight(9);
n1000_sprite_->setVelX(0.0F);
n1000_sprite_->setVelY(-0.5F);
n1000_sprite_->setVelY(-30.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_->setEnabled(false);
n1000_sprite_->setEnabledCounter(0);
@@ -305,9 +315,9 @@ void Game::init() {
n2500_sprite_->setWidth(28);
n2500_sprite_->setHeight(9);
n2500_sprite_->setVelX(0.0F);
n2500_sprite_->setVelY(-0.5F);
n2500_sprite_->setVelY(-30.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_->setEnabled(false);
n2500_sprite_->setEnabledCounter(0);
@@ -320,9 +330,9 @@ void Game::init() {
n5000_sprite_->setWidth(28);
n5000_sprite_->setHeight(9);
n5000_sprite_->setVelX(0.0F);
n5000_sprite_->setVelY(-0.5F);
n5000_sprite_->setVelY(-30.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_->setEnabled(false);
n5000_sprite_->setEnabledCounter(0);
@@ -1429,11 +1439,10 @@ void Game::renderScoreBoard() {
}
// Actualiza las variables del jugador
void Game::updatePlayers() {
void Game::updatePlayers(float dt_s) {
for (auto *player : players_) {
player->update();
player->update(dt_s);
// Comprueba la colisión entre el jugador y los globos
if (checkPlayerBalloonCollision(player)) {
if (player->isAlive()) {
if (demo_.enabled) {
@@ -1445,7 +1454,6 @@ void Game::updatePlayers() {
}
}
// Comprueba las colisiones entre el jugador y los items
checkPlayerItemCollision(player);
}
}
@@ -1458,19 +1466,18 @@ void Game::renderPlayers() {
}
// 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) {
// Cambio de fase
current_stage_++;
last_stage_reached_ = current_stage_;
if (current_stage_ == 10) { // Ha llegado al final el juego
game_completed_ = true; // Marca el juego como completado
current_stage_ = 9; // Deja el valor dentro de los limites
stage_[current_stage_].current_power = 0; // Deja el poder a cero para que no vuelva a entrar en esta condición
destroyAllBalloons(); // Destruye a todos los enemigos
stage_[current_stage_].current_power = 0; // Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos lo globos
menace_current_ = 255; // Sube el nivel de amenaza para que no cree mas globos
for (auto *player : players_) { // Añade un millon de puntos a los jugadores que queden vivos
if (current_stage_ == 10) {
game_completed_ = true;
current_stage_ = 9;
stage_[current_stage_].current_power = 0;
destroyAllBalloons();
stage_[current_stage_].current_power = 0;
menace_current_ = 255;
for (auto *player : players_) {
if (player->isAlive()) {
player->addScore(1000000);
}
@@ -1480,45 +1487,52 @@ void Game::updateStage() {
}
Audio::get()->playSound(stage_change_sound_);
stage_bitmap_counter_ = 0;
stage_bitmap_counter_s_ = 0.0F;
enemy_speed_ = default_enemy_speed_;
setBalloonSpeed(enemy_speed_);
effect_.flash = true;
effect_.shake = true;
}
// Incrementa el contador del bitmap que aparece mostrando el cambio de fase
if (stage_bitmap_counter_ < STAGE_COUNTER) {
stage_bitmap_counter_++;
if (stage_bitmap_counter_s_ < (STAGE_COUNTER / 60.0F)) {
stage_bitmap_counter_s_ += dt_s;
}
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_) {
stage_bitmap_counter_ = std::min<int>(stage_bitmap_counter_, 100);
}
}
// Actualiza el estado de muerte
void Game::updateDeath() {
// Comprueba si todos los jugadores estan muertos
// Actualiza el estado de muerte. Detecta el creuament dels llindars 250/200/180/120/60
// (en frames) per a reproduir els bubbles als mateixos moments.
void Game::updateDeath(float dt_s) {
bool all_dead = true;
for (const auto *player : players_) {
all_dead &= (!player->isAlive());
}
if (all_dead) {
if (death_counter_ > 0) {
death_counter_--;
if (!all_dead) { return; }
if ((death_counter_ == 250) || (death_counter_ == 200) || (death_counter_ == 180) || (death_counter_ == 120) || (death_counter_ == 60)) {
// Hace sonar aleatoriamente uno de los 4 sonidos de burbujas
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]);
}
}
} else {
section_->subsection = SUBSECTION_GAME_GAMEOVER;
if (death_counter_s_ <= 0.0F) {
section_->subsection = SUBSECTION_GAME_GAMEOVER;
return;
}
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);
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
void Game::updateBalloons() {
void Game::updateBalloons(float dt_s) {
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);
balloons_[INDEX]->allignTo(balloon->getPosX() + (balloon->getWidth() / 2));
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 {
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);
balloons_[INDEX2]->allignTo(balloon->getPosX() + (balloon->getWidth() / 2));
if (balloons_[INDEX2]->getClass() == Balloon::BALLOON_CLASS) {
balloons_[INDEX2]->setVelY(-2.50F);
balloons_[INDEX2]->setVelY(-150.0F);
} else {
balloons_[INDEX2]->setVelY(Balloon::VELX_NEGATIVE);
}
@@ -1904,10 +1918,10 @@ void Game::resolveBulletBalloonHit(Bullet *bullet, Balloon *balloon) {
}
// Mueve las balas activas
void Game::moveBullets() {
void Game::moveBullets(float dt_s) {
for (auto *bullet : bullets_) {
if (bullet->isEnabled()) {
if (bullet->move() == Bullet::MoveResult::OUT) {
if (bullet->move(dt_s) == Bullet::MoveResult::OUT) {
players_[bullet->getOwner()]->decScoreMultiplier();
}
}
@@ -1942,10 +1956,10 @@ void Game::freeBullets() {
}
// Actualiza los items
void Game::updateItems() {
void Game::updateItems(float dt_s) {
for (auto *item : items_) {
if (item->isEnabled()) {
item->update();
item->update(dt_s);
if (item->isOnFloor()) {
Audio::get()->playSound(coffee_machine_sound_);
effect_.shake = true;
@@ -2067,14 +2081,26 @@ void Game::renderFlashEffect() {
}
}
// Actualiza el efecto de agitar la pantalla
void Game::updateShakeEffect() {
if (effect_.shake) {
// Actualiza el efecto de agitar la pantalla. Decrementa `shake_counter` a
// cadència fixa de 60Hz amb un acumulador de fase (independent del framerate)
// — 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) {
effect_.shake_counter--;
} else {
effect_.shake = false;
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->setWidth(16);
ss->setHeight(16);
ss->setVelX(-1.0F + ((rand() % 5) * 0.5F));
ss->setVelY(-4.0F);
// Conversió a px/s i px/s² (era -1..1 px/frame i 0.2 px/frame²)
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->setAccelY(0.2F);
ss->setAccelY(720.0F);
ss->setDestX(x + (ss->getVelX() * 50));
ss->setDestY(GAMECANVAS_HEIGHT + 1);
ss->setEnabled(true);
@@ -2103,9 +2131,9 @@ void Game::throwCoffee(int x, int y) {
}
// Actualiza los SmartSprites
void Game::updateSmartSprites() {
void Game::updateSmartSprites(float dt_s) {
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
void Game::setTimeStoppedCounter(Uint16 value) {
time_stopped_counter_ = value;
time_stopped_counter_s_ = static_cast<float>(value) / 60.0F;
}
// Incrementa el valor de la variable
void Game::incTimeStoppedCounter(Uint16 value) {
time_stopped_counter_ += value;
time_stopped_counter_s_ += static_cast<float>(value) / 60.0F;
}
// Actualiza y comprueba el valor de la variable
void Game::updateTimeStoppedCounter() {
if (isTimeStopped()) {
if (time_stopped_counter_ > 0) {
time_stopped_counter_--;
stopAllBalloons(TIME_STOPPED_COUNTER);
} else {
disableTimeStopItem();
}
void Game::updateTimeStoppedCounter(float dt_s) {
if (!isTimeStopped()) { return; }
if (time_stopped_counter_s_ > 0.0F) {
time_stopped_counter_s_ = std::max(0.0F, time_stopped_counter_s_ - dt_s);
time_stopped_counter_ = static_cast<Uint16>(time_stopped_counter_s_ * 60.0F);
stopAllBalloons(TIME_STOPPED_COUNTER);
} else {
disableTimeStopItem();
}
}
// Actualiza la variable enemyDeployCounter
void Game::updateEnemyDeployCounter() {
if (enemy_deploy_counter_ > 0) {
// Actualiza la variable enemyDeployCounter. Decrementa a 60Hz fixe amb
// acumulador de fase — és un comptador discret consultat per
// `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_--;
}
}
// Actualiza el juego
void Game::update() {
// Actualiza el audio
// Actualiza el juego. La cadència la dicta dt_s, propagat des de iterate()
// via DeltaTime::tick(). El comptador global `counter_` queda derivat de
// `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();
// Actualiza los efectos basados en tiempo real (no en el throttle del juego)
updateDeathShake();
updateDeathSequence();
// Durante la secuencia de muerte, congela el resto del juego
if (death_sequence_.phase == DeathPhase::SHAKING || death_sequence_.phase == DeathPhase::WAITING) {
return;
}
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
if (SDL_GetTicks() - ticks_ > ticks_speed_) {
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
// Acumulador i derivació del comptador legacy
elapsed_s_ += dt_s;
counter_ = static_cast<Uint32>(elapsed_s_ * 60.0F);
// Actualiza el contador de juego
counter_++;
checkGameInput();
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
checkGameInput();
// Actualiza las variables del jugador
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();
}
freeBullets();
freeBalloons();
freeItems();
freeSmartSprites();
}
// Actualiza el fondo
void Game::updateBackground() {
if (!game_completed_) { // Si el juego no esta completo, la velocidad de las nubes es igual a los globos explotados
// Actualiza el fondo. Velocitats dels núvols expressades com a px/s.
void Game::updateBackground(float dt_s) {
if (!game_completed_) {
clouds_speed_ = balloons_popped_;
} else { // Si el juego está completado, se reduce la velocidad de las nubes
} else {
if (clouds_speed_ > 400) {
clouds_speed_ -= 25;
} 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
const float SPEED = (-0.2F) + (-3.00F * ((float)clouds_speed_ / (float)total_power_to_complete_game_));
// Velocitat per frame (mateixa fórmula); en time-based la passem com a px/s.
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);
clouds1_b_->setVelX(SPEED);
clouds2_a_->setVelX(SPEED / 2);
clouds2_b_->setVelX(SPEED / 2);
clouds1_a_->setVelX(SPEED_PX_PER_S);
clouds1_b_->setVelX(SPEED_PX_PER_S);
clouds2_a_->setVelX(SPEED_PX_PER_S / 2.0F);
clouds2_b_->setVelX(SPEED_PX_PER_S / 2.0F);
// Mueve las nubes
clouds1_a_->move();
clouds1_b_->move();
clouds2_a_->move();
clouds2_b_->move();
clouds1_a_->move(dt_s);
clouds1_b_->move(dt_s);
clouds2_a_->move(dt_s);
clouds2_b_->move(dt_s);
// 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()) { clouds1_a_->setPosX(clouds1_a_->getWidth()); }
if (clouds1_b_->getPosX() < -clouds1_b_->getWidth()) { 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()); }
if (clouds1_b_->getPosX() < -clouds1_b_->getWidth()) {
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
// Herba: `counter_` derivat de `elapsed_s_*60` ja oscil·la a 60Hz.
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) {
const int V[] = {-1, 1, -1, 1, -1, 1, -1, 0};
buildings_sprite_->setPosX(V[death_shake_.step]);
@@ -2679,55 +2666,62 @@ auto Game::isDeathShaking() const -> bool {
// Ejecuta un frame del juego
void Game::iterate() {
// En modo demo, no hay pausa ni game over
if (demo_.enabled) {
if (section_->subsection == SUBSECTION_GAME_PAUSE || section_->subsection == SUBSECTION_GAME_GAMEOVER) {
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_INSTRUCTIONS;
return;
}
// Consum del temps real des de l'última iteració. Sempre s'ha de cridar
// perquè el rellotge no acumuli a través de transicions/sub-estats.
const float DELTA_TIME_S = DeltaTime::tick();
// En modo demo, ni pause ni game over: torna immediatament al títol
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
if (section_->subsection == SUBSECTION_GAME_PAUSE) {
if (!pause_initialized_) {
enterPausedGame();
}
updatePausedGame();
renderPausedGame();
switch (section_->subsection) {
case SUBSECTION_GAME_PAUSE:
iteratePaused(DELTA_TIME_S);
break;
case SUBSECTION_GAME_GAMEOVER:
iterateGameOver(DELTA_TIME_S);
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
else if (section_->subsection == SUBSECTION_GAME_GAMEOVER) {
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();
}
update(dt_s);
render();
}
// Indica si el juego ha terminado
@@ -2765,54 +2759,50 @@ void Game::run() {
}
}
// Actualiza las variables del menu de pausa del juego
void Game::updatePausedGame() {
if (SDL_GetTicks() - ticks_ <= ticks_speed_) {
return;
}
ticks_ = SDL_GetTicks();
// Atalls globals (zoom finestra, fullscreen, shaders, presets, ...)
// Actualiza el menu de pausa
void Game::updatePausedGame(float dt_s) {
GlobalInputs::handle();
if (leaving_pause_menu_) {
updateLeavingPauseMenu();
updateLeavingPauseMenu(dt_s);
} else {
updatePauseMenuUI();
updatePauseMenuUI(dt_s);
}
}
// Rama de updatePausedGame: cuenta atrás de salida y vuelta al juego
void Game::updateLeavingPauseMenu() {
if (pause_counter_ > 0) { // El contador está descendiendo
const bool A = pause_counter_ == 90;
const bool B = pause_counter_ == 60;
const bool C = pause_counter_ == 30;
if (A || B || C) {
Audio::get()->playSound(clock_sound_);
// Rama de updatePausedGame: cuenta atrás de salida y vuelta al juego.
// Decrementa pause_counter_ a 60Hz exactes amb un acumulador de fase per a
// mantenir els sons de rellotge als llindars originals (90, 60, 30 frames =
// 1.5s, 1.0s, 0.5s restants).
void Game::updateLeavingPauseMenu(float dt_s) {
if (pause_counter_ <= 0) {
section_->name = SECTION_PROG_GAME;
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;
}
// Ha finalizado el contador
section_->name = SECTION_PROG_GAME;
section_->subsection = num_players_ == 1 ? SUBSECTION_GAME_PLAY_1P : SUBSECTION_GAME_PLAY_2P;
if (Audio::getRealMusicState() == Audio::MusicState::PAUSED) {
Audio::get()->resumeMusic();
constexpr float STEP_S = 1.0F / 60.0F;
pause_counter_phase_s_ += dt_s;
while (pause_counter_phase_s_ >= STEP_S && pause_counter_ > 0) {
pause_counter_phase_s_ -= STEP_S;
if (pause_counter_ == 90 || pause_counter_ == 60 || pause_counter_ == 30) {
Audio::get()->playSound(clock_sound_);
}
pause_counter_--;
}
}
// Rama de updatePausedGame: lógica del menú de pausa
void Game::updatePauseMenuUI() {
// Lógica del menú de pausa (time-based)
void Game::updatePauseMenuUI(float dt_s) {
pause_menu_->update();
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)) {
leaving_pause_menu_ = true;
pause_counter_phase_s_ = 0.0F;
if (!Options::gameplay.pause_countdown) {
pause_counter_ = 0;
}
@@ -2822,21 +2812,20 @@ void Game::updatePauseMenuUI() {
switch (pause_menu_->getItemSelected()) {
case 1:
leaving_pause_menu_ = true;
pause_counter_phase_s_ = 0.0F;
if (!Options::gameplay.pause_countdown) {
pause_counter_ = 0; // salta el compte enrere de 3 segons
pause_counter_ = 0;
}
break;
case 2:
fade_->setFadeType(Fade::Type::CENTER);
fade_->activateFade();
break;
default:
break;
}
fade_->update();
fade_->update(dt_s);
if (fade_->hasEnded()) {
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_1;
@@ -2904,60 +2893,42 @@ void Game::enterPausedGame() {
}
// Actualiza los elementos de la pantalla de game over
void Game::updateGameOverScreen() {
// Calcula la lógica de los objetos
if (SDL_GetTicks() - ticks_ > ticks_speed_) {
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
void Game::updateGameOverScreen(float dt_s) {
GlobalInputs::handle();
// Atalls globals (zoom finestra, fullscreen, shaders, presets, ...)
GlobalInputs::handle();
game_over_menu_->update();
fade_->update(dt_s);
// Actualiza la lógica del menu
game_over_menu_->update();
// Actualiza el fade
fade_->update();
// Si ha terminado el fade, actua segun se haya operado
if (fade_->hasEnded()) {
switch (game_over_post_fade_) {
case 0: // YES
section_->name = SECTION_PROG_GAME;
deleteAllVectorObjects();
init();
section_->subsection = num_players_ == 1 ? SUBSECTION_GAME_PLAY_1P : SUBSECTION_GAME_PLAY_2P;
break;
case 1: // NO
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_1;
break;
default:
break;
}
if (fade_->hasEnded()) {
switch (game_over_post_fade_) {
case 0: // YES
section_->name = SECTION_PROG_GAME;
deleteAllVectorObjects();
init();
section_->subsection = num_players_ == 1 ? SUBSECTION_GAME_PLAY_1P : SUBSECTION_GAME_PLAY_2P;
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_) {
game_over_menu_->checkInput();
// Comprueba si se ha seleccionado algún item del menú
switch (game_over_menu_->getItemSelected()) {
case 0: // YES
game_over_post_fade_ = 0;
fade_->activateFade();
break;
case 1: // NO
game_over_post_fade_ = 1;
fade_->activateFade();
break;
default:
break;
}
if (!game_completed_) {
game_over_menu_->checkInput();
switch (game_over_menu_->getItemSelected()) {
case 0: // YES
game_over_post_fade_ = 0;
fade_->activateFade();
break;
case 1: // NO
game_over_post_fade_ = 1;
fade_->activateFade();
break;
default:
break;
}
}
}
@@ -3099,23 +3070,24 @@ void Game::initPaths() {
}
// Actualiza el tramo final de juego, una vez completado
void Game::updateGameCompleted() {
if (game_completed_) {
game_completed_counter_++;
}
void Game::updateGameCompleted(float dt_s) {
if (!game_completed_) { return; }
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;
}
}
// Actualiza las variables de ayuda
void Game::updateHelper() {
// Solo ofrece ayuda cuando la amenaza es elevada
// Actualiza las variables de ayuda. De moment cap timer real dins helper_;
// el dt_s queda reservat per a futurs comptadors d'helper.
void Game::updateHelper([[maybe_unused]] float dt_s) {
if (menace_current_ > 15) {
for (const auto *player : players_) {
helper_.need_coffee = player->getCoffees() == 0;
helper_.need_coffee_machine = !player->isPowerUp();
}
} else {
+53 -40
View File
@@ -38,6 +38,11 @@ class Game {
void handleEvent(const SDL_Event *event); // Procesa un evento
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
static constexpr int TOTAL_SCORE_DATA = 3;
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
};
void update(); // Actualiza el juego
void render(); // Dibuja el juego
void init(); // Inicializa las variables necesarias para la sección 'Game'
void loadMedia(); // Carga los recursos necesarios para la sección 'Game'
void update(float dt_s); // Actualiza el juego
void render(); // Dibuja el juego
void init(); // Inicializa las variables necesarias 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 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
void renderScoreBoard(); // Pinta el marcador en pantalla usando un objeto texto
void updatePlayers(); // Actualiza las variables del jugador
void renderPlayers(); // Dibuja a los jugadores
void updatePlayers(float dt_s); // Actualiza las variables del jugador
void renderPlayers(); // Dibuja a los jugadores
void updateStage(); // Actualiza las variables de la fase
void updateDeath(); // Actualiza el estado de muerte
void updateStage(float dt_s); // Actualiza las variables de la fase
void updateDeath(float dt_s); // Actualiza el estado de muerte
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
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
@@ -193,12 +198,12 @@ class Game {
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 moveBullets(); // Mueve las balas activas
void moveBullets(float dt_s); // Mueve 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 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
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
@@ -207,11 +212,11 @@ class Game {
void createItemScoreSprite(int x, int y, const SmartSprite *sprite); // Crea un objeto SmartSprite
void freeSmartSprites(); // Vacia el vector de smartsprites
void renderFlashEffect(); // Dibuja el efecto de flash
void updateShakeEffect(); // 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 updateSmartSprites(); // Actualiza los SmartSprites
void renderSmartSprites(); // Pinta los SmartSprites activos
void renderFlashEffect(); // Dibuja el efecto de flash
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 updateSmartSprites(float dt_s); // Actualiza los SmartSprites
void renderSmartSprites(); // Pinta los SmartSprites activos
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
@@ -222,11 +227,11 @@ class Game {
void setTimeStoppedCounter(Uint16 value); // Establece el valor de la variable
void incTimeStoppedCounter(Uint16 value); // Incrementa el valor de la variable
void updateEnemyDeployCounter(); // Actualiza la variable EnemyDeployCounter
void updateTimeStoppedCounter(); // Actualiza y comprueba el valor de la variable
void updateMenace(); // Gestiona el nivel de amenaza
void updateBackground(); // Actualiza el fondo
void renderBackground(); // Dibuja el fondo
void updateEnemyDeployCounter(float dt_s); // Actualiza la variable EnemyDeployCounter
void updateTimeStoppedCounter(float dt_s); // Actualiza y comprueba el valor de la variable
void updateMenace(); // Gestiona el nivel de amenaza
void updateBackground(float dt_s); // Actualiza el fondo
void renderBackground(); // Dibuja el fondo
void checkGameInput(); // Gestiona la entrada durante el juego
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
void updateDeathSequence(); // Actualiza la secuencia de muerte del jugador
void updatePausedGame(); // Actualiza las variables del menu de pausa del juego
void updateLeavingPauseMenu(); // Helper de updatePausedGame
void updatePauseMenuUI(); // Helper de updatePausedGame
void renderPausedGame(); // Dibuja el menu de pausa del juego
void enterPausedGame(); // Inicializa el estado de pausa del juego
void updatePausedGame(float dt_s); // Actualiza el menu de pausa
void updateLeavingPauseMenu(float dt_s); // Helper
void updatePauseMenuUI(float dt_s); // Helper
void renderPausedGame(); // Dibuja el menu 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 renderGameOverScreen(); // Dibuja los elementos de la pantalla de game over
void enterGameOverScreen(); // Inicializa el estado de game over
void updateGameOverScreen(float dt_s); // Actualiza game over
void renderGameOverScreen(); // Dibuja los elementos de la pantalla de game over
void enterGameOverScreen(); // Inicializa el estado de game over
auto canPowerBallBeCreated() -> bool; // Indica si se puede crear una powerball
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 updateGameCompleted(); // Actualiza el tramo final de juego, una vez completado
void updateHelper(); // Actualiza las variables de ayuda
void updateGameCompleted(float dt_s); // Actualiza el tramo final de juego
void updateHelper(float dt_s); // Actualiza las variables de ayuda
auto allPlayersAreDead() -> bool; // Comprueba si todos los jugadores han muerto
void deleteAllVectorObjects(); // Elimina todos los objetos contenidos en vectores
void setHiScore(); // Establece la máxima puntuación desde fichero o desde las puntuaciones online
@@ -337,30 +342,36 @@ class Game {
// Variables
int num_players_; // Numero de jugadores
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa
Uint8 ticks_speed_; // Velocidad a la que se repiten los bucles del programa
float elapsed_s_{0.0F}; // Acumulador global de temps de joc
Uint32 hi_score_; // 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
Stage stage_[10]; // Variable con los datos de cada pantalla
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 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_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
Uint16 time_stopped_counter_; // Temporizador para llevar la cuenta del tiempo detenido
Uint32 counter_; // Contador para el juego
Uint16 time_stopped_counter_; // Temporizador (frame-based)
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
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
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 default_enemy_speed_; // Velocidad base de los enemigos, sin incrementar
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
DeathSequence death_sequence_; // Variable para gestionar la secuencia de muerte
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
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
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
float difficulty_score_multiplier_; // Multiplicador de puntos en función de 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
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 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 pause_initialized_; // Indica si la pausa ha sido inicializada
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
void Instructions::render() {
// Pinta en pantalla
@@ -252,6 +277,7 @@ void Instructions::start(Mode mode) {
manual_quit_ = false;
counter_ = 0;
ticks_ = 0;
elapsed_s_ = 0.0F;
}
// Indica si las instrucciones han terminado
+15 -8
View File
@@ -23,11 +23,12 @@ class Instructions {
Instructions(const Instructions &) = delete;
auto operator=(const Instructions &) -> Instructions & = delete;
void run(Mode mode); // Bucle principal
void start(Mode mode); // Inicia las instrucciones (sin bucle)
void update(); // Actualiza las variables
void render(); // Pinta en pantalla
void checkEvents(); // Comprueba los eventos
void run(Mode mode); // Bucle principal
void start(Mode mode); // Inicia las instrucciones (sin bucle)
void update(); // Actualiza las variables (frame-based)
void update(float dt_s); // Actualiza las variables (time-based)
void render(); // Pinta en pantalla
void checkEvents(); // Comprueba los eventos
[[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
@@ -43,14 +44,20 @@ class Instructions {
Section *section_; // Estado del bucle principal para saber si continua o se sale
// Variables
Uint16 counter_; // Contador
Uint16 counter_; // Contador (derivat de elapsed_s_ * 60 en mode time-based)
Uint16 counter_end_; // Valor final para el contador
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa
Uint32 ticks_speed_; // Velocidad a la que se repiten los bucles 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 (frame-based)
float elapsed_s_{0.0F}; // Acumulador de temps (time-based)
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
bool finished_; // Indica si las instrucciones han terminado
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
};
+64 -43
View File
@@ -12,9 +12,29 @@
#include "core/rendering/smartsprite.h" // for SmartSprite
#include "core/rendering/writer.h" // for Writer
#include "core/resources/resource.h"
#include "core/system/delta_time.hpp"
#include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, GAMECANVAS_FIRST_QU...
#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
Intro::Intro(SDL_Renderer *renderer, Section *section) {
// Copia los punteros
@@ -30,8 +50,6 @@ Intro::Intro(SDL_Renderer *renderer, Section *section) {
// Inicializa variables
section->name = SECTION_PROG_INTRO;
section->subsection = 0;
ticks_ = 0;
ticks_speed_ = 15;
scene_ = 1;
// Inicializa los bitmaps de la intro
@@ -40,62 +58,69 @@ Intro::Intro(SDL_Renderer *renderer, Section *section) {
auto *ss = new SmartSprite(texture_, renderer);
ss->setWidth(128);
ss->setHeight(96);
ss->setEnabledCounter(20);
ss->setRemainingTime(BITMAP_REMAINING_TIME_S);
ss->setDestX(GAMECANVAS_CENTER_X - 64);
ss->setDestY(GAMECANVAS_FIRST_QUARTER_Y - 24);
bitmaps_.push_back(ss);
}
// bitmap 0: entra des de l'esquerra, accelerant cap a la dreta
bitmaps_[0]->setPosX(-128);
bitmaps_[0]->setPosY(GAMECANVAS_FIRST_QUARTER_Y - 24);
bitmaps_[0]->setVelX(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]->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]->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]->setAccelX(-0.3F);
bitmaps_[1]->setAccelX(-1080.0F); // -0.3 px/tick² ⇒ -1080 px/s²
bitmaps_[1]->setAccelY(0.0F);
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]->setPosY(-96);
bitmaps_[2]->setVelX(0.0F);
bitmaps_[2]->setVelY(3.0F);
bitmaps_[2]->setAccelX(0.1F);
bitmaps_[2]->setAccelY(0.3F);
bitmaps_[2]->setVelY(180.0F); // 3 px/tick ⇒ 180 px/s
bitmaps_[2]->setAccelX(360.0F); // 0.1 px/tick² ⇒ 360 px/s²
bitmaps_[2]->setAccelY(1080.0F); // 0.3 px/tick² ⇒ 1080 px/s²
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]->setPosY(GAMECANVAS_HEIGHT);
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]->setAccelY(0.0F);
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]->setPosY(-96);
bitmaps_[4]->setVelX(0.0F);
bitmaps_[4]->setVelY(3.0F);
bitmaps_[4]->setAccelX(0.1F);
bitmaps_[4]->setAccelY(0.3F);
bitmaps_[4]->setVelY(180.0F);
bitmaps_[4]->setAccelX(360.0F);
bitmaps_[4]->setAccelY(1080.0F);
bitmaps_[4]->setSpriteClip(0, 192, 128, 96);
// bitmap 5: entra des de la dreta lentament
bitmaps_[5]->setPosX(GAMECANVAS_WIDTH);
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]->setAccelX(0.0F);
bitmaps_[5]->setAccelY(0.0F);
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;
for (int i = 0; i < TOTAL_TEXTS; ++i) {
auto *w = new Writer(text_);
@@ -103,51 +128,53 @@ Intro::Intro(SDL_Renderer *renderer, Section *section) {
w->setPosY(GAMECANVAS_HEIGHT - (BLOCK * 6));
w->setKerning(-1);
w->setEnabled(false);
w->setEnabledCounter(180);
w->setRemainingTime(TEXT_REMAINING_TIME_S);
texts_.push_back(w);
}
// Un dia qualsevol de l'any 2000
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
texts_[1]->setCaption(Lang::get()->getText(28));
texts_[1]->setSpeed(8);
texts_[1]->setSecondsPerChar(0.15F);
// Fins que un desaprensiu...
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...
texts_[3]->setCaption(Lang::get()->getText(30));
texts_[3]->setSpeed(8);
texts_[3]->setSecondsPerChar(0.15F);
// UAAAAAAAAAAAAA!!!
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...
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!
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!
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...
texts_[8]->setCaption(Lang::get()->getText(35));
texts_[8]->setSpeed(16);
texts_[8]->setSecondsPerChar(0.3F);
for (auto *t : texts_) {
t->center(GAMECANVAS_CENTER_X);
}
Audio::get()->playMusic(music_, 0);
DeltaTime::reset();
}
// Destructor
@@ -332,26 +359,19 @@ void Intro::updateScene6() {
}
// Actualiza las variables del objeto
void Intro::update() {
void Intro::update(float dt_s) {
Audio::update();
checkInput();
if (SDL_GetTicks() - ticks_ > ticks_speed_) {
// Actualiza el contador de ticks
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 *bitmap : bitmaps_) {
bitmap->update(dt_s);
}
for (auto *t : texts_) {
t->update(dt_s);
}
updateScenes();
}
// Dibuja el objeto en pantalla
@@ -386,7 +406,8 @@ void Intro::run() {
// Ejecuta un frame
void Intro::iterate() {
update();
const float DELTA_TIME_S = DeltaTime::tick();
update(DELTA_TIME_S);
render();
}
+5 -7
View File
@@ -36,15 +36,13 @@ class Intro {
Section *section_; // Estado del bucle principal para saber si continua o se sale
// 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
int scene_; // Indica que escena está activa
int scene_; // Indica que escena está activa
void update(); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla
void checkInput(); // Comprueba las entradas
void updateScenes(); // Actualiza las escenas de la intro
void update(float dt_s); // Actualiza las variables del objeto (time-based)
void render(); // Dibuja el objeto en pantalla
void checkInput(); // Comprueba las entradas
void updateScenes(); // Actualiza las escenas de la intro
// Helpers de updateScenes, uno por cada escena
void updateScene1();
+21 -24
View File
@@ -11,12 +11,16 @@
#include "core/rendering/screen.h" // for Screen
#include "core/rendering/sprite.h" // for Sprite
#include "core/resources/resource.h"
#include "game/defaults.hpp" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG...
#include "utils/utils.h" // for Section, Color
#include "core/system/delta_time.hpp" // for DeltaTime::reset / tick
#include "game/defaults.hpp" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG...
#include "utils/utils.h" // for Section, Color
// Valores de inicialización y fin
constexpr int INIT_FADE = 100;
constexpr int END_LOGO = 200;
// Durades de l'escena (segons). Time-based: ja no comptem frames. Valors
// equivalents al comportament anterior (frame counter a 15ms): 100 i 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
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);
// Inicializa variables
counter_ = 0;
section->name = SECTION_PROG_LOGO;
section->subsection = 0;
ticks_ = 0;
ticks_speed_ = 15;
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
@@ -48,7 +53,7 @@ Logo::~Logo() {
// Comprueba si ha terminado el logo
void Logo::checkLogoEnd() {
if (counter_ >= END_LOGO + 20) {
if (elapsed_time_s_ >= SCENE_END_S) {
section_->name = SECTION_PROG_INTRO;
section_->subsection = 0;
}
@@ -68,9 +73,8 @@ void Logo::checkInput() {
// Dibuja el fade
void Logo::renderFade() {
// Dibuja el fade
if (counter_ >= INIT_FADE) {
const float STEP = (float)(counter_ - INIT_FADE) / (float)(END_LOGO - INIT_FADE);
if (elapsed_time_s_ >= FADE_START_S) {
const float STEP = (elapsed_time_s_ - FADE_START_S) / (FADE_END_S - FADE_START_S);
const int ALPHA = std::min((int)(255 * STEP), 255);
SDL_SetRenderDrawColor(renderer_, BG_COLOR.r, BG_COLOR.g, BG_COLOR.b, ALPHA);
SDL_RenderFillRect(renderer_, nullptr);
@@ -78,20 +82,12 @@ void Logo::renderFade() {
}
// Actualiza las variables del objeto
void Logo::update() {
void Logo::update(float delta_time_s) {
Audio::update();
checkInput();
if (SDL_GetTicks() - ticks_ > ticks_speed_) {
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
// Actualiza el contador
counter_++;
// Comprueba si ha terminado el logo
checkLogoEnd();
}
elapsed_time_s_ += delta_time_s;
checkLogoEnd();
}
// Dibuja el objeto en pantalla
@@ -123,7 +119,8 @@ void Logo::run() {
// Ejecuta un frame
void Logo::iterate() {
update();
const float DELTA_TIME_S = DeltaTime::tick();
update(DELTA_TIME_S);
render();
}
+7 -9
View File
@@ -26,14 +26,12 @@ class Logo {
Sprite *sprite_; // Sprite con la textura del logo
Section *section_; // Estado del bucle principal para saber si continua o se sale
// Variables
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa
Uint32 ticks_speed_; // Velocidad a la que se repiten los bucles del programa
int counter_; // Contador
// Temps acumulat de l'escena (segons). Time-based: no comptem frames.
float elapsed_time_s_{0.0F};
void update(); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla
void checkLogoEnd(); // Comprueba si ha terminado el logo
void checkInput(); // Comprueba las entradas
void renderFade(); // Dibuja el fade
void update(float delta_time_s); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla
void checkLogoEnd(); // Comprueba si ha terminado el logo
void checkInput(); // Comprueba las entradas
void renderFade(); // Dibuja el fade
};
+87 -62
View File
@@ -2,9 +2,10 @@
#include <SDL3/SDL.h>
#include <cstdlib> // for rand
#include <iostream> // for basic_ostream, operator<<, basic_ostrea...
#include <string> // for basic_string, operator+, char_traits
#include <algorithm> // for min
#include <cstdlib> // for rand
#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/input/global_inputs.hpp" // for GlobalInputs::handle
@@ -19,10 +20,11 @@
#include "core/rendering/texture.h" // for Texture
#include "core/resources/asset.h" // for Asset
#include "core/resources/resource.h"
#include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT
#include "game/game.h" // for Game
#include "game/options.hpp" // for Options
#include "game/ui/menu.h" // for Menu
#include "core/system/delta_time.hpp" // for DeltaTime::reset / tick
#include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT
#include "game/game.h" // for Game
#include "game/options.hpp" // for Options
#include "game/ui/menu.h" // for Menu
// Constructor
Title::Title(SDL_Renderer *renderer, Section *section) {
@@ -70,6 +72,9 @@ Title::Title(SDL_Renderer *renderer, Section *section) {
// Inicializa los valores
init();
// Reset del rellotge: la primera crida a tick() retornarà ~0.
DeltaTime::reset();
}
// Destructor
@@ -92,18 +97,19 @@ Title::~Title() {
void Title::init() {
// Inicializa variables
section_->subsection = SUBSECTION_TITLE_1;
counter_ = COUNTER;
background_counter_ = 0;
demo_remaining_s_ = DEMO_TIMEOUT_S;
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;
menu_visible_ = false;
menu_.active = menu_.title;
next_section_.name = SECTION_PROG_GAME;
post_fade_ = 0;
ticks_ = 0;
ticks_speed_ = 15;
fade_->init(0x17, 0x17, 0x26);
demo_ = true;
vibration_step_ = 0;
vibration_elapsed_s_ = 0.0F;
vibration_initialized_ = false;
instructions_active_ = false;
demo_game_active_ = false;
@@ -148,12 +154,12 @@ void Title::init() {
coffee_bitmap_->setWidth(167);
coffee_bitmap_->setHeight(46);
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_->setAccelY(0.1F);
coffee_bitmap_->setAccelY(360.0F); // 0.1 px/tick² ⇒ 360 px/s²
coffee_bitmap_->setSpriteClip(0, 0, 167, 46);
coffee_bitmap_->setEnabled(true);
coffee_bitmap_->setEnabledCounter(0);
coffee_bitmap_->setRemainingTime(0.0F);
coffee_bitmap_->setDestX(45);
coffee_bitmap_->setDestY(11);
@@ -164,12 +170,12 @@ void Title::init() {
crisis_bitmap_->setWidth(137);
crisis_bitmap_->setHeight(46);
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_->setAccelY(-0.1F);
crisis_bitmap_->setAccelY(-360.0F); // -0.1 px/tick² ⇒ -360 px/s²
crisis_bitmap_->setSpriteClip(0, 0, 137, 46);
crisis_bitmap_->setEnabled(true);
crisis_bitmap_->setEnabledCounter(0);
crisis_bitmap_->setRemainingTime(0.0F);
crisis_bitmap_->setDestX(60);
crisis_bitmap_->setDestY(57);
@@ -209,25 +215,20 @@ void Title::init() {
updateMenuLabels();
}
// Actualiza las variables del objeto
void Title::update() {
// Actualiza las variables del objeto (time-based)
void Title::update(float dt_s) {
Audio::update();
checkInput();
if (SDL_GetTicks() - ticks_ <= ticks_speed_) {
return;
}
ticks_ = SDL_GetTicks();
switch (section_->subsection) {
case SUBSECTION_TITLE_1:
updateTitle1();
updateTitle1(dt_s);
break;
case SUBSECTION_TITLE_2:
updateTitle2();
updateTitle2(dt_s);
break;
case SUBSECTION_TITLE_3:
updateTitle3();
updateTitle3(dt_s);
break;
default:
break;
@@ -235,9 +236,9 @@ void Title::update() {
}
// Sección 1 - Titulo desplazandose
void Title::updateTitle1() {
coffee_bitmap_->update();
crisis_bitmap_->update();
void Title::updateTitle1(float dt_s) {
coffee_bitmap_->update(dt_s);
crisis_bitmap_->update(dt_s);
// Si los objetos han llegado a su destino, cambiamos de Sección
if (coffee_bitmap_->hasFinished() && crisis_bitmap_->hasFinished()) {
@@ -257,7 +258,7 @@ void Title::updateTitle1() {
}
// Sección 2 - Titulo vibrando
void Title::updateTitle2() {
void Title::updateTitle2(float dt_s) {
// Captura las posiciones base la primera vez
if (!vibration_initialized_) {
vibration_coffee_base_x_ = coffee_bitmap_->getPosX();
@@ -265,37 +266,44 @@ void Title::updateTitle2() {
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};
coffee_bitmap_->setPosX(vibration_coffee_base_x_ + V[vibration_step_ / 3]);
crisis_bitmap_->setPosX(vibration_crisis_base_x_ + V[vibration_step_ / 3]);
dust_bitmap_right_->update();
dust_bitmap_left_->update();
constexpr int V_SIZE = static_cast<int>(sizeof(V) / sizeof(V[0]));
const int IDX = std::min(static_cast<int>(vibration_elapsed_s_ / VIBRATION_STEP_DURATION_S), V_SIZE - 1);
coffee_bitmap_->setPosX(vibration_coffee_base_x_ + V[IDX]);
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;
vibration_step_ = 0;
vibration_elapsed_s_ = 0.0F;
vibration_initialized_ = false;
}
}
// Sección 3 - La pantalla de titulo con el menú y la música
void Title::updateTitle3() {
if (counter_ > 0) {
void Title::updateTitle3(float dt_s) {
if (demo_remaining_s_ > 0.0F) {
if (Audio::getRealMusicState() == Audio::MusicState::STOPPED) {
Audio::get()->playMusic(title_music_);
}
dust_bitmap_right_->update();
dust_bitmap_left_->update();
fade_->update();
dust_bitmap_right_->update(dt_s);
dust_bitmap_left_->update(dt_s);
fade_->update(dt_s);
if (fade_->hasEnded()) {
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()) {
menu_.active->update();
@@ -312,9 +320,9 @@ void Title::updateTitle3() {
}
if (menu_.active->getName() == "TITLE") {
counter_--;
demo_remaining_s_ -= dt_s;
}
} else if (counter_ == 0) {
} else {
if (demo_) {
demo_then_instructions_ = true;
runDemoGame();
@@ -351,7 +359,7 @@ void Title::handlePostFadeAction() {
break;
case 3: // TIME OUT
counter_ = COUNTER;
demo_remaining_s_ = DEMO_TIMEOUT_S;
menu_.active->reset();
if (demo_) {
demo_then_instructions_ = true;
@@ -602,8 +610,8 @@ void Title::render() {
dust_bitmap_right_->render();
dust_bitmap_left_->render();
// PRESS ANY KEY!
if ((counter_ % 50 > 14) && (!menu_visible_)) {
// PRESS ANY KEY! Blink: visible quan la fase passa l'umbral "off".
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);
}
@@ -626,15 +634,26 @@ void Title::checkInput() {
GlobalInputs::handle();
}
// Actualiza el tileado de fondo
void Title::updateBG() {
if (background_mode_ == 0) { // El tileado de fondo se desplaza en diagonal
++background_window_.x %= 64;
++background_window_.y %= 64;
} else { // El tileado de fondo se desplaza en circulo
++background_counter_ %= 360;
background_window_.x = 128 + (int(sin_[(background_counter_ + 270) % 360] * 128));
background_window_.y = 96 + (int(sin_[(360 - background_counter_) % 360] * 96));
// Actualiza el tileado de fondo (time-based).
void Title::updateBG(float dt_s) {
if (background_mode_ == 0) {
// Diagonal: 60 px/s a 60Hz ⇒ un cicle de 64 px cada 64/60 = 1.067 s.
// Ancorat a la posició inicial (128, 96): t=0 dona la mateixa vista que
// l'estàtic dels Title1/Title2, sense salt visual a l'entrada del Title3.
constexpr float SCROLL_PERIOD_S = 64.0F / BG_SCROLL_SPEED_PX_PER_S;
bg_scroll_x_s_ += dt_s;
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
void Title::iterate() {
const float DELTA_TIME_S = DeltaTime::tick();
// Si las instrucciones están activas, delega el frame
if (instructions_active_) {
instructions_->update();
instructions_->update(DELTA_TIME_S);
instructions_->render();
if (instructions_->hasFinished()) {
@@ -855,6 +876,8 @@ void Title::iterate() {
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_3;
}
// Reset del rellotge per evitar un dt enorme al tornar al Title.
DeltaTime::reset();
}
return;
}
@@ -884,6 +907,8 @@ void Title::iterate() {
section_->name = SECTION_PROG_TITLE;
section_->subsection = SUBSECTION_TITLE_1;
}
// Reset del rellotge per evitar un dt enorme al tornar al Title.
DeltaTime::reset();
} else {
// Restaura section para que Director no transicione fuera de Title
section_->name = SECTION_PROG_TITLE;
@@ -892,7 +917,7 @@ void Title::iterate() {
}
// Ejecución normal del título
update();
update(DELTA_TIME_S);
render();
}
@@ -918,7 +943,7 @@ void Title::handleEvent(const SDL_Event *event) {
if (section_->subsection == SUBSECTION_TITLE_3) {
if ((event->type == SDL_EVENT_KEY_UP) || (event->type == SDL_EVENT_JOYSTICK_BUTTON_UP)) {
menu_visible_ = true;
counter_ = COUNTER;
demo_remaining_s_ = DEMO_TIMEOUT_S;
}
}
}
+38 -23
View File
@@ -34,7 +34,21 @@ class Title {
private:
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 {
Menu *title; // Menu de la pantalla de título
@@ -72,19 +86,20 @@ class Title {
Fade *fade_; // Objeto para realizar fundidos en pantalla
// Variables
Ja::Music *title_music_; // Musica para el titulo
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
int counter_; // Temporizador para la pantalla de titulo
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa
Uint8 background_mode_; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo
float sin_[360]; // Vector con los valores del seno precalculados
bool menu_visible_; // Indicador para saber si se muestra el menu del titulo o la frase intermitente
bool demo_; // Indica si el modo demo estará activo
Section next_section_; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo
Uint32 ticks_speed_; // Velocidad a la que se repiten los bucles del programa
Uint8 post_fade_; // Opción a realizar cuando termina el fundido
MenuData menu_; // Variable con todos los objetos menus y sus variables
Ja::Music *title_music_; // Musica para el titulo
Ja::Sound *crash_sound_; // Sonido con el impacto del título
float bg_scroll_x_s_{0.0F}; // Acumulador d'scroll horitzontal (segons) per al BG mode 0
float bg_scroll_y_s_{0.0F}; // Acumulador d'scroll vertical (segons) per al BG mode 0
float bg_phase_s_{0.0F}; // Fase del cicle del BG mode 1 (0..BG_CIRCLE_PERIOD_S)
float demo_remaining_s_; // Temps que queda al títol abans de tornar al logo / demo
float blink_phase_s_{0.0F}; // Fase del blink de "PRESS ANY KEY"
Uint8 background_mode_; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo
float sin_[360]; // Vector con los valores del seno precalculados
bool menu_visible_; // Indicador para saber si se muestra el menu del titulo o la frase intermitente
bool demo_; // Indica si el modo demo estará activo
Section next_section_; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo
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.
Options::Video prev_video_;
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
// Variables para la vibración del título (SUBSECTION_TITLE_2)
int vibration_step_; // Paso actual de la vibración
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
bool vibration_initialized_; // Indica si se han capturado las posiciones base
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_crisis_base_x_{0}; // Posición X base del bitmap Crisis
bool vibration_initialized_; // Indica si se han capturado las posiciones base
// Variables para sub-estados delegados (instrucciones y demo)
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
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
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
void updateTitle1();
void updateTitle2();
void updateTitle3();
void updateTitle1(float dt_s);
void updateTitle2(float dt_s);
void updateTitle3(float dt_s);
void handlePostFadeAction();
void handleTitleMenuSelection();
void handlePlayerSelectMenuSelection();
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
void updateMenuLabels() const; // Actualiza los elementos de los menus
void applyOptions(); // Aplica las opciones de menu seleccionadas