#define _USE_MATH_DEFINES #include "background.h" #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 "moving_sprite.h" // Para MovingSprite #include "param.h" // Para Param, ParamBackground, param #include "resource.h" // Para Resource #include "screen.h" // Para Screen #include "sprite.h" // Para Sprite #include "texture.h" // Para Texture // 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")), grass_texture_(Resource::get()->getTexture("game_grass.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")), 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), 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); grass_sprite_ = std::make_unique(grass_texture_, 0, 0, grass_texture_->getWidth(), grass_texture_->getHeight() / 2); sun_sprite_ = std::make_unique(sun_texture_); moon_sprite_ = std::make_unique(moon_texture_); } // Configura las propiedades iniciales de los sprites void Background::initializeSpriteProperties() { constexpr float TOP_CLOUDS_SPEED = 0.1F; constexpr float BOTTOM_CLOUDS_SPEED = 0.05F; top_clouds_sprite_a_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight()); top_clouds_sprite_a_->setVelX(-TOP_CLOUDS_SPEED); top_clouds_sprite_b_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight()); top_clouds_sprite_b_->setVelX(-TOP_CLOUDS_SPEED); bottom_clouds_sprite_a_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight()); bottom_clouds_sprite_a_->setVelX(-BOTTOM_CLOUDS_SPEED); bottom_clouds_sprite_b_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight()); bottom_clouds_sprite_b_->setVelX(-BOTTOM_CLOUDS_SPEED); buildings_sprite_->setY(base_ - buildings_sprite_->getHeight()); grass_sprite_->setY(base_ - grass_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(); } // Actualiza el valor de alpha updateAlphaColorTexture(); // Actualiza las nubes updateClouds(delta_time); // Calcula el frame de la hierba grass_sprite_->setSpriteClip(0, (10 * (counter_ / 20 % 2)), 320, 10); // 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_)); // Incrementa el contador ++counter_; // 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) { 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; // 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() { // Si el juego está completado, reduce la progresión gradualmente if (state_ == State::COMPLETED) { if (progress_ > MINIMUM_COMPLETED_PROGRESS) { progress_ -= COMPLETED_REDUCTION_RATE; } else { 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 constexpr float CLOUDS_INITIAL_SPEED = 0.05F; constexpr float CLOUDS_FINAL_SPEED = 2.00F - CLOUDS_INITIAL_SPEED; // Velocidad base según progreso (de -0.05 a -2.00) float base_clouds_speed = (-CLOUDS_INITIAL_SPEED) + (-CLOUDS_FINAL_SPEED * (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}); } } }