#define _USE_MATH_DEFINES #include "background.hpp" #include // Para SDL_FRect, SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_GetRenderTarget, SDL_RenderTexture, SDL_SetTextureAlphaMod, SDL_SetTextureBlendMode, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_TextureAccess, SDL_FPoint #include // Para clamp, max #include // Para M_PI, cos, sin #include #include "animated_sprite.hpp" // Para MovingSprite #include "moving_sprite.hpp" // Para MovingSprite #include "param.hpp" // Para Param, ParamBackground, param #include "resource.hpp" // Para Resource #include "screen.hpp" // Para Screen #include "sprite.hpp" // Para Sprite #include "texture.hpp" // Para Texture #include "utils.hpp" // Para funciones de easing // Constructor Background::Background(float total_progress_to_complete) : renderer_(Screen::get()->getRenderer()), buildings_texture_(Resource::get()->getTexture("game_buildings.png")), top_clouds_texture_(Resource::get()->getTexture("game_clouds1.png")), bottom_clouds_texture_(Resource::get()->getTexture("game_clouds2.png")), gradients_texture_(Resource::get()->getTexture("game_sky_colors.png")), sun_texture_(Resource::get()->getTexture("game_sun.png")), moon_texture_(Resource::get()->getTexture("game_moon.png")), grass_sprite_(std::make_unique(Resource::get()->getTexture("game_grass.png"), Resource::get()->getAnimation("game_grass.ani"))), total_progress_to_complete_(total_progress_to_complete), progress_per_stage_(total_progress_to_complete_ / STAGES), sun_completion_progress_(total_progress_to_complete_ * SUN_COMPLETION_FACTOR), minimum_completed_progress_(total_progress_to_complete_ * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE), rect_(SDL_FRect{0, 0, static_cast(gradients_texture_->getWidth() / 2), static_cast(gradients_texture_->getHeight() / 2)}), src_rect_({.x = 0, .y = 0, .w = 320, .h = 240}), dst_rect_({.x = 0, .y = 0, .w = 320, .h = 240}), attenuate_color_(Color(param.background.attenuate_color.r, param.background.attenuate_color.g, param.background.attenuate_color.b)), alpha_color_texture_(param.background.attenuate_color.a), previous_alpha_color_texture_(param.background.attenuate_color.a), base_(rect_.h) { initializePaths(); initializeRects(); initializeSprites(); initializeSpriteProperties(); initializeTextures(); } // Destructor Background::~Background() { SDL_DestroyTexture(canvas_); SDL_DestroyTexture(color_texture_); } // Inicializa las rutas del sol y la luna void Background::initializePaths() { createSunPath(); createMoonPath(); } // Inicializa los rectángulos de gradientes y nubes void Background::initializeRects() { gradient_rect_[0] = {.x = 0, .y = 0, .w = rect_.w, .h = rect_.h}; gradient_rect_[1] = {.x = rect_.w, .y = 0, .w = rect_.w, .h = rect_.h}; gradient_rect_[2] = {.x = 0, .y = rect_.h, .w = rect_.w, .h = rect_.h}; gradient_rect_[3] = {.x = rect_.w, .y = rect_.h, .w = rect_.w, .h = rect_.h}; const float TOP_CLOUDS_TEXTURE_HEIGHT = top_clouds_texture_->getHeight() / 4; const float BOTTOM_CLOUDS_TEXTURE_HEIGHT = bottom_clouds_texture_->getHeight() / 4; for (int i = 0; i < 4; ++i) { top_clouds_rect_[i] = {.x = 0, .y = i * TOP_CLOUDS_TEXTURE_HEIGHT, .w = static_cast(top_clouds_texture_->getWidth()), .h = TOP_CLOUDS_TEXTURE_HEIGHT}; bottom_clouds_rect_[i] = {.x = 0, .y = i * BOTTOM_CLOUDS_TEXTURE_HEIGHT, .w = static_cast(bottom_clouds_texture_->getWidth()), .h = BOTTOM_CLOUDS_TEXTURE_HEIGHT}; } } // Crea los sprites void Background::initializeSprites() { const float TOP_CLOUDS_Y = base_ - 165; const float BOTTOM_CLOUDS_Y = base_ - 101; top_clouds_sprite_a_ = std::make_unique(top_clouds_texture_, (SDL_FRect){0, TOP_CLOUDS_Y, rect_.w, static_cast(top_clouds_texture_->getHeight())}); top_clouds_sprite_b_ = std::make_unique(top_clouds_texture_, (SDL_FRect){rect_.w, TOP_CLOUDS_Y, rect_.w, static_cast(top_clouds_texture_->getHeight())}); bottom_clouds_sprite_a_ = std::make_unique(bottom_clouds_texture_, (SDL_FRect){0, BOTTOM_CLOUDS_Y, rect_.w, static_cast(bottom_clouds_texture_->getHeight())}); bottom_clouds_sprite_b_ = std::make_unique(bottom_clouds_texture_, (SDL_FRect){rect_.w, BOTTOM_CLOUDS_Y, rect_.w, static_cast(bottom_clouds_texture_->getHeight())}); buildings_sprite_ = std::make_unique(buildings_texture_); gradient_sprite_ = std::make_unique(gradients_texture_, 0, 0, rect_.w, rect_.h); sun_sprite_ = std::make_unique(sun_texture_); moon_sprite_ = std::make_unique(moon_texture_); } // Configura las propiedades iniciales de los sprites void Background::initializeSpriteProperties() { // Velocidades iniciales que coinciden con updateCloudsSpeed() cuando progress=0 constexpr float INITIAL_TOP_CLOUDS_SPEED_PX_PER_S = 0.05F * 60.0F; // 3.0 píxeles/segundo (coincide con CLOUDS_INITIAL_SPEED) constexpr float INITIAL_BOTTOM_CLOUDS_SPEED_PX_PER_S = 0.05F * 60.0F / 2.0F; // 1.5 píxeles/segundo (mitad de velocidad) top_clouds_sprite_a_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight()); top_clouds_sprite_a_->setVelX(-INITIAL_TOP_CLOUDS_SPEED_PX_PER_S); top_clouds_sprite_b_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight()); top_clouds_sprite_b_->setVelX(-INITIAL_TOP_CLOUDS_SPEED_PX_PER_S); bottom_clouds_sprite_a_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight()); bottom_clouds_sprite_a_->setVelX(-INITIAL_BOTTOM_CLOUDS_SPEED_PX_PER_S); bottom_clouds_sprite_b_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight()); bottom_clouds_sprite_b_->setVelX(-INITIAL_BOTTOM_CLOUDS_SPEED_PX_PER_S); // grass_sprite_->setY(base_ - grass_sprite_->getHeight()); // grass_sprite_->resetAnimation(); grass_sprite_->setPos(0.0F, base_ - 10.0F); grass_sprite_->setWidth(320.0F); grass_sprite_->setHeight(10.0F); // grass_sprite_->setCurrentAnimation(0); buildings_sprite_->setY(base_ - buildings_sprite_->getHeight()); sun_sprite_->setPosition(sun_path_.front()); moon_sprite_->setPosition(moon_path_.front()); } // Inicializa las texturas de renderizado void Background::initializeTextures() { canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, rect_.w, rect_.h); SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND); color_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, rect_.w, rect_.h); SDL_SetTextureBlendMode(color_texture_, SDL_BLENDMODE_BLEND); setColor(attenuate_color_); SDL_SetTextureAlphaMod(color_texture_, alpha_color_texture_); } // Actualiza la lógica del objeto void Background::update(float delta_time) { // Actualiza la progresión y calcula transiciones if (!manual_mode_) { updateProgression(delta_time); } // Actualiza el valor de alpha updateAlphaColorTexture(); // Actualiza las nubes updateClouds(delta_time); // Actualiza el sprite con la hierba grass_sprite_->update(delta_time); // Calcula el valor de alpha alpha_ = std::max((255 - (int)(255 * transition_)), 0); // Mueve el sol y la luna según la progresión sun_sprite_->setPosition(sun_path_.at(sun_index_)); moon_sprite_->setPosition(moon_path_.at(moon_index_)); // Compone todos los elementos del fondo en la textura fillCanvas(); } // Incrementa la progresión interna void Background::incrementProgress(float amount) { if (state_ == State::NORMAL) { float old_progress = progress_; progress_ += amount; progress_ = std::min(progress_, total_progress_to_complete_); // Notifica el cambio si hay callback y el progreso cambió if (progress_callback_ && progress_ != old_progress) { progress_callback_(progress_); } } } // Establece la progresión absoluta void Background::setProgress(float absolute_progress) { float old_progress = progress_; progress_ = std::clamp(absolute_progress, 0.0F, total_progress_to_complete_); // Notifica el cambio si hay callback y el progreso cambió if (progress_callback_ && progress_ != old_progress) { progress_callback_(progress_); } } // Cambia el estado del fondo void Background::setState(State new_state) { // Si entra en estado completado, inicializar variables de transición if (new_state == State::COMPLETED && state_ != State::COMPLETED) { completion_initial_progress_ = progress_; completion_transition_timer_ = 0.0f; } state_ = new_state; } // Reinicia la progresión void Background::reset() { float old_progress = progress_; progress_ = 0.0F; state_ = State::NORMAL; manual_mode_ = false; gradient_number_ = 0; transition_ = 0.0F; sun_index_ = 0; moon_index_ = 0; // Resetear variables de transición de completado completion_transition_timer_ = 0.0f; completion_initial_progress_ = 0.0f; // Notifica el cambio si hay callback if (progress_callback_ && progress_ != old_progress) { progress_callback_(progress_); } } // Activa/desactiva el modo manual void Background::setManualMode(bool manual) { manual_mode_ = manual; } // Establece callback para sincronización automática void Background::setProgressCallback(ProgressCallback callback) { progress_callback_ = std::move(callback); } // Elimina el callback void Background::removeProgressCallback() { progress_callback_ = nullptr; } // Ajusta la velocidad de las nubes void Background::setCloudsSpeed(float value) { clouds_speed_ = value; // En modo manual, aplicar la velocidad directamente // Las nubes inferiores van a la mitad de velocidad que las superiores top_clouds_sprite_a_->setVelX(value); top_clouds_sprite_b_->setVelX(value); bottom_clouds_sprite_a_->setVelX(value / 2.0F); bottom_clouds_sprite_b_->setVelX(value / 2.0F); } // Establece el degradado de fondo void Background::setGradientNumber(int value) { gradient_number_ = value % STAGES; } // Ajusta la transición entre texturas void Background::setTransition(float value) { transition_ = std::clamp(value, 0.0F, 1.0F); } // Establece la posición del sol void Background::setSunProgression(float progress) { progress = std::clamp(progress, 0.0F, 1.0F); sun_index_ = static_cast(progress * (sun_path_.size() - 1)); } // Establece la posición de la luna void Background::setMoonProgression(float progress) { progress = std::clamp(progress, 0.0F, 1.0F); moon_index_ = static_cast(progress * (moon_path_.size() - 1)); } // Actualiza la progresión y calcula las transiciones void Background::updateProgression(float delta_time) { // Si el juego está completado, hacer transición suave con easing if (state_ == State::COMPLETED) { completion_transition_timer_ += delta_time; // Calcular progreso normalizado de la transición (0.0 a 1.0) float t = std::min(completion_transition_timer_ / COMPLETION_TRANSITION_DURATION_S, 1.0f); if (t < 1.0f) { // Usar easeOutCubic para transición suave (rápido al inicio, lento al final) float eased_t = easeOutCubic(static_cast(t)); // Interpolación desde progreso inicial hasta mínimo float progress_range = completion_initial_progress_ - minimum_completed_progress_; progress_ = completion_initial_progress_ - (progress_range * eased_t); } else { // Transición completada, fijar al valor mínimo progress_ = minimum_completed_progress_; } } // Calcula la transición de los diferentes fondos const float GRADIENT_NUMBER_FLOAT = std::min(progress_ / progress_per_stage_, 3.0F); const float PERCENT = GRADIENT_NUMBER_FLOAT - static_cast(GRADIENT_NUMBER_FLOAT); gradient_number_ = static_cast(GRADIENT_NUMBER_FLOAT); transition_ = PERCENT; // Calcula la posición del sol const float SUN_PROGRESSION = std::min(progress_ / sun_completion_progress_, 1.0F); sun_index_ = static_cast(SUN_PROGRESSION * (sun_path_.size() - 1)); // Calcula la posición de la luna const float MOON_PROGRESSION = std::min(progress_ / total_progress_to_complete_, 1.0F); moon_index_ = static_cast(MOON_PROGRESSION * (moon_path_.size() - 1)); // Actualiza la velocidad de las nubes updateCloudsSpeed(); } // Actualiza la velocidad de las nubes según el estado y progresión void Background::updateCloudsSpeed() { // Cálculo de velocidad según progreso (convertido de frame-based a time-based) constexpr float CLOUDS_INITIAL_SPEED_PX_PER_S = 0.05F * 60.0F; // 3.0 píxeles/segundo (era 0.05 px/frame @ 60fps) constexpr float CLOUDS_TOTAL_SPEED_PX_PER_S = 2.00F * 60.0F; // 120.0 píxeles/segundo (era 2.00 px/frame @ 60fps) constexpr float CLOUDS_FINAL_SPEED_RANGE_PX_PER_S = CLOUDS_TOTAL_SPEED_PX_PER_S - CLOUDS_INITIAL_SPEED_PX_PER_S; // 117.0 píxeles/segundo // Velocidad base según progreso (de -3.0 a -120.0 píxeles/segundo, igual que la versión original) float base_clouds_speed = (-CLOUDS_INITIAL_SPEED_PX_PER_S) + (-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / total_progress_to_complete_)); // En estado completado, las nubes se ralentizan gradualmente if (state_ == State::COMPLETED) { float completion_factor = (progress_ - minimum_completed_progress_) / (total_progress_to_complete_ - minimum_completed_progress_); completion_factor = std::max(0.1F, completion_factor); base_clouds_speed *= completion_factor; } // Aplicar velocidades diferentes para nubes superiores e inferiores const float TOP_CLOUDS_SPEED = base_clouds_speed; const float BOTTOM_CLOUDS_SPEED = base_clouds_speed / 2.0F; // Aplicar las velocidades a los sprites correspondientes top_clouds_sprite_a_->setVelX(TOP_CLOUDS_SPEED); top_clouds_sprite_b_->setVelX(TOP_CLOUDS_SPEED); bottom_clouds_sprite_a_->setVelX(BOTTOM_CLOUDS_SPEED); bottom_clouds_sprite_b_->setVelX(BOTTOM_CLOUDS_SPEED); // Guardar la velocidad principal clouds_speed_ = TOP_CLOUDS_SPEED; } // Actualiza las nubes void Background::updateClouds(float deltaTime) { // Mueve las nubes top_clouds_sprite_a_->update(deltaTime); top_clouds_sprite_b_->update(deltaTime); bottom_clouds_sprite_a_->update(deltaTime); bottom_clouds_sprite_b_->update(deltaTime); // Calcula el offset de las nubes if (top_clouds_sprite_a_->getPosX() < -top_clouds_sprite_a_->getWidth()) { top_clouds_sprite_a_->setPosX(top_clouds_sprite_a_->getWidth()); } if (top_clouds_sprite_b_->getPosX() < -top_clouds_sprite_b_->getWidth()) { top_clouds_sprite_b_->setPosX(top_clouds_sprite_b_->getWidth()); } if (bottom_clouds_sprite_a_->getPosX() < -bottom_clouds_sprite_a_->getWidth()) { bottom_clouds_sprite_a_->setPosX(bottom_clouds_sprite_a_->getWidth()); } if (bottom_clouds_sprite_b_->getPosX() < -bottom_clouds_sprite_b_->getWidth()) { bottom_clouds_sprite_b_->setPosX(bottom_clouds_sprite_b_->getWidth()); } } // Dibuja el gradiente de fondo void Background::renderGradient() { // Dibuja el gradiente de detras gradients_texture_->setAlpha(255); gradient_sprite_->setSpriteClip(gradient_rect_[(gradient_number_ + 1) % 4]); gradient_sprite_->render(); // Dibuja el gradiente de delante con una opacidad cada vez menor gradients_texture_->setAlpha(alpha_); gradient_sprite_->setSpriteClip(gradient_rect_[gradient_number_]); gradient_sprite_->render(); } // Dibuja las nubes de arriba void Background::renderTopClouds() { // Dibuja el primer conjunto de nubes, las de detras top_clouds_texture_->setAlpha(255); top_clouds_sprite_a_->setSpriteClip(top_clouds_rect_[(gradient_number_ + 1) % 4]); top_clouds_sprite_b_->setSpriteClip(top_clouds_rect_[(gradient_number_ + 1) % 4]); top_clouds_sprite_a_->render(); top_clouds_sprite_b_->render(); // Dibuja el segundo conjunto de nubes, las de delante top_clouds_texture_->setAlpha(alpha_); top_clouds_sprite_a_->setSpriteClip(top_clouds_rect_[gradient_number_]); top_clouds_sprite_b_->setSpriteClip(top_clouds_rect_[gradient_number_]); top_clouds_sprite_a_->render(); top_clouds_sprite_b_->render(); } // Dibuja las nubes de abajo void Background::renderBottomClouds() { // Dibuja el primer conjunto de nubes, las de detras bottom_clouds_texture_->setAlpha(255); bottom_clouds_sprite_a_->setSpriteClip(bottom_clouds_rect_[(gradient_number_ + 1) % 4]); bottom_clouds_sprite_b_->setSpriteClip(bottom_clouds_rect_[(gradient_number_ + 1) % 4]); bottom_clouds_sprite_a_->render(); bottom_clouds_sprite_b_->render(); // Dibuja el segundo conjunto de nubes, las de delante bottom_clouds_texture_->setAlpha(alpha_); bottom_clouds_sprite_a_->setSpriteClip(bottom_clouds_rect_[gradient_number_]); bottom_clouds_sprite_b_->setSpriteClip(bottom_clouds_rect_[gradient_number_]); bottom_clouds_sprite_a_->render(); bottom_clouds_sprite_b_->render(); } // Compone todos los elementos del fondo en la textura void Background::fillCanvas() { // Cambia el destino del renderizador auto* temp = SDL_GetRenderTarget(renderer_); SDL_SetRenderTarget(renderer_, canvas_); // Dibuja el gradiente de fondo renderGradient(); // Dibuja los astros sun_sprite_->render(); moon_sprite_->render(); // Dibuja las nubes de arriba renderTopClouds(); // Dibuja las nubes de abajo renderBottomClouds(); // Dibuja los edificios buildings_sprite_->render(); // Dibuja la hierba grass_sprite_->render(); // Deja el renderizador apuntando donde estaba SDL_SetRenderTarget(renderer_, temp); } // Dibuja el objeto void Background::render() { // Fondo SDL_RenderTexture(renderer_, canvas_, &src_rect_, &dst_rect_); // Atenuación SDL_RenderTexture(renderer_, color_texture_, &src_rect_, &dst_rect_); } // Establece la posición del objeto void Background::setPos(SDL_FRect pos) { dst_rect_ = pos; // Si cambian las medidas del destino, hay que cambiar las del origen para evitar deformar la imagen src_rect_.x = 0; src_rect_.y = rect_.h - pos.h; src_rect_.w = pos.w; src_rect_.h = pos.h; } // Establece el color de atenuación void Background::setColor(Color color) { attenuate_color_ = color; // Colorea la textura auto* temp = SDL_GetRenderTarget(renderer_); SDL_SetRenderTarget(renderer_, color_texture_); SDL_SetRenderDrawColor(renderer_, attenuate_color_.r, attenuate_color_.g, attenuate_color_.b, 255); SDL_RenderClear(renderer_); SDL_SetRenderTarget(renderer_, temp); } // Establece la transparencia de la atenuación void Background::setAlpha(int alpha) { // Evita que se asignen valores fuera de rango alpha = std::clamp(alpha, 0, 255); // Guarda el valor actual y establece el nuevo valor previous_alpha_color_texture_ = alpha_color_texture_; alpha_color_texture_ = alpha; } // Actualiza el valor de alpha void Background::updateAlphaColorTexture() { if (alpha_color_texture_ == previous_alpha_color_texture_) { return; } alpha_color_texture_ > previous_alpha_color_texture_ ? ++previous_alpha_color_texture_ : --previous_alpha_color_texture_; SDL_SetTextureAlphaMod(color_texture_, previous_alpha_color_texture_); } // Precalcula el vector con el recorrido del sol void Background::createSunPath() { constexpr float CENTER_X = 170; const float CENTER_Y = base_ - 80; constexpr float RADIUS = 120; // Generar puntos de la curva desde 90 a 180 grados constexpr double STEP = 0.01; const int NUM_STEPS = static_cast((M_PI - M_PI / 2) / STEP) + 1; for (int i = 0; i < NUM_STEPS; ++i) { double theta = M_PI / 2 + (i * STEP); float x = CENTER_X + (RADIUS * cos(theta)); float y = CENTER_Y - (RADIUS * sin(theta)); sun_path_.push_back({x, y}); } // Agregar puntos en línea recta después de la curva constexpr int EXTRA_PIXELS = 40; SDL_FPoint last_point = sun_path_.back(); for (int i = 1; i <= EXTRA_PIXELS; ++i) { sun_path_.push_back({last_point.x, last_point.y + i}); } } // Precalcula el vector con el recorrido de la luna void Background::createMoonPath() { constexpr float CENTER_X = 100; const float CENTER_Y = base_ - 50; constexpr float RADIUS = 140; constexpr double STEP = 0.01; const int NUM_STEPS = static_cast((M_PI / 2) / STEP) + 1; constexpr float FREEZE_PERCENTAGE = 0.2F; // Porcentaje final del recorrido que se mantiene fijo const int FREEZE_START_INDEX = static_cast(NUM_STEPS * (1.0F - FREEZE_PERCENTAGE)); for (int i = 0; i < NUM_STEPS; ++i) { double theta = i * STEP; float x = CENTER_X + (RADIUS * cos(theta)); float y = CENTER_Y - (RADIUS * sin(theta)); if (i >= FREEZE_START_INDEX && !moon_path_.empty()) { moon_path_.push_back(moon_path_.back()); // Repite el último punto válido } else { moon_path_.push_back({x, y}); } } }