reestructuració
This commit is contained in:
@@ -0,0 +1,575 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "background.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // 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 <algorithm> // Para clamp, min, max
|
||||
#include <cmath> // Para M_PI, cos, sin
|
||||
#include <string> // Para basic_string
|
||||
#include <utility> // Para move
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
#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 easeOutCubic
|
||||
|
||||
// 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<AnimatedSprite>(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{.x = 0, .y = 0, .w = static_cast<float>(gradients_texture_->getWidth() / 2), .h = static_cast<float>(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<float>(top_clouds_texture_->getWidth()), .h = TOP_CLOUDS_TEXTURE_HEIGHT};
|
||||
bottom_clouds_rect_[i] = {.x = 0, .y = i * BOTTOM_CLOUDS_TEXTURE_HEIGHT, .w = static_cast<float>(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<MovingSprite>(top_clouds_texture_, (SDL_FRect){.x = 0, .y = TOP_CLOUDS_Y, .w = rect_.w, .h = static_cast<float>(top_clouds_texture_->getHeight())});
|
||||
top_clouds_sprite_b_ = std::make_unique<MovingSprite>(top_clouds_texture_, (SDL_FRect){.x = rect_.w, .y = TOP_CLOUDS_Y, .w = rect_.w, .h = static_cast<float>(top_clouds_texture_->getHeight())});
|
||||
|
||||
bottom_clouds_sprite_a_ = std::make_unique<MovingSprite>(bottom_clouds_texture_, (SDL_FRect){.x = 0, .y = BOTTOM_CLOUDS_Y, .w = rect_.w, .h = static_cast<float>(bottom_clouds_texture_->getHeight())});
|
||||
bottom_clouds_sprite_b_ = std::make_unique<MovingSprite>(bottom_clouds_texture_, (SDL_FRect){.x = rect_.w, .y = BOTTOM_CLOUDS_Y, .w = rect_.w, .h = static_cast<float>(bottom_clouds_texture_->getHeight())});
|
||||
|
||||
buildings_sprite_ = std::make_unique<Sprite>(buildings_texture_);
|
||||
gradient_sprite_ = std::make_unique<Sprite>(gradients_texture_, 0, 0, rect_.w, rect_.h);
|
||||
sun_sprite_ = std::make_unique<Sprite>(sun_texture_);
|
||||
moon_sprite_ = std::make_unique<Sprite>(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(delta_time);
|
||||
|
||||
// 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 - static_cast<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<size_t>(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<size_t>(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<double>(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<int>(GRADIENT_NUMBER_FLOAT);
|
||||
|
||||
gradient_number_ = static_cast<size_t>(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<size_t>(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<size_t>(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 delta_time) {
|
||||
// Mueve las nubes
|
||||
top_clouds_sprite_a_->update(delta_time);
|
||||
top_clouds_sprite_b_->update(delta_time);
|
||||
bottom_clouds_sprite_a_->update(delta_time);
|
||||
bottom_clouds_sprite_b_->update(delta_time);
|
||||
|
||||
// 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 (time-based)
|
||||
void Background::updateAlphaColorTexture(float delta_time) {
|
||||
// 1. Si ya hemos llegado al destino, no hacemos nada.
|
||||
if (alpha_color_texture_ == previous_alpha_color_texture_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Define la velocidad del cambio (p. ej., 150 unidades de alfa por segundo).
|
||||
// Puedes ajustar este valor para que la transición sea más rápida o lenta.
|
||||
constexpr float ALPHA_TRANSITION_SPEED = 150.0F;
|
||||
|
||||
// 3. Determina la dirección del cambio (subir o bajar el alfa)
|
||||
if (alpha_color_texture_ > previous_alpha_color_texture_) {
|
||||
// Aumentar el alfa
|
||||
current_alpha_float_ += ALPHA_TRANSITION_SPEED * delta_time;
|
||||
// Nos aseguramos de no pasarnos del objetivo
|
||||
current_alpha_float_ = std::min(current_alpha_float_, static_cast<float>(alpha_color_texture_));
|
||||
} else {
|
||||
// Disminuir el alfa
|
||||
current_alpha_float_ -= ALPHA_TRANSITION_SPEED * delta_time;
|
||||
// Nos aseguramos de no quedarnos cortos del objetivo
|
||||
current_alpha_float_ = std::max(current_alpha_float_, static_cast<float>(alpha_color_texture_));
|
||||
}
|
||||
|
||||
// 4. Actualiza el valor entero solo si ha cambiado lo suficiente
|
||||
// Usamos std::round para un redondeo más natural.
|
||||
const auto NEW_ALPHA = static_cast<size_t>(std::round(current_alpha_float_));
|
||||
|
||||
if (NEW_ALPHA != previous_alpha_color_texture_) {
|
||||
previous_alpha_color_texture_ = NEW_ALPHA;
|
||||
// SDL espera un Uint8 (0-255), así que hacemos un cast seguro.
|
||||
SDL_SetTextureAlphaMod(color_texture_, static_cast<Uint8>(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<int>((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 = x, .y = 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({.x = last_point.x, .y = 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<int>((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<int>(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 = x, .y = y});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_FPoint, SDL_Texture, SDL_Renderer
|
||||
|
||||
#include <array> // Para array
|
||||
#include <cstddef> // Para size_t
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para unique_ptr, shared_ptr
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
|
||||
class MovingSprite;
|
||||
class Sprite;
|
||||
class Texture;
|
||||
class AnimatedSprite;
|
||||
|
||||
// --- Clase Background: gestiona el fondo de la sección jugable ---
|
||||
class Background {
|
||||
public:
|
||||
// --- Enums ---
|
||||
enum class State {
|
||||
NORMAL, // Progresión normal del día
|
||||
COMPLETED // Reducción gradual de la actividad
|
||||
};
|
||||
|
||||
// --- Tipos ---
|
||||
using ProgressCallback = std::function<void(float)>; // Callback para sincronización
|
||||
|
||||
// --- Constructor y destructor ---
|
||||
Background(float total_progress_to_complete = 6100.0F); // Constructor principal
|
||||
~Background(); // Destructor
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Actualiza la lógica del objeto
|
||||
void render(); // Dibuja el objeto
|
||||
void reset(); // Reinicia la progresión
|
||||
|
||||
// --- Configuración ---
|
||||
void setPos(SDL_FRect pos); // Establece la posición del objeto
|
||||
void setState(State new_state); // Cambia el estado del fondo
|
||||
void setProgressCallback(ProgressCallback callback); // Establece callback para sincronización
|
||||
void removeProgressCallback(); // Elimina el callback
|
||||
void setManualMode(bool manual); // Activa/desactiva el modo manual
|
||||
void setCloudsSpeed(float value); // Ajusta la velocidad de las nubes
|
||||
void setGradientNumber(int value); // Establece el degradado de fondo
|
||||
void setTransition(float value); // Ajusta la transición entre texturas
|
||||
void setSunProgression(float progress); // Establece la posición del sol
|
||||
void setMoonProgression(float progress); // Establece la posición de la luna
|
||||
void setColor(Color color); // Establece el color de atenuación
|
||||
void setAlpha(int alpha); // Ajusta la transparencia del fondo
|
||||
|
||||
// --- Control de progresión ---
|
||||
void incrementProgress(float amount = 1.0F); // Incrementa la progresión interna
|
||||
void setProgress(float absolute_progress); // Establece la progresión absoluta
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getProgress() const -> float { return progress_; } // Obtiene el progreso actual
|
||||
[[nodiscard]] auto getState() const -> State { return state_; } // Obtiene el estado actual
|
||||
[[nodiscard]] auto getCurrentGradient() const -> int { return static_cast<int>(gradient_number_); } // Obtiene el gradiente actual
|
||||
|
||||
private:
|
||||
// --- Constantes ---
|
||||
static constexpr size_t STAGES = 4; // Número de etapas
|
||||
static constexpr float MINIMUM_COMPLETED_PROGRESS_PERCENTAGE = 0.05F; // Porcentaje mínimo completado (10%)
|
||||
static constexpr float SUN_COMPLETION_FACTOR = 0.5F; // Factor de completado del sol
|
||||
static constexpr float COMPLETION_TRANSITION_DURATION_S = 3.0F; // Duración de la transición de completado en segundos
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Renderer* renderer_; // Renderizador de la ventana
|
||||
SDL_Texture* canvas_; // Textura para componer el fondo
|
||||
SDL_Texture* color_texture_; // Textura para atenuar el fondo
|
||||
std::shared_ptr<Texture> buildings_texture_; // Textura de edificios
|
||||
std::shared_ptr<Texture> top_clouds_texture_; // Textura de nubes superiores
|
||||
std::shared_ptr<Texture> bottom_clouds_texture_; // Textura de nubes inferiores
|
||||
std::shared_ptr<Texture> gradients_texture_; // Textura de gradientes
|
||||
std::shared_ptr<Texture> sun_texture_; // Textura del sol
|
||||
std::shared_ptr<Texture> moon_texture_; // Textura de la luna
|
||||
std::unique_ptr<MovingSprite> top_clouds_sprite_a_; // Sprite de nubes superiores A
|
||||
std::unique_ptr<MovingSprite> top_clouds_sprite_b_; // Sprite de nubes superiores B
|
||||
std::unique_ptr<MovingSprite> bottom_clouds_sprite_a_; // Sprite de nubes inferiores A
|
||||
std::unique_ptr<MovingSprite> bottom_clouds_sprite_b_; // Sprite de nubes inferiores B
|
||||
std::unique_ptr<Sprite> buildings_sprite_; // Sprite de edificios
|
||||
std::unique_ptr<Sprite> gradient_sprite_; // Sprite de gradiente
|
||||
std::unique_ptr<Sprite> sun_sprite_; // Sprite del sol
|
||||
std::unique_ptr<Sprite> moon_sprite_; // Sprite de la luna
|
||||
std::unique_ptr<AnimatedSprite> grass_sprite_; // Sprite con la hierba
|
||||
|
||||
// --- Variables de configuración ---
|
||||
const float total_progress_to_complete_; // Progreso total para completar
|
||||
const float progress_per_stage_; // Progreso por etapa
|
||||
const float sun_completion_progress_; // Progreso de completado del sol
|
||||
const float minimum_completed_progress_; // Progreso mínimo calculado dinámicamente
|
||||
ProgressCallback progress_callback_; // Callback para notificar cambios de progreso
|
||||
|
||||
// --- Variables de estado ---
|
||||
std::vector<SDL_FPoint> sun_path_; // Recorrido del sol
|
||||
std::vector<SDL_FPoint> moon_path_; // Recorrido de la luna
|
||||
std::array<SDL_FRect, STAGES> gradient_rect_; // Fondos degradados
|
||||
std::array<SDL_FRect, 4> top_clouds_rect_; // Nubes superiores
|
||||
std::array<SDL_FRect, 4> bottom_clouds_rect_; // Nubes inferiores
|
||||
SDL_FRect rect_; // Tamaño del objeto
|
||||
SDL_FRect src_rect_; // Parte del objeto para copiar en pantalla
|
||||
SDL_FRect dst_rect_; // Posición en pantalla donde se copia el objeto
|
||||
Color attenuate_color_; // Color de atenuación
|
||||
State state_ = State::NORMAL; // Estado actual
|
||||
float progress_ = 0.0F; // Progresión interna
|
||||
float clouds_speed_ = 0; // Velocidad de las nubes
|
||||
float transition_ = 0; // Porcentaje de transición
|
||||
float current_alpha_float_ = 0.0F; // Acumulador para el valor alfa preciso
|
||||
size_t gradient_number_ = 0; // Índice de fondo degradado
|
||||
size_t alpha_color_texture_ = 0; // Transparencia de atenuación
|
||||
size_t previous_alpha_color_texture_ = 0; // Transparencia anterior
|
||||
size_t sun_index_ = 0; // Índice del recorrido del sol
|
||||
size_t moon_index_ = 0; // Índice del recorrido de la luna
|
||||
int base_ = 0; // Posición base del fondo
|
||||
Uint8 alpha_ = 0; // Transparencia entre fases
|
||||
bool manual_mode_ = false; // Si está en modo manual
|
||||
|
||||
// --- Variables para transición suave de completado ---
|
||||
float completion_transition_timer_ = 0.0F; // Timer para la transición de completado
|
||||
float completion_initial_progress_ = 0.0F; // Progreso inicial al entrar en estado completado
|
||||
|
||||
// --- Métodos internos ---
|
||||
void initializePaths(); // Inicializa las rutas del sol y la luna
|
||||
void initializeRects(); // Inicializa los rectángulos de gradientes y nubes
|
||||
void initializeSprites(); // Crea los sprites
|
||||
void initializeSpriteProperties(); // Configura las propiedades iniciales de los sprites
|
||||
void initializeTextures(); // Inicializa las texturas de renderizado
|
||||
void updateProgression(float delta_time); // Actualiza la progresión y calcula transiciones
|
||||
void updateCloudsSpeed(); // Actualiza la velocidad de las nubes según el estado
|
||||
void renderGradient(); // Dibuja el gradiente de fondo
|
||||
void renderTopClouds(); // Dibuja las nubes superiores
|
||||
void renderBottomClouds(); // Dibuja las nubes inferiores
|
||||
void fillCanvas(); // Compone todos los elementos en la textura
|
||||
void updateAlphaColorTexture(float delta_time); // Actualiza el alpha de la textura de atenuación
|
||||
void updateClouds(float delta_time); // Actualiza el movimiento de las nubes (time-based)
|
||||
void createSunPath(); // Precalcula el recorrido del sol
|
||||
void createMoonPath(); // Precalcula el recorrido de la luna
|
||||
};
|
||||
@@ -0,0 +1,551 @@
|
||||
#include "fade.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
#include "color.hpp"
|
||||
#include "param.hpp"
|
||||
#include "screen.hpp"
|
||||
|
||||
// Constructor
|
||||
Fade::Fade()
|
||||
: renderer_(Screen::get()->getRenderer()) {
|
||||
// Crea la textura donde dibujar el fade
|
||||
backbuffer_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
|
||||
SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Inicializa las variables
|
||||
init();
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Fade::~Fade() {
|
||||
SDL_DestroyTexture(backbuffer_);
|
||||
}
|
||||
|
||||
// Inicializa las variables
|
||||
void Fade::init() {
|
||||
type_ = Type::CENTER;
|
||||
mode_ = Mode::OUT;
|
||||
r_ = 0;
|
||||
g_ = 0;
|
||||
b_ = 0;
|
||||
a_ = 0;
|
||||
post_duration_ = 0;
|
||||
post_start_time_ = 0;
|
||||
pre_duration_ = 0;
|
||||
pre_start_time_ = 0;
|
||||
fading_duration_ = param.fade.random_squares_duration_ms; // Duración por defecto para FADING
|
||||
fading_start_time_ = 0;
|
||||
num_squares_width_ = param.fade.num_squares_width;
|
||||
num_squares_height_ = param.fade.num_squares_height;
|
||||
square_transition_duration_ = fading_duration_ / 4; // 25% del tiempo total para la transición individual
|
||||
}
|
||||
|
||||
// Resetea algunas variables para volver a hacer el fade sin perder ciertos parametros
|
||||
void Fade::reset() {
|
||||
state_ = State::NOT_ENABLED;
|
||||
post_start_time_ = 0;
|
||||
pre_start_time_ = 0;
|
||||
fading_start_time_ = 0;
|
||||
value_ = 0;
|
||||
// La duración del fade se mantiene, se puede cambiar con setDuration()
|
||||
}
|
||||
|
||||
// Pinta una transición en pantalla
|
||||
void Fade::render() {
|
||||
if (state_ != State::NOT_ENABLED) {
|
||||
// Para fade IN terminado, no renderizar (auto-desactivación visual)
|
||||
if (state_ == State::FINISHED && mode_ == Mode::IN) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_RenderTexture(renderer_, backbuffer_, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables internas
|
||||
void Fade::update(float delta_time) {
|
||||
switch (state_) {
|
||||
case State::PRE:
|
||||
updatePreState();
|
||||
break;
|
||||
case State::FADING:
|
||||
updateFadingState();
|
||||
break;
|
||||
case State::POST:
|
||||
updatePostState();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::updatePreState() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - pre_start_time_;
|
||||
|
||||
if (std::cmp_greater_equal(elapsed_time, pre_duration_)) {
|
||||
state_ = State::FADING;
|
||||
fading_start_time_ = SDL_GetTicks(); // Inicia el temporizador del fade AQUI
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::updateFadingState() {
|
||||
switch (type_) {
|
||||
case Type::FULLSCREEN:
|
||||
updateFullscreenFade();
|
||||
break;
|
||||
case Type::CENTER:
|
||||
updateCenterFade();
|
||||
break;
|
||||
case Type::RANDOM_SQUARE:
|
||||
updateRandomSquareFade();
|
||||
break;
|
||||
case Type::RANDOM_SQUARE2:
|
||||
updateRandomSquare2Fade();
|
||||
break;
|
||||
case Type::DIAGONAL:
|
||||
updateDiagonalFade();
|
||||
break;
|
||||
case Type::VENETIAN:
|
||||
updateVenetianFade();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::changeToPostState() {
|
||||
state_ = State::POST;
|
||||
post_start_time_ = SDL_GetTicks();
|
||||
}
|
||||
|
||||
void Fade::updatePostState() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - post_start_time_;
|
||||
|
||||
if (std::cmp_greater_equal(elapsed_time, post_duration_)) {
|
||||
state_ = State::FINISHED;
|
||||
}
|
||||
|
||||
// Mantener el estado final del fade
|
||||
Uint8 post_alpha = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
|
||||
cleanBackbuffer(r_, g_, b_, post_alpha);
|
||||
}
|
||||
|
||||
void Fade::updateFullscreenFade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
float progress = std::min(static_cast<float>(elapsed_time) / fading_duration_, 1.0F);
|
||||
|
||||
// Modifica la transparencia basada en el progreso
|
||||
auto current_alpha = static_cast<Uint8>(progress * 255.0F);
|
||||
a_ = (mode_ == Mode::OUT) ? current_alpha : 255 - current_alpha;
|
||||
SDL_SetTextureAlphaMod(backbuffer_, a_);
|
||||
|
||||
value_ = static_cast<int>(progress * 100);
|
||||
|
||||
// Comprueba si ha terminado
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::updateCenterFade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
float progress = std::min(static_cast<float>(elapsed_time) / fading_duration_, 1.0F);
|
||||
|
||||
// Calcula la altura de las barras
|
||||
float rect_height = progress * (param.game.height / 2.0F);
|
||||
|
||||
if (mode_ == Mode::IN) {
|
||||
rect_height = (param.game.height / 2.0F) - rect_height;
|
||||
}
|
||||
|
||||
rect1_.h = rect_height;
|
||||
rect2_.h = rect_height;
|
||||
rect2_.y = param.game.height - rect_height;
|
||||
|
||||
drawCenterFadeRectangles();
|
||||
value_ = static_cast<int>(progress * 100);
|
||||
|
||||
// Comprueba si ha terminado
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
a_ = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::drawCenterFadeRectangles() {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
cleanBackbuffer(r_, g_, b_, 0); // Limpiar para modo IN
|
||||
SDL_SetRenderDrawColor(renderer_, r_, g_, b_, 255);
|
||||
|
||||
SDL_RenderFillRect(renderer_, &rect1_);
|
||||
SDL_RenderFillRect(renderer_, &rect2_);
|
||||
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
void Fade::updateRandomSquareFade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
float progress = std::min(static_cast<float>(elapsed_time) / fading_duration_, 1.0F);
|
||||
|
||||
// Calcula cuántos cuadrados deberían estar activos
|
||||
int total_squares = num_squares_width_ * num_squares_height_;
|
||||
int active_squares = static_cast<int>(progress * total_squares);
|
||||
|
||||
drawRandomSquares(active_squares);
|
||||
|
||||
value_ = static_cast<int>(progress * 100);
|
||||
|
||||
// Comprueba si ha terminado
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::updateRandomSquare2Fade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
int total_squares = num_squares_width_ * num_squares_height_;
|
||||
|
||||
int activation_time = fading_duration_ - square_transition_duration_;
|
||||
activation_time = std::max(activation_time, square_transition_duration_);
|
||||
|
||||
int squares_to_activate = 0;
|
||||
if (mode_ == Mode::OUT) {
|
||||
if (std::cmp_less(elapsed_time, activation_time)) {
|
||||
float activation_progress = static_cast<float>(elapsed_time) / activation_time;
|
||||
squares_to_activate = static_cast<int>(activation_progress * total_squares);
|
||||
} else {
|
||||
squares_to_activate = total_squares;
|
||||
}
|
||||
for (int i = 0; i < squares_to_activate; ++i) {
|
||||
if (square_age_[i] == -1) { square_age_[i] = elapsed_time; }
|
||||
}
|
||||
} else {
|
||||
squares_to_activate = total_squares;
|
||||
float activation_progress = static_cast<float>(elapsed_time) / activation_time;
|
||||
int squares_starting_transition = std::min(total_squares, std::max(1, static_cast<int>(activation_progress * total_squares)));
|
||||
for (int i = 0; i < squares_starting_transition; ++i) {
|
||||
if (square_age_[i] == -1) { square_age_[i] = elapsed_time; }
|
||||
}
|
||||
}
|
||||
|
||||
drawRandomSquares2();
|
||||
value_ = calculateValue(0, total_squares, squares_to_activate);
|
||||
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
Uint8 final_alpha = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
cleanBackbuffer(r_, g_, b_, final_alpha);
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::updateDiagonalFade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
|
||||
int activation_time = fading_duration_ - square_transition_duration_;
|
||||
activation_time = std::max(activation_time, square_transition_duration_);
|
||||
|
||||
int max_diagonal = num_squares_width_ + num_squares_height_ - 1;
|
||||
int active_diagonals = 0;
|
||||
if (mode_ == Mode::OUT) {
|
||||
if (std::cmp_less(elapsed_time, activation_time)) {
|
||||
float activation_progress = static_cast<float>(elapsed_time) / activation_time;
|
||||
active_diagonals = static_cast<int>(activation_progress * max_diagonal);
|
||||
} else {
|
||||
active_diagonals = max_diagonal;
|
||||
}
|
||||
for (int diagonal = 0; diagonal < active_diagonals; ++diagonal) {
|
||||
activateDiagonal(diagonal, elapsed_time);
|
||||
}
|
||||
} else {
|
||||
active_diagonals = max_diagonal;
|
||||
if (std::cmp_less(elapsed_time, activation_time)) {
|
||||
float activation_progress = static_cast<float>(elapsed_time) / activation_time;
|
||||
int diagonals_starting_transition = static_cast<int>(activation_progress * max_diagonal);
|
||||
for (int diagonal = 0; diagonal < diagonals_starting_transition; ++diagonal) {
|
||||
activateDiagonal(diagonal, elapsed_time);
|
||||
}
|
||||
} else {
|
||||
for (int diagonal = 0; diagonal < max_diagonal; ++diagonal) {
|
||||
activateDiagonal(diagonal, elapsed_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawDiagonal();
|
||||
value_ = calculateValue(0, max_diagonal, active_diagonals);
|
||||
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
Uint8 final_alpha = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
cleanBackbuffer(r_, g_, b_, final_alpha);
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::activateDiagonal(int diagonal_index, Uint32 current_time) {
|
||||
for (int x = 0; x < num_squares_width_; ++x) {
|
||||
int y = diagonal_index - x;
|
||||
if (y >= 0 && y < num_squares_height_) {
|
||||
int index = (y * num_squares_width_) + x;
|
||||
if (index >= 0 && std::cmp_less(index, square_age_.size()) && square_age_[index] == -1) {
|
||||
square_age_[index] = current_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::drawDiagonal() {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_BlendMode blend_mode;
|
||||
SDL_GetRenderDrawBlendMode(renderer_, &blend_mode);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||
|
||||
Uint32 current_time = SDL_GetTicks() - fading_start_time_;
|
||||
for (size_t i = 0; i < square_.size(); ++i) {
|
||||
Uint8 current_alpha = 0;
|
||||
if (square_age_[i] == -1) {
|
||||
current_alpha = (mode_ == Mode::OUT) ? 0 : a_;
|
||||
} else {
|
||||
Uint32 square_elapsed = current_time - square_age_[i];
|
||||
float progress = std::min(static_cast<float>(square_elapsed) / square_transition_duration_, 1.0F);
|
||||
current_alpha = (mode_ == Mode::OUT) ? static_cast<Uint8>(progress * a_) : static_cast<Uint8>((1.0F - progress) * a_);
|
||||
}
|
||||
if (current_alpha > 0) {
|
||||
SDL_SetRenderDrawColor(renderer_, r_, g_, b_, current_alpha);
|
||||
SDL_RenderFillRect(renderer_, &square_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer_, blend_mode);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
void Fade::drawRandomSquares(int active_count) {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
|
||||
// El fondo se prepara en activate()
|
||||
SDL_BlendMode blend_mode;
|
||||
SDL_GetRenderDrawBlendMode(renderer_, &blend_mode);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_NONE);
|
||||
SDL_SetRenderDrawColor(renderer_, r_, g_, b_, a_);
|
||||
|
||||
// Dibuja solo los cuadrados activos
|
||||
for (int i = 0; i < active_count && std::cmp_less(i, square_.size()); ++i) {
|
||||
SDL_RenderFillRect(renderer_, &square_[i]);
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer_, blend_mode);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
void Fade::drawRandomSquares2() {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_BlendMode blend_mode;
|
||||
SDL_GetRenderDrawBlendMode(renderer_, &blend_mode);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||
|
||||
Uint32 current_time = SDL_GetTicks() - fading_start_time_;
|
||||
for (size_t i = 0; i < square_.size(); ++i) {
|
||||
Uint8 current_alpha = 0;
|
||||
if (square_age_[i] == -1) {
|
||||
current_alpha = (mode_ == Mode::OUT) ? 0 : a_;
|
||||
} else {
|
||||
Uint32 square_elapsed = current_time - square_age_[i];
|
||||
float progress = std::min(static_cast<float>(square_elapsed) / square_transition_duration_, 1.0F);
|
||||
current_alpha = (mode_ == Mode::OUT) ? static_cast<Uint8>(progress * a_) : static_cast<Uint8>((1.0F - progress) * a_);
|
||||
}
|
||||
if (current_alpha > 0) {
|
||||
SDL_SetRenderDrawColor(renderer_, r_, g_, b_, current_alpha);
|
||||
SDL_RenderFillRect(renderer_, &square_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer_, blend_mode);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
void Fade::updateVenetianFade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
float progress = std::min(static_cast<float>(elapsed_time) / fading_duration_, 1.0F);
|
||||
|
||||
// Calcula la altura de las persianas
|
||||
float rect_height = progress * param.fade.venetian_size;
|
||||
if (mode_ == Mode::IN) {
|
||||
rect_height = param.fade.venetian_size - rect_height;
|
||||
}
|
||||
|
||||
for (auto& rect : square_) {
|
||||
rect.h = rect_height;
|
||||
}
|
||||
|
||||
drawVenetianBlinds();
|
||||
value_ = static_cast<int>(progress * 100);
|
||||
|
||||
// Comprueba si ha terminado
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::drawVenetianBlinds() {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
|
||||
// Limpia la textura con el color base (transparente para OUT, opaco para IN)
|
||||
Uint8 initial_alpha = (mode_ == Mode::OUT) ? 0 : 255;
|
||||
cleanBackbuffer(r_, g_, b_, initial_alpha);
|
||||
|
||||
SDL_BlendMode blend_mode;
|
||||
SDL_GetRenderDrawBlendMode(renderer_, &blend_mode);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_NONE);
|
||||
|
||||
// Dibuja las persianas con el color opuesto al fondo
|
||||
Uint8 draw_alpha = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
SDL_SetRenderDrawColor(renderer_, r_, g_, b_, draw_alpha);
|
||||
|
||||
for (const auto& rect : square_) {
|
||||
SDL_RenderFillRect(renderer_, &rect);
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer_, blend_mode);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
// Activa el fade
|
||||
void Fade::activate() {
|
||||
if (state_ != State::NOT_ENABLED) {
|
||||
return;
|
||||
}
|
||||
state_ = State::PRE;
|
||||
pre_start_time_ = SDL_GetTicks();
|
||||
value_ = 0;
|
||||
|
||||
// Preparación inicial de cada tipo
|
||||
switch (type_) {
|
||||
/*case Type::FULLSCREEN:
|
||||
cleanBackbuffer(r_, g_, b_, (mode_ == Mode::OUT) ? 0 : 255);
|
||||
SDL_SetTextureAlphaMod(backbuffer_, (mode_ == Mode::OUT) ? 255 : 0);
|
||||
break;*/
|
||||
|
||||
case Type::FULLSCREEN: {
|
||||
// La textura en sí siempre debe ser de un color sólido y opaco.
|
||||
// La transparencia se gestionará con la modulación de alfa.
|
||||
cleanBackbuffer(r_, g_, b_, 255);
|
||||
|
||||
// Ahora, inicializamos la modulación de alfa correctamente:
|
||||
// - IN: Empieza opaco (255) y se desvanece a transparente.
|
||||
// - OUT: Empieza transparente (0) y se desvanece a opaco.
|
||||
const Uint8 INITIAL_ALPHA = (mode_ == Mode::IN) ? 255 : 0;
|
||||
SDL_SetTextureAlphaMod(backbuffer_, INITIAL_ALPHA);
|
||||
break;
|
||||
}
|
||||
|
||||
case Type::CENTER:
|
||||
rect1_ = {.x = 0, .y = 0, .w = param.game.width, .h = 0};
|
||||
rect2_ = {.x = 0, .y = param.game.height, .w = param.game.width, .h = 0};
|
||||
a_ = 255;
|
||||
break;
|
||||
|
||||
case Type::RANDOM_SQUARE: {
|
||||
rect1_ = {.x = 0, .y = 0, .w = static_cast<float>(param.game.width / num_squares_width_), .h = static_cast<float>(param.game.height / num_squares_height_)};
|
||||
square_.clear();
|
||||
for (int i = 0; i < num_squares_width_ * num_squares_height_; ++i) {
|
||||
rect1_.x = (i % num_squares_width_) * rect1_.w;
|
||||
rect1_.y = (i / num_squares_width_) * rect1_.h;
|
||||
square_.push_back(rect1_);
|
||||
}
|
||||
auto num = square_.size();
|
||||
while (num > 1) {
|
||||
auto num_arreu = rand() % num;
|
||||
std::swap(square_[num_arreu], square_[--num]);
|
||||
}
|
||||
a_ = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
cleanBackbuffer(r_, g_, b_, (mode_ == Mode::OUT) ? 0 : 255);
|
||||
break;
|
||||
}
|
||||
|
||||
case Type::RANDOM_SQUARE2:
|
||||
case Type::DIAGONAL: {
|
||||
rect1_ = {.x = 0, .y = 0, .w = static_cast<float>(param.game.width / num_squares_width_), .h = static_cast<float>(param.game.height / num_squares_height_)};
|
||||
square_.clear();
|
||||
square_age_.assign(num_squares_width_ * num_squares_height_, -1);
|
||||
for (int i = 0; i < num_squares_width_ * num_squares_height_; ++i) {
|
||||
rect1_.x = (i % num_squares_width_) * rect1_.w;
|
||||
rect1_.y = (i / num_squares_width_) * rect1_.h;
|
||||
square_.push_back(rect1_);
|
||||
}
|
||||
|
||||
if (type_ == Type::RANDOM_SQUARE2) {
|
||||
auto num = square_.size();
|
||||
while (num > 1) {
|
||||
auto num_arreu = rand() % num;
|
||||
std::swap(square_[num_arreu], square_[--num]);
|
||||
// No es necesario desordenar square_age_ ya que todos son -1
|
||||
}
|
||||
}
|
||||
Uint8 initial_alpha = (mode_ == Mode::OUT) ? 0 : 255;
|
||||
cleanBackbuffer(r_, g_, b_, initial_alpha);
|
||||
a_ = 255;
|
||||
square_transition_duration_ = std::max(fading_duration_ / 4, 100);
|
||||
break;
|
||||
}
|
||||
|
||||
case Type::VENETIAN: {
|
||||
square_.clear();
|
||||
rect1_ = {.x = 0, .y = 0, .w = param.game.width, .h = (mode_ == Mode::OUT) ? 0.0F : param.fade.venetian_size};
|
||||
const int MAX = param.game.height / param.fade.venetian_size;
|
||||
for (int i = 0; i < MAX; ++i) {
|
||||
rect1_.y = i * param.fade.venetian_size;
|
||||
square_.push_back(rect1_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el color del fade
|
||||
void Fade::setColor(Uint8 r, Uint8 g, Uint8 b) {
|
||||
r_ = r;
|
||||
g_ = g;
|
||||
b_ = b;
|
||||
}
|
||||
|
||||
// Establece el color del fade
|
||||
void Fade::setColor(Color color) {
|
||||
r_ = color.r;
|
||||
g_ = color.g;
|
||||
b_ = color.b;
|
||||
}
|
||||
|
||||
// Limpia el backbuffer
|
||||
void Fade::cleanBackbuffer(Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
SDL_SetRenderDrawColor(renderer_, r, g, b, a);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
// Calcula el valor del estado del fade
|
||||
auto Fade::calculateValue(int min, int max, int current) -> int {
|
||||
if (current <= min) {
|
||||
return 0;
|
||||
}
|
||||
if (current >= max) {
|
||||
return 100;
|
||||
}
|
||||
return static_cast<int>(100.0 * (current - min) / (max - min));
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para Uint8, SDL_FRect, SDL_Renderer, SDL_Texture, Uint32
|
||||
|
||||
#include <vector> // Para vector
|
||||
|
||||
struct Color;
|
||||
|
||||
// --- Clase Fade: gestor de transiciones de fundido ---
|
||||
class Fade {
|
||||
public:
|
||||
// --- Enums ---
|
||||
enum class Type : Uint8 {
|
||||
FULLSCREEN = 0, // Fundido de pantalla completa
|
||||
CENTER = 1, // Fundido desde el centro
|
||||
RANDOM_SQUARE = 2, // Fundido con cuadrados aleatorios
|
||||
RANDOM_SQUARE2 = 3, // Fundido con cuadrados aleatorios (variante 2)
|
||||
DIAGONAL = 4, // Fundido diagonal desde esquina superior izquierda
|
||||
VENETIAN = 5, // Fundido tipo persiana veneciana
|
||||
};
|
||||
|
||||
enum class Mode : Uint8 {
|
||||
IN = 0, // Fundido de entrada
|
||||
OUT = 1, // Fundido de salida
|
||||
};
|
||||
|
||||
enum class State : Uint8 {
|
||||
NOT_ENABLED = 0, // No activado
|
||||
PRE = 1, // Estado previo
|
||||
FADING = 2, // Fundiendo
|
||||
POST = 3, // Estado posterior
|
||||
FINISHED = 4, // Finalizado
|
||||
};
|
||||
|
||||
// --- Constructores y destructor ---
|
||||
Fade();
|
||||
~Fade();
|
||||
|
||||
// --- Métodos principales ---
|
||||
void reset(); // Resetea variables para reutilizar el fade
|
||||
void render(); // Dibuja la transición en pantalla
|
||||
void update(float delta_time = 0.0F); // Actualiza el estado interno
|
||||
void activate(); // Activa el fade
|
||||
|
||||
// --- Configuración ---
|
||||
void setColor(Uint8 r, Uint8 g, Uint8 b); // Establece el color RGB del fade
|
||||
void setColor(Color color); // Establece el color del fade
|
||||
void setType(Type type) { type_ = type; } // Establece el tipo de fade
|
||||
void setMode(Mode mode) { mode_ = mode; } // Establece el modo de fade
|
||||
void setDuration(int milliseconds) { fading_duration_ = milliseconds; } // Duración del estado FADING en milisegundos
|
||||
void setPostDuration(int milliseconds) { post_duration_ = milliseconds; } // Duración posterior al fade en milisegundos
|
||||
void setPreDuration(int milliseconds) { pre_duration_ = milliseconds; } // Duración previa al fade en milisegundos
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getValue() const -> int { return value_; }
|
||||
[[nodiscard]] auto isEnabled() const -> bool { return state_ != State::NOT_ENABLED; }
|
||||
[[nodiscard]] auto hasEnded() const -> bool { return state_ == State::FINISHED; }
|
||||
|
||||
private:
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Renderer* renderer_; // Renderizador de la ventana
|
||||
SDL_Texture* backbuffer_; // Backbuffer para efectos
|
||||
|
||||
// --- Variables de estado ---
|
||||
std::vector<SDL_FRect> square_; // Vector de cuadrados
|
||||
std::vector<int> square_age_; // Edad de cada cuadrado (para RANDOM_SQUARE2 y DIAGONAL)
|
||||
SDL_FRect rect1_, rect2_; // Rectángulos para efectos
|
||||
Type type_; // Tipo de fade
|
||||
Mode mode_; // Modo de fade
|
||||
State state_ = State::NOT_ENABLED; // Estado actual
|
||||
Uint8 r_, g_, b_, a_; // Color del fade (RGBA)
|
||||
int num_squares_width_; // Cuadrados en horizontal
|
||||
int num_squares_height_; // Cuadrados en vertical
|
||||
int square_transition_duration_; // Duración de transición de cada cuadrado en ms
|
||||
int fading_duration_{0}; // Duración del estado FADING en milisegundos
|
||||
Uint32 fading_start_time_ = 0; // Tiempo de inicio del estado FADING
|
||||
int post_duration_ = 0; // Duración posterior en milisegundos
|
||||
Uint32 post_start_time_ = 0; // Tiempo de inicio del estado POST
|
||||
int pre_duration_ = 0; // Duración previa en milisegundos
|
||||
Uint32 pre_start_time_ = 0; // Tiempo de inicio del estado PRE
|
||||
int value_ = 0; // Estado del fade (0-100)
|
||||
|
||||
// --- Inicialización y limpieza ---
|
||||
void init(); // Inicializa variables
|
||||
void cleanBackbuffer(Uint8 r, Uint8 g, Uint8 b, Uint8 a); // Limpia el backbuffer con un color RGBA
|
||||
|
||||
// --- Utilidades generales ---
|
||||
static auto calculateValue(int min, int max, int current) -> int; // Calcula el valor del fade entre dos límites
|
||||
|
||||
// --- Lógica de estado ---
|
||||
void updatePreState(); // Actualiza el estado previo al fade
|
||||
void updateFadingState(); // Actualiza el estado durante el fade
|
||||
void updatePostState(); // Actualiza el estado posterior al fade
|
||||
void changeToPostState(); // Cambia al estado POST e inicializa el tiempo
|
||||
|
||||
// --- Efectos de fundido (fade) ---
|
||||
void updateFullscreenFade(); // Actualiza el fundido de pantalla completa
|
||||
void updateCenterFade(); // Actualiza el fundido desde el centro
|
||||
void updateRandomSquareFade(); // Actualiza el fundido con cuadrados aleatorios
|
||||
void updateRandomSquare2Fade(); // Actualiza el fundido con cuadrados aleatorios (variante 2)
|
||||
void updateDiagonalFade(); // Actualiza el fundido diagonal
|
||||
void updateVenetianFade(); // Actualiza el fundido tipo persiana veneciana
|
||||
|
||||
// --- Dibujo de efectos visuales ---
|
||||
void drawCenterFadeRectangles(); // Dibuja los rectángulos del fundido central
|
||||
void drawRandomSquares(int active_count); // Dibuja los cuadrados aleatorios del fundido
|
||||
void drawRandomSquares2(); // Dibuja los cuadrados con transición de color (RANDOM_SQUARE2)
|
||||
void drawDiagonal(); // Dibuja los cuadrados con patrón diagonal
|
||||
void activateDiagonal(int diagonal_index, Uint32 current_time); // Activa una diagonal específica
|
||||
void drawVenetianBlinds(); // Dibuja las persianas venecianas del fundido
|
||||
};
|
||||
@@ -0,0 +1,252 @@
|
||||
#include "gif.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_LogError, SDL_LogCategory, SDL_LogInfo
|
||||
#include <cstring> // Para memcpy, size_t
|
||||
#include <iostream> // Para std::cout
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <string> // Para char_traits, operator==, basic_string, string
|
||||
|
||||
namespace GIF {
|
||||
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size) {
|
||||
std::memcpy(dst, buffer, size);
|
||||
buffer += size;
|
||||
}
|
||||
|
||||
void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out) {
|
||||
if (code_length < 2 || code_length > 12) {
|
||||
std::cout << "Invalid LZW code length: " << code_length << '\n';
|
||||
throw std::runtime_error("Invalid LZW code length");
|
||||
}
|
||||
|
||||
int i, bit;
|
||||
int prev = -1;
|
||||
std::vector<DictionaryEntry> dictionary;
|
||||
int dictionary_ind;
|
||||
unsigned int mask = 0x01;
|
||||
int reset_code_length = code_length;
|
||||
int clear_code = 1 << code_length;
|
||||
int stop_code = clear_code + 1;
|
||||
int match_len = 0;
|
||||
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
||||
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
||||
dictionary[dictionary_ind].prev = -1;
|
||||
dictionary[dictionary_ind].len = 1;
|
||||
}
|
||||
dictionary_ind += 2;
|
||||
|
||||
while (input_length > 0) {
|
||||
int code = 0;
|
||||
for (i = 0; i < (code_length + 1); i++) {
|
||||
if (input_length <= 0) {
|
||||
std::cout << "Unexpected end of input in decompress" << '\n';
|
||||
throw std::runtime_error("Unexpected end of input in decompress");
|
||||
}
|
||||
bit = ((*input & mask) != 0) ? 1 : 0;
|
||||
mask <<= 1;
|
||||
if (mask == 0x100) {
|
||||
mask = 0x01;
|
||||
input++;
|
||||
input_length--;
|
||||
}
|
||||
code |= (bit << i);
|
||||
}
|
||||
|
||||
if (code == clear_code) {
|
||||
code_length = reset_code_length;
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
||||
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
||||
dictionary[dictionary_ind].prev = -1;
|
||||
dictionary[dictionary_ind].len = 1;
|
||||
}
|
||||
dictionary_ind += 2;
|
||||
prev = -1;
|
||||
continue;
|
||||
} else if (code == stop_code) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (prev > -1 && code_length < 12) {
|
||||
if (code > dictionary_ind) {
|
||||
std::cout << "LZW error: code (" << code << ") exceeds dictionary_ind (" << dictionary_ind << ")" << '\n';
|
||||
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
|
||||
}
|
||||
|
||||
int ptr;
|
||||
if (code == dictionary_ind) {
|
||||
ptr = prev;
|
||||
while (dictionary[ptr].prev != -1)
|
||||
ptr = dictionary[ptr].prev;
|
||||
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
||||
} else {
|
||||
ptr = code;
|
||||
while (dictionary[ptr].prev != -1)
|
||||
ptr = dictionary[ptr].prev;
|
||||
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
||||
}
|
||||
dictionary[dictionary_ind].prev = prev;
|
||||
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
|
||||
dictionary_ind++;
|
||||
|
||||
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) {
|
||||
code_length++;
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
}
|
||||
}
|
||||
|
||||
prev = code;
|
||||
|
||||
if (code < 0 || static_cast<size_t>(code) >= dictionary.size()) {
|
||||
std::cout << "Invalid LZW code " << code << ", dictionary size " << static_cast<unsigned long>(dictionary.size()) << '\n';
|
||||
throw std::runtime_error("LZW error: invalid code encountered");
|
||||
}
|
||||
|
||||
int curCode = code;
|
||||
match_len = dictionary[curCode].len;
|
||||
while (curCode != -1) {
|
||||
out[dictionary[curCode].len - 1] = dictionary[curCode].byte;
|
||||
if (dictionary[curCode].prev == curCode) {
|
||||
std::cout << "Internal error; self-reference detected." << '\n';
|
||||
throw std::runtime_error("Internal error in decompress: self-reference");
|
||||
}
|
||||
curCode = dictionary[curCode].prev;
|
||||
}
|
||||
out += match_len;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::readSubBlocks(const uint8_t *&buffer) {
|
||||
std::vector<uint8_t> data;
|
||||
uint8_t block_size = *buffer;
|
||||
buffer++;
|
||||
while (block_size != 0) {
|
||||
data.insert(data.end(), buffer, buffer + block_size);
|
||||
buffer += block_size;
|
||||
block_size = *buffer;
|
||||
buffer++;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits) {
|
||||
ImageDescriptor image_descriptor;
|
||||
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
||||
|
||||
uint8_t lzw_code_size;
|
||||
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
|
||||
|
||||
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
|
||||
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
|
||||
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
|
||||
|
||||
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
|
||||
return uncompressed_data;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> Gif::loadPalette(const uint8_t *buffer) {
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
buffer += 6;
|
||||
|
||||
ScreenDescriptor screen_descriptor;
|
||||
std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor));
|
||||
buffer += sizeof(ScreenDescriptor);
|
||||
|
||||
std::vector<uint32_t> global_color_table;
|
||||
if (screen_descriptor.fields & 0x80) {
|
||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
global_color_table.resize(global_color_table_size);
|
||||
for (int i = 0; i < global_color_table_size; ++i) {
|
||||
uint8_t r = buffer[0];
|
||||
uint8_t g = buffer[1];
|
||||
uint8_t b = buffer[2];
|
||||
global_color_table[i] = (r << 16) | (g << 8) | b;
|
||||
buffer += 3;
|
||||
}
|
||||
}
|
||||
return global_color_table;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h) {
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
buffer += 6;
|
||||
|
||||
std::string headerStr(reinterpret_cast<char *>(header), 6);
|
||||
if (headerStr != "GIF87a" && headerStr != "GIF89a") {
|
||||
std::cout << "Formato de archivo GIF inválido: " << headerStr << '\n';
|
||||
throw std::runtime_error("Formato de archivo GIF inválido.");
|
||||
}
|
||||
|
||||
ScreenDescriptor screen_descriptor;
|
||||
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
||||
|
||||
w = screen_descriptor.width;
|
||||
h = screen_descriptor.height;
|
||||
|
||||
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
||||
std::vector<RGB> global_color_table;
|
||||
if (screen_descriptor.fields & 0x80) {
|
||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
global_color_table.resize(global_color_table_size);
|
||||
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
|
||||
buffer += 3 * global_color_table_size;
|
||||
}
|
||||
|
||||
uint8_t block_type = *buffer++;
|
||||
while (block_type != TRAILER) {
|
||||
if (block_type == EXTENSION_INTRODUCER) {
|
||||
uint8_t extension_label = *buffer++;
|
||||
switch (extension_label) {
|
||||
case GRAPHIC_CONTROL: {
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0) {
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APPLICATION_EXTENSION:
|
||||
case COMMENT_EXTENSION:
|
||||
case PLAINTEXT_EXTENSION: {
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0) {
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0) {
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (block_type == IMAGE_DESCRIPTOR) {
|
||||
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
|
||||
} else {
|
||||
std::cout << "Unrecognized block type: 0x" << std::hex << static_cast<int>(block_type) << std::dec << '\n';
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
block_type = *buffer++;
|
||||
}
|
||||
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) {
|
||||
return processGifStream(buffer, w, h);
|
||||
}
|
||||
|
||||
} // namespace GIF
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // Para uint8_t, uint16_t, uint32_t
|
||||
#include <vector> // Para vector
|
||||
|
||||
namespace GIF {
|
||||
|
||||
// Constantes definidas con constexpr, en lugar de macros
|
||||
constexpr uint8_t EXTENSION_INTRODUCER = 0x21;
|
||||
constexpr uint8_t IMAGE_DESCRIPTOR = 0x2C;
|
||||
constexpr uint8_t TRAILER = 0x3B;
|
||||
constexpr uint8_t GRAPHIC_CONTROL = 0xF9;
|
||||
constexpr uint8_t APPLICATION_EXTENSION = 0xFF;
|
||||
constexpr uint8_t COMMENT_EXTENSION = 0xFE;
|
||||
constexpr uint8_t PLAINTEXT_EXTENSION = 0x01;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct ScreenDescriptor {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t fields;
|
||||
uint8_t background_color_index;
|
||||
uint8_t pixel_aspect_ratio;
|
||||
};
|
||||
|
||||
struct RGB {
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
struct ImageDescriptor {
|
||||
uint16_t image_left_position;
|
||||
uint16_t image_top_position;
|
||||
uint16_t image_width;
|
||||
uint16_t image_height;
|
||||
uint8_t fields;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct DictionaryEntry {
|
||||
uint8_t byte;
|
||||
int prev;
|
||||
int len;
|
||||
};
|
||||
|
||||
struct Extension {
|
||||
uint8_t extension_code;
|
||||
uint8_t block_size;
|
||||
};
|
||||
|
||||
struct GraphicControlExtension {
|
||||
uint8_t fields;
|
||||
uint16_t delay_time;
|
||||
uint8_t transparent_color_index;
|
||||
};
|
||||
|
||||
struct ApplicationExtension {
|
||||
uint8_t application_id[8];
|
||||
uint8_t version[3];
|
||||
};
|
||||
|
||||
struct PlaintextExtension {
|
||||
uint16_t left, top, width, height;
|
||||
uint8_t cell_width, cell_height;
|
||||
uint8_t foreground_color, background_color;
|
||||
};
|
||||
|
||||
class Gif {
|
||||
public:
|
||||
// Descompone (uncompress) el bloque comprimido usando LZW.
|
||||
// Este método puede lanzar std::runtime_error en caso de error.
|
||||
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out);
|
||||
|
||||
// Carga la paleta (global color table) a partir de un buffer,
|
||||
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
|
||||
std::vector<uint32_t> loadPalette(const uint8_t *buffer);
|
||||
|
||||
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
|
||||
// asigna el ancho y alto mediante referencias.
|
||||
std::vector<uint8_t> loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h);
|
||||
|
||||
private:
|
||||
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
|
||||
std::vector<uint8_t> readSubBlocks(const uint8_t *&buffer);
|
||||
|
||||
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
|
||||
std::vector<uint8_t> processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits);
|
||||
|
||||
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
|
||||
std::vector<uint8_t> processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h);
|
||||
};
|
||||
|
||||
} // namespace GIF
|
||||
@@ -0,0 +1,624 @@
|
||||
#include "screen.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_RenderTexture, SDL_SetRenderDrawColor, SDL_SetRenderVSync, SDL_LogCategory, SDL_GetError, SDL_LogError, SDL_LogInfo, SDL_RendererLogicalPresentation, SDL_SetRenderLogicalPresentation, SDL_CreateTexture, SDL_DestroyTexture, SDL_DestroyWindow, SDL_GetDisplayName, SDL_GetTicks, SDL_Quit, SDL_RENDERER_VSYNC_DISABLED, SDL_RenderClear, SDL_CreateRenderer, SDL_CreateWindow, SDL_DestroyRenderer, SDL_DisplayID, SDL_FRect, SDL_GetCurrentDisplayMode, SDL_GetDisplays, SDL_GetRenderTarget, SDL_GetWindowPosition, SDL_GetWindowSize, SDL_Init, SDL_LogWarn, SDL_PixelFormat, SDL_RenderFillRect, SDL_RenderPresent, SDL_SetHint, SDL_SetRenderDrawBlendMode, SDL_SetTextureScaleMode, SDL_SetWindowFullscreen, SDL_SetWindowPosition, SDL_SetWindowSize, SDL_TextureAccess, SDL_free, SDL_BLENDMODE_BLEND, SDL_HINT_RENDER_DRIVER, SDL_INIT_VIDEO, SDL_ScaleMode, SDL_WINDOW_FULLSCREEN, SDL_WindowFlags
|
||||
|
||||
#include <algorithm> // Para min, max
|
||||
#include <cstring> // Para memcpy
|
||||
#include <iostream> // Para std::cout
|
||||
#include <memory> // Para allocator, shared_ptr, unique_ptr, __shared_ptr_access, make_shared, make_unique
|
||||
#include <string> // Para basic_string, operator+, char_traits, to_string, string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "asset.hpp" // Para Asset
|
||||
#include "director.hpp" // Para Director::debug_config
|
||||
#include "mouse.hpp" // Para updateCursorVisibility
|
||||
#include "options.hpp" // Para Video, video, Window, window
|
||||
#include "param.hpp" // Para Param, param, ParamGame, ParamDebug
|
||||
#include "rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
|
||||
#include "resource.hpp" // Para Resource
|
||||
#include "text.hpp" // Para Text
|
||||
#include "texture.hpp" // Para Texture
|
||||
#include "ui/notifier.hpp" // Para Notifier
|
||||
#include "ui/service_menu.hpp" // Para ServiceMenu
|
||||
#include "utils.hpp" // Para toLower
|
||||
|
||||
// Singleton
|
||||
Screen* Screen::instance = nullptr;
|
||||
|
||||
// Inicializa la instancia única del singleton
|
||||
void Screen::init() {
|
||||
Screen::instance = new Screen();
|
||||
Screen::initShaders(); // Llamar aquí para que Screen::get() ya devuelva la instancia
|
||||
}
|
||||
|
||||
// Libera la instancia
|
||||
void Screen::destroy() { delete Screen::instance; }
|
||||
|
||||
// Obtiene la instancia
|
||||
auto Screen::get() -> Screen* { return Screen::instance; }
|
||||
|
||||
// Constructor
|
||||
Screen::Screen()
|
||||
: window_(nullptr),
|
||||
renderer_(nullptr),
|
||||
game_canvas_(nullptr),
|
||||
service_menu_(nullptr),
|
||||
notifier_(nullptr),
|
||||
src_rect_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}),
|
||||
dst_rect_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}) {
|
||||
// Arranca SDL VIDEO, crea la ventana y el renderizador
|
||||
initSDLVideo();
|
||||
|
||||
// Crea la textura de destino
|
||||
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
|
||||
SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
// Inicializar buffer de píxeles para SDL3GPU
|
||||
pixel_buffer_.resize(static_cast<size_t>(param.game.width) * static_cast<size_t>(param.game.height));
|
||||
|
||||
// Crea el objeto de texto
|
||||
createText();
|
||||
|
||||
#ifdef _DEBUG
|
||||
debug_info_.text = text_;
|
||||
setDebugInfoEnabled(Director::debug_config.show_render_info);
|
||||
#endif
|
||||
|
||||
// Renderizar una vez la textura vacía para que tenga contenido válido antes de inicializar los shaders (evita pantalla negra)
|
||||
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
|
||||
|
||||
// Limpiar renderer
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Screen::~Screen() {
|
||||
SDL_DestroyTexture(game_canvas_);
|
||||
SDL_DestroyRenderer(renderer_);
|
||||
SDL_DestroyWindow(window_);
|
||||
}
|
||||
|
||||
// Limpia la pantalla
|
||||
void Screen::clean(Color color) {
|
||||
SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 0xFF);
|
||||
SDL_RenderClear(renderer_);
|
||||
}
|
||||
|
||||
// Prepara para empezar a dibujar en la textura de juego
|
||||
void Screen::start() { SDL_SetRenderTarget(renderer_, game_canvas_); }
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
void Screen::render() {
|
||||
fps_.increment();
|
||||
renderOverlays(); // Renderiza todos los overlays y efectos
|
||||
renderPresent(); // Renderiza el contenido del game_canvas_
|
||||
}
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla exceptuando ciertas partes
|
||||
void Screen::coreRender() {
|
||||
/*fps_.increment();
|
||||
#ifdef _DEBUG
|
||||
renderInfo();
|
||||
#endif*/
|
||||
renderPresent(); // Renderiza el contenido del game_canvas_
|
||||
}
|
||||
|
||||
// Renderiza el contenido del game_canvas_
|
||||
void Screen::renderPresent() {
|
||||
#ifndef NO_SHADERS
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
// Leer píxeles de game_canvas_ con la API SDL3 (devuelve SDL_Surface*)
|
||||
SDL_SetRenderTarget(renderer_, game_canvas_);
|
||||
SDL_Surface* surface = SDL_RenderReadPixels(renderer_, nullptr);
|
||||
if (surface != nullptr) {
|
||||
if (surface->format == SDL_PIXELFORMAT_ARGB8888) {
|
||||
std::memcpy(pixel_buffer_.data(), surface->pixels, pixel_buffer_.size() * sizeof(Uint32));
|
||||
} else {
|
||||
SDL_Surface* converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
|
||||
if (converted != nullptr) {
|
||||
std::memcpy(pixel_buffer_.data(), converted->pixels, pixel_buffer_.size() * sizeof(Uint32));
|
||||
SDL_DestroySurface(converted);
|
||||
}
|
||||
}
|
||||
SDL_DestroySurface(surface);
|
||||
}
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
// Subir a GPU y presentar con PostFX
|
||||
shader_backend_->uploadPixels(pixel_buffer_.data(), param.game.width, param.game.height);
|
||||
shader_backend_->render();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// Fallback: SDL_Renderer
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
clean();
|
||||
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
|
||||
// Establece el modo de video
|
||||
void Screen::setFullscreenMode() {
|
||||
SDL_SetWindowFullscreen(window_, Options::video.fullscreen);
|
||||
}
|
||||
|
||||
// Camibia entre pantalla completa y ventana
|
||||
void Screen::toggleFullscreen() {
|
||||
Options::video.fullscreen = !Options::video.fullscreen;
|
||||
setFullscreenMode();
|
||||
}
|
||||
|
||||
// Cambia el tamaño de la ventana
|
||||
void Screen::setWindowZoom(int zoom) {
|
||||
Options::window.zoom = zoom;
|
||||
adjustWindowSize();
|
||||
}
|
||||
|
||||
// Reduce el tamaño de la ventana
|
||||
auto Screen::decWindowSize() -> bool {
|
||||
if (!Options::video.fullscreen) {
|
||||
const int PREVIOUS_ZOOM = Options::window.zoom;
|
||||
--Options::window.zoom;
|
||||
Options::window.zoom = std::max(Options::window.zoom, 1);
|
||||
|
||||
if (Options::window.zoom != PREVIOUS_ZOOM) {
|
||||
adjustWindowSize();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Aumenta el tamaño de la ventana
|
||||
auto Screen::incWindowSize() -> bool {
|
||||
if (!Options::video.fullscreen) {
|
||||
const int PREVIOUS_ZOOM = Options::window.zoom;
|
||||
++Options::window.zoom;
|
||||
Options::window.zoom = std::min(Options::window.zoom, Options::window.max_zoom);
|
||||
|
||||
if (Options::window.zoom != PREVIOUS_ZOOM) {
|
||||
adjustWindowSize();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recibe deltaTime de las secciones y actualiza la lógica
|
||||
void Screen::update(float delta_time) {
|
||||
fps_.calculate(SDL_GetTicks());
|
||||
shake_effect_.update(src_rect_, dst_rect_, delta_time);
|
||||
flash_effect_.update(delta_time);
|
||||
if (service_menu_ != nullptr) {
|
||||
service_menu_->update(delta_time);
|
||||
}
|
||||
if (notifier_ != nullptr) {
|
||||
notifier_->update(delta_time);
|
||||
}
|
||||
Mouse::updateCursorVisibility();
|
||||
}
|
||||
|
||||
// Actualiza los elementos mínimos
|
||||
void Screen::coreUpdate() {
|
||||
fps_.calculate(SDL_GetTicks());
|
||||
Mouse::updateCursorVisibility();
|
||||
}
|
||||
|
||||
// Actualiza y dibuja el efecto de flash en la pantalla
|
||||
void Screen::renderFlash() {
|
||||
if (flash_effect_.isRendarable()) {
|
||||
SDL_SetRenderDrawColor(renderer_, flash_effect_.color.r, flash_effect_.color.g, flash_effect_.color.b, 0xFF);
|
||||
SDL_RenderClear(renderer_);
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica el efecto de agitar la pantalla
|
||||
void Screen::renderShake() {
|
||||
if (shake_effect_.enabled) {
|
||||
// Guarda el renderizador actual para dejarlo despues como estaba
|
||||
auto* current_target = SDL_GetRenderTarget(renderer_);
|
||||
|
||||
// Crea una textura temporal
|
||||
auto* temp_texture = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
|
||||
|
||||
// Vuelca game_canvas_ a la textura temporal
|
||||
SDL_SetRenderTarget(renderer_, temp_texture);
|
||||
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
|
||||
|
||||
// Vuelca textura temporal a game_canvas_
|
||||
SDL_SetRenderTarget(renderer_, game_canvas_);
|
||||
SDL_RenderTexture(renderer_, temp_texture, &src_rect_, &dst_rect_);
|
||||
|
||||
// Elimina la textura temporal
|
||||
SDL_DestroyTexture(temp_texture);
|
||||
|
||||
// Restaura el renderizador de destino original
|
||||
SDL_SetRenderTarget(renderer_, current_target);
|
||||
}
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
// Muestra información por pantalla
|
||||
void Screen::renderInfo() const {
|
||||
if (debug_info_.show) {
|
||||
const Color GOLD(0xFF, 0xD7, 0x00);
|
||||
const Color GOLD_SHADOW = GOLD.DARKEN(150);
|
||||
|
||||
// Construir texto: fps - driver - preset
|
||||
std::string info_text = std::to_string(fps_.last_value) + " fps";
|
||||
|
||||
// Driver GPU
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
const std::string DRIVER = shader_backend_->getDriverName();
|
||||
if (!DRIVER.empty()) {
|
||||
info_text += " - " + toLower(DRIVER);
|
||||
}
|
||||
} else {
|
||||
info_text += " - sdl";
|
||||
}
|
||||
|
||||
// Shader + preset
|
||||
if (Options::video.shader.enabled) {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
||||
info_text += " - crtpi " + toLower(PRESET_NAME);
|
||||
} else {
|
||||
const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
||||
info_text += " - postfx " + toLower(PRESET_NAME);
|
||||
if (Options::video.supersampling.enabled) { info_text += " (ss)"; }
|
||||
}
|
||||
}
|
||||
|
||||
// Centrado arriba
|
||||
const int TEXT_WIDTH = debug_info_.text->length(info_text);
|
||||
const int X_POS = (static_cast<int>(param.game.width) - TEXT_WIDTH) / 2;
|
||||
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, info_text, 1, GOLD, 1, GOLD_SHADOW);
|
||||
|
||||
#ifdef RECORDING
|
||||
const std::string REC_TEXT = "recording";
|
||||
const int REC_WIDTH = debug_info_.text->length(REC_TEXT);
|
||||
const int REC_X = (static_cast<int>(param.game.width) - REC_WIDTH) / 2;
|
||||
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Inicializa shaders (SDL3GPU)
|
||||
void Screen::initShaders() {
|
||||
#ifndef NO_SHADERS
|
||||
auto* self = Screen::get();
|
||||
if (self == nullptr) {
|
||||
std::cout << "Screen::initShaders: instance is null, skipping" << '\n';
|
||||
return;
|
||||
}
|
||||
if (!self->shader_backend_) {
|
||||
self->shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||
const std::string FALLBACK_DRIVER = "none";
|
||||
self->shader_backend_->setPreferredDriver(
|
||||
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
|
||||
}
|
||||
if (!self->shader_backend_->isHardwareAccelerated()) {
|
||||
const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
|
||||
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n';
|
||||
}
|
||||
if (self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||
self->shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
|
||||
self->shader_backend_->setDownscaleAlgo(Options::video.supersampling.downscale_algo);
|
||||
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
|
||||
if (!Options::video.shader.enabled) {
|
||||
// Passthrough: POSTFX con parámetros a cero
|
||||
self->shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
self->shader_backend_->setPostFXParams(Rendering::PostFXParams{});
|
||||
} else if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
self->shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
|
||||
self->applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
self->shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Calcula el tamaño de la ventana
|
||||
void Screen::adjustWindowSize() {
|
||||
if (!Options::video.fullscreen) {
|
||||
// Establece el nuevo tamaño
|
||||
const int WIDTH = param.game.width * Options::window.zoom;
|
||||
const int HEIGHT = param.game.height * Options::window.zoom;
|
||||
|
||||
int old_width;
|
||||
int old_height;
|
||||
SDL_GetWindowSize(window_, &old_width, &old_height);
|
||||
|
||||
int old_pos_x;
|
||||
int old_pos_y;
|
||||
SDL_GetWindowPosition(window_, &old_pos_x, &old_pos_y);
|
||||
|
||||
const int NEW_POS_X = old_pos_x + ((old_width - WIDTH) / 2);
|
||||
const int NEW_POS_Y = old_pos_y + ((old_height - HEIGHT) / 2);
|
||||
|
||||
SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS), std::max(NEW_POS_Y, 0));
|
||||
SDL_SetWindowSize(window_, WIDTH, HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza todos los overlays y efectos
|
||||
void Screen::renderOverlays() {
|
||||
// Dibuja efectos y elementos sobre el game_canvas_
|
||||
renderShake();
|
||||
renderFlash();
|
||||
renderAttenuate();
|
||||
service_menu_->render();
|
||||
notifier_->render();
|
||||
#ifdef _DEBUG
|
||||
renderInfo();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Atenua la pantalla
|
||||
void Screen::renderAttenuate() {
|
||||
if (attenuate_effect_) {
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 64);
|
||||
SDL_RenderFillRect(renderer_, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Arranca SDL VIDEO y crea la ventana
|
||||
auto Screen::initSDLVideo() -> bool {
|
||||
// Inicializar SDL
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
std::cout << "FATAL: Failed to initialize SDL_VIDEO! SDL Error: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obtener información de la pantalla
|
||||
getDisplayInfo();
|
||||
|
||||
// Configurar hint para renderizado
|
||||
#ifdef __APPLE__
|
||||
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")) {
|
||||
std::cout << "Warning: Failed to set Metal hint!" << '\n';
|
||||
}
|
||||
#endif
|
||||
|
||||
// Crear ventana
|
||||
#ifdef __APPLE__
|
||||
SDL_WindowFlags window_flags = SDL_WINDOW_METAL;
|
||||
#else
|
||||
SDL_WindowFlags window_flags = 0;
|
||||
#endif
|
||||
if (Options::video.fullscreen) {
|
||||
window_flags |= SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
window_flags |= SDL_WINDOW_HIDDEN;
|
||||
window_ = SDL_CreateWindow(
|
||||
Options::window.caption.c_str(),
|
||||
param.game.width * Options::window.zoom,
|
||||
param.game.height * Options::window.zoom,
|
||||
window_flags);
|
||||
|
||||
if (window_ == nullptr) {
|
||||
std::cout << "FATAL: Failed to create window! SDL Error: " << SDL_GetError() << '\n';
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Crear renderer
|
||||
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
||||
if (renderer_ == nullptr) {
|
||||
std::cout << "FATAL: Failed to create renderer! SDL Error: " << SDL_GetError() << '\n';
|
||||
SDL_DestroyWindow(window_);
|
||||
window_ = nullptr;
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configurar renderer
|
||||
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Obtiene información sobre la pantalla
|
||||
void Screen::getDisplayInfo() {
|
||||
int num_displays = 0;
|
||||
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
||||
if (displays != nullptr) {
|
||||
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
|
||||
|
||||
// Guarda información del monitor en display_monitor_
|
||||
const char* first_display_name = SDL_GetDisplayName(displays[0]);
|
||||
display_monitor_.name = (first_display_name != nullptr) ? first_display_name : "Unknown";
|
||||
display_monitor_.width = static_cast<int>(dm->w);
|
||||
display_monitor_.height = static_cast<int>(dm->h);
|
||||
display_monitor_.refresh_rate = static_cast<int>(dm->refresh_rate);
|
||||
|
||||
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
|
||||
Options::window.max_zoom = std::min(dm->w / param.game.width, dm->h / param.game.height);
|
||||
Options::window.zoom = std::min(Options::window.zoom, Options::window.max_zoom);
|
||||
|
||||
// Obtiene la cadena con la información sobre la resolución y el refresco
|
||||
Options::video.info = std::to_string(dm->w) + "x" +
|
||||
std::to_string(dm->h) + " @ " +
|
||||
std::to_string(static_cast<int>(dm->refresh_rate)) + " Hz";
|
||||
|
||||
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
|
||||
const int MAX_ZOOM = std::min(dm->w / param.game.width, (dm->h - WINDOWS_DECORATIONS) / param.game.height);
|
||||
|
||||
// Normaliza los valores de zoom
|
||||
Options::window.zoom = std::min(Options::window.zoom, MAX_ZOOM);
|
||||
|
||||
SDL_free(displays);
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna activar/desactivar shaders
|
||||
void Screen::toggleShaders() {
|
||||
Options::video.shader.enabled = !Options::video.shader.enabled;
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr) {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
self->applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia entre PostFX y CrtPi
|
||||
void Screen::nextShader() {
|
||||
auto* self = Screen::get();
|
||||
if (self == nullptr || !self->shader_backend_ || !self->shader_backend_->isHardwareAccelerated()) { return; }
|
||||
|
||||
const auto NEXT = (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX)
|
||||
? Rendering::ShaderType::CRTPI
|
||||
: Rendering::ShaderType::POSTFX;
|
||||
|
||||
Options::video.shader.current_shader = NEXT;
|
||||
self->shader_backend_->setActiveShader(NEXT);
|
||||
|
||||
if (NEXT == Rendering::ShaderType::CRTPI) {
|
||||
self->applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
|
||||
// Avanza al siguiente preset PostFX
|
||||
void Screen::nextPostFXPreset() {
|
||||
if (Options::postfx_presets.empty()) { return; }
|
||||
Options::video.shader.current_postfx_preset = (Options::video.shader.current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr) {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
|
||||
// Avanza al siguiente preset CrtPi
|
||||
void Screen::nextCrtPiPreset() {
|
||||
if (Options::crtpi_presets.empty()) { return; }
|
||||
Options::video.shader.current_crtpi_preset = (Options::video.shader.current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size());
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr) {
|
||||
self->applyCurrentCrtPiPreset();
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna supersampling
|
||||
void Screen::toggleSupersampling() {
|
||||
Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
self->applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica el preset PostFX activo al backend
|
||||
void Screen::applyCurrentPostFXPreset() {
|
||||
if (!shader_backend_) { return; }
|
||||
shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
Rendering::PostFXParams p{};
|
||||
if (Options::video.shader.enabled && !Options::postfx_presets.empty()) {
|
||||
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset));
|
||||
p.vignette = preset.vignette;
|
||||
p.scanlines = preset.scanlines;
|
||||
p.chroma = preset.chroma;
|
||||
p.mask = preset.mask;
|
||||
p.gamma = preset.gamma;
|
||||
p.curvature = preset.curvature;
|
||||
p.bleeding = preset.bleeding;
|
||||
p.flicker = preset.flicker;
|
||||
std::cout << "Screen::applyCurrentPostFXPreset: preset='" << preset.name << "' scan=" << p.scanlines << " vign=" << p.vignette << " chroma=" << p.chroma << '\n';
|
||||
}
|
||||
shader_backend_->setPostFXParams(p);
|
||||
}
|
||||
|
||||
// Aplica el preset CrtPi activo al backend
|
||||
void Screen::applyCurrentCrtPiPreset() {
|
||||
if (!shader_backend_) { return; }
|
||||
if (Options::video.shader.enabled && !Options::crtpi_presets.empty()) {
|
||||
const auto& preset = Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset));
|
||||
Rendering::CrtPiParams p{
|
||||
.scanline_weight = preset.scanline_weight,
|
||||
.scanline_gap_brightness = preset.scanline_gap_brightness,
|
||||
.bloom_factor = preset.bloom_factor,
|
||||
.input_gamma = preset.input_gamma,
|
||||
.output_gamma = preset.output_gamma,
|
||||
.mask_brightness = preset.mask_brightness,
|
||||
.curvature_x = preset.curvature_x,
|
||||
.curvature_y = preset.curvature_y,
|
||||
.mask_type = preset.mask_type,
|
||||
.enable_scanlines = preset.enable_scanlines,
|
||||
.enable_multisample = preset.enable_multisample,
|
||||
.enable_gamma = preset.enable_gamma,
|
||||
.enable_curvature = preset.enable_curvature,
|
||||
.enable_sharper = preset.enable_sharper,
|
||||
};
|
||||
shader_backend_->setCrtPiParams(p);
|
||||
std::cout << "Screen::applyCurrentCrtPiPreset: preset='" << preset.name << "'" << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar el escalado entero
|
||||
void Screen::toggleIntegerScale() {
|
||||
Options::video.integer_scale = !Options::video.integer_scale;
|
||||
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
if (shader_backend_) {
|
||||
shader_backend_->setScaleMode(Options::video.integer_scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar el V-Sync
|
||||
void Screen::toggleVSync() {
|
||||
Options::video.vsync = !Options::video.vsync;
|
||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
if (shader_backend_) {
|
||||
shader_backend_->setVSync(Options::video.vsync);
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el estado del V-Sync
|
||||
void Screen::setVSync(bool enabled) {
|
||||
Options::video.vsync = enabled;
|
||||
SDL_SetRenderVSync(renderer_, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
}
|
||||
|
||||
// Obtiene los punteros a los singletones
|
||||
void Screen::getSingletons() {
|
||||
service_menu_ = ServiceMenu::get();
|
||||
notifier_ = Notifier::get();
|
||||
#ifdef _DEBUG
|
||||
// Actualizar la fuente de debug a 8bithud (ahora Resource está disponible)
|
||||
if (Resource::get() != nullptr) {
|
||||
auto hud_text = Resource::get()->getText("8bithud");
|
||||
if (hud_text) {
|
||||
debug_info_.text = hud_text;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Aplica los valores de las opciones
|
||||
void Screen::applySettings() {
|
||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
SDL_SetRenderLogicalPresentation(Screen::get()->getRenderer(), param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
setFullscreenMode();
|
||||
adjustWindowSize();
|
||||
}
|
||||
|
||||
// Crea el objeto de texto
|
||||
void Screen::createText() {
|
||||
auto texture = std::make_shared<Texture>(getRenderer(), Asset::get()->getPath("aseprite.png"));
|
||||
text_ = std::make_shared<Text>(texture, Asset::get()->getPath("aseprite.txt"));
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_HideWindow, SDL_Renderer, SDL_ShowWindow, Uint32, SDL_Texture, SDL_Window
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
#include "options.hpp" // Para VideoOptions, video
|
||||
#include "rendering/shader_backend.hpp" // Para Rendering::ShaderType
|
||||
|
||||
// Forward declarations
|
||||
class Notifier;
|
||||
class ServiceMenu;
|
||||
class Text;
|
||||
|
||||
// --- Clase Screen: gestiona la ventana, el renderizador y los efectos visuales globales (singleton) ---
|
||||
class Screen {
|
||||
public:
|
||||
// --- Métodos de singleton ---
|
||||
static void init(); // Inicializa el objeto Screen
|
||||
static void destroy(); // Libera el objeto Screen
|
||||
static auto get() -> Screen*; // Obtiene el puntero al objeto Screen
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Recibe deltaTime de las secciones y actualiza la lógica
|
||||
void coreUpdate(); // Actualiza los elementos mínimos
|
||||
void clean(Color color = Color(0x00, 0x00, 0x00)); // Limpia la pantalla
|
||||
void start(); // Prepara para empezar a dibujar en la textura de juego
|
||||
void render(); // Vuelca el contenido del renderizador en pantalla
|
||||
void coreRender(); // Vuelca el contenido del renderizador en pantalla exceptuando ciertas partes
|
||||
|
||||
// --- Configuración de ventana y render ---
|
||||
void setFullscreenMode(); // Establece el modo de pantalla completa
|
||||
void toggleFullscreen(); // Cambia entre pantalla completa y ventana
|
||||
void setWindowZoom(int zoom); // Cambia el tamaño de la ventana
|
||||
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
|
||||
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
|
||||
void applySettings(); // Aplica los valores de las opciones
|
||||
static void initShaders(); // Inicializa shaders (SDL3GPU)
|
||||
|
||||
// --- Efectos visuales ---
|
||||
void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); }
|
||||
void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); }
|
||||
static void toggleShaders(); // Alterna activar/desactivar shaders
|
||||
static void nextShader(); // Cambia entre PostFX y CrtPi
|
||||
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
||||
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
|
||||
static void toggleSupersampling(); // Alterna supersampling
|
||||
void toggleIntegerScale();
|
||||
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
||||
void setVSync(bool enabled); // Establece el estado del V-Sync
|
||||
void attenuate(bool value) { attenuate_effect_ = value; } // Atenúa la pantalla
|
||||
|
||||
// --- Getters ---
|
||||
auto getRenderer() -> SDL_Renderer* { return renderer_; } // Obtiene el renderizador
|
||||
void show() { SDL_ShowWindow(window_); } // Muestra la ventana
|
||||
void hide() { SDL_HideWindow(window_); } // Oculta la ventana
|
||||
void getSingletons(); // Obtiene los punteros a los singletones
|
||||
[[nodiscard]] static auto getVSync() -> bool { return Options::video.vsync; } // Obtiene el valor de V-Sync
|
||||
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; } // Obtiene el puntero al texto de Screen
|
||||
|
||||
// --- Display Monitor getters ---
|
||||
[[nodiscard]] auto getDisplayMonitorName() const -> std::string { return display_monitor_.name; }
|
||||
[[nodiscard]] auto getDisplayMonitorWidth() const -> int { return display_monitor_.width; }
|
||||
[[nodiscard]] auto getDisplayMonitorHeight() const -> int { return display_monitor_.height; }
|
||||
[[nodiscard]] auto getDisplayMonitorRefreshRate() const -> int { return display_monitor_.refresh_rate; }
|
||||
|
||||
#ifdef _DEBUG
|
||||
// --- Debug ---
|
||||
void toggleDebugInfo() { debug_info_.show = !debug_info_.show; }
|
||||
void setDebugInfoEnabled(bool value) { debug_info_.show = value; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
// --- Constantes ---
|
||||
static constexpr int WINDOWS_DECORATIONS = 35; // Decoraciones de la ventana
|
||||
|
||||
// --- Estructuras privadas ---
|
||||
struct DisplayMonitor {
|
||||
std::string name;
|
||||
int width;
|
||||
int height;
|
||||
int refresh_rate;
|
||||
};
|
||||
struct FPS {
|
||||
Uint32 ticks{0}; // Tiempo en milisegundos desde que se comenzó a contar.
|
||||
int frame_count{0}; // Número acumulado de frames en el intervalo.
|
||||
int last_value{0}; // Número de frames calculado en el último segundo.
|
||||
|
||||
FPS() = default;
|
||||
void increment() { frame_count++; }
|
||||
auto calculate(Uint32 current_ticks) -> int {
|
||||
if (current_ticks - ticks >= 1000) {
|
||||
last_value = frame_count;
|
||||
frame_count = 0;
|
||||
ticks = current_ticks;
|
||||
}
|
||||
return last_value;
|
||||
}
|
||||
};
|
||||
|
||||
// Efecto de flash en pantalla: pinta la pantalla de un color durante un tiempo
|
||||
struct FlashEffect {
|
||||
bool enabled; // Indica si el efecto está activo
|
||||
float duration_s; // Duración total del efecto en segundos
|
||||
float delay_s; // Retraso antes de mostrar el flash en segundos
|
||||
float timer_s; // Timer en segundos (contador decreciente)
|
||||
Color color; // Color del flash
|
||||
|
||||
explicit FlashEffect(bool enabled = false, float duration_s = 0.0F, float delay_s = 0.0F, Color color = Color(0xFF, 0xFF, 0xFF))
|
||||
: enabled(enabled),
|
||||
duration_s(duration_s),
|
||||
delay_s(delay_s),
|
||||
timer_s(duration_s),
|
||||
color(color) {}
|
||||
|
||||
void update(float delta_time) {
|
||||
if (enabled && timer_s > 0.0F) {
|
||||
timer_s -= delta_time;
|
||||
if (timer_s <= 0.0F) {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
[[nodiscard]] auto isRendarable() const -> bool { return enabled && timer_s < duration_s - delay_s; }
|
||||
};
|
||||
|
||||
// Efecto de sacudida/agitación de pantalla: mueve la imagen para simular un temblor
|
||||
struct ShakeEffect {
|
||||
int desp; // Desplazamiento máximo de la sacudida (en píxeles)
|
||||
float delay_s; // Segundos entre cada movimiento de sacudida
|
||||
float counter_s; // Timer para el siguiente movimiento (decreciente)
|
||||
float duration_s; // Duración total del efecto en segundos
|
||||
float remaining_s; // Tiempo restante de sacudida
|
||||
int original_pos; // Posición original de la imagen (x)
|
||||
int original_width; // Ancho original de la imagen
|
||||
bool enabled; // Indica si el efecto está activo
|
||||
|
||||
explicit ShakeEffect(bool en = false, int dp = 2, float dl_s = 0.05F, float cnt_s = 0.0F, float len_s = 0.133F, float rem_s = 0.0F, int orig_pos = 0, int orig_width = 800)
|
||||
: desp(dp),
|
||||
delay_s(dl_s),
|
||||
counter_s(cnt_s),
|
||||
duration_s(len_s),
|
||||
remaining_s(rem_s),
|
||||
original_pos(orig_pos),
|
||||
original_width(orig_width),
|
||||
enabled(en) {}
|
||||
|
||||
// Activa el efecto de sacudida y guarda la posición y tamaño originales
|
||||
void enable(SDL_FRect& src_rect, SDL_FRect& dst_rect, int new_desp = -1, float new_delay_s = -1.0F, float new_duration_s = -1.0F) {
|
||||
if (!enabled) {
|
||||
enabled = true;
|
||||
original_pos = src_rect.x;
|
||||
original_width = src_rect.w;
|
||||
|
||||
// Usar nuevos valores si se proporcionan, sino mantener los actuales
|
||||
if (new_desp != -1) {
|
||||
desp = new_desp;
|
||||
}
|
||||
if (new_delay_s >= 0.0F) {
|
||||
delay_s = new_delay_s;
|
||||
}
|
||||
if (new_duration_s >= 0.0F) {
|
||||
duration_s = new_duration_s;
|
||||
}
|
||||
|
||||
src_rect.w -= desp;
|
||||
dst_rect.w = src_rect.w;
|
||||
}
|
||||
remaining_s = duration_s;
|
||||
counter_s = delay_s;
|
||||
}
|
||||
|
||||
// Actualiza el estado del efecto de sacudida
|
||||
void update(SDL_FRect& src_rect, SDL_FRect& dst_rect, float delta_time) {
|
||||
if (enabled) {
|
||||
counter_s -= delta_time;
|
||||
if (counter_s <= 0.0F) {
|
||||
counter_s = delay_s;
|
||||
// Alternar desplazamiento basado en tiempo restante
|
||||
const bool SHAKE_LEFT = static_cast<int>(remaining_s * 30.0F) % 2 == 0; // ~30 cambios por segundo
|
||||
const auto SRC_DESP = SHAKE_LEFT ? 0 : desp;
|
||||
const auto DST_DESP = SHAKE_LEFT ? desp : 0;
|
||||
src_rect.x = original_pos + SRC_DESP;
|
||||
dst_rect.x = original_pos + DST_DESP;
|
||||
|
||||
remaining_s -= delay_s;
|
||||
if (remaining_s <= 0.0F) {
|
||||
enabled = false;
|
||||
src_rect.x = original_pos;
|
||||
src_rect.w = original_width;
|
||||
dst_rect.x = original_pos;
|
||||
dst_rect.w = original_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto isEnabled() const -> bool { return enabled; }
|
||||
};
|
||||
|
||||
#ifdef _DEBUG
|
||||
struct Debug {
|
||||
std::shared_ptr<Text> text;
|
||||
bool show = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Window* window_; // Ventana de la aplicación
|
||||
SDL_Renderer* renderer_; // El renderizador de la ventana
|
||||
SDL_Texture* game_canvas_; // Textura donde se dibuja todo antes de volcarse al renderizador
|
||||
ServiceMenu* service_menu_; // Objeto para mostrar el menú de servicio
|
||||
Notifier* notifier_; // Objeto para mostrar las notificaciones por pantalla
|
||||
std::shared_ptr<Text> text_; // Objeto para escribir texto en pantalla
|
||||
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (SDL3GPU)
|
||||
|
||||
// --- Variables de estado ---
|
||||
SDL_FRect src_rect_; // Coordenadas de origen para dibujar la textura del juego
|
||||
SDL_FRect dst_rect_; // Coordenadas destino para dibujar la textura del juego
|
||||
std::vector<Uint32> pixel_buffer_; // Buffer de píxeles para SDL_RenderReadPixels
|
||||
FPS fps_; // Gestión de frames por segundo
|
||||
FlashEffect flash_effect_; // Efecto de flash en pantalla
|
||||
ShakeEffect shake_effect_; // Efecto de agitar la pantalla
|
||||
bool attenuate_effect_ = false; // Indica si la pantalla ha de estar atenuada
|
||||
DisplayMonitor display_monitor_; // Información del monitor actual
|
||||
#ifdef _DEBUG
|
||||
Debug debug_info_; // Información de debug
|
||||
#endif
|
||||
|
||||
// --- Métodos internos ---
|
||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||
void renderFlash(); // Dibuja el efecto de flash en la pantalla
|
||||
void renderShake(); // Aplica el efecto de agitar la pantalla
|
||||
void renderInfo() const; // Muestra información por pantalla
|
||||
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
||||
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
|
||||
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
|
||||
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||
void renderOverlays(); // Renderiza todos los overlays y efectos
|
||||
void renderAttenuate(); // Atenúa la pantalla
|
||||
void createText(); // Crea el objeto de texto
|
||||
|
||||
// --- Constructores y destructor privados (singleton) ---
|
||||
Screen(); // Constructor privado
|
||||
~Screen(); // Destructor privado
|
||||
|
||||
// --- Instancia singleton ---
|
||||
static Screen* instance; // Instancia única de Screen
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "rendering/shader_backend.hpp"
|
||||
|
||||
// PostFX uniforms pushed to fragment stage each frame.
|
||||
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float oversample;
|
||||
float flicker;
|
||||
};
|
||||
|
||||
// CrtPi uniforms pushed to fragment stage each frame.
|
||||
// 16 fields = 64 bytes — 4 × 16-byte alignment.
|
||||
struct CrtPiUniforms {
|
||||
float scanline_weight;
|
||||
float scanline_gap_brightness;
|
||||
float bloom_factor;
|
||||
float input_gamma;
|
||||
float output_gamma;
|
||||
float mask_brightness;
|
||||
float curvature_x;
|
||||
float curvature_y;
|
||||
int mask_type;
|
||||
int enable_scanlines;
|
||||
int enable_multisample;
|
||||
int enable_gamma;
|
||||
int enable_curvature;
|
||||
int enable_sharper;
|
||||
float texture_width;
|
||||
float texture_height;
|
||||
};
|
||||
|
||||
// Downscale uniforms for Lanczos downscale fragment stage.
|
||||
// 1 int + 3 floats = 16 bytes.
|
||||
struct DownscaleUniforms {
|
||||
int algorithm;
|
||||
float pad0;
|
||||
float pad1;
|
||||
float pad2;
|
||||
};
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/**
|
||||
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
|
||||
*
|
||||
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
||||
* → [Upscale →] PostFX/CrtPi render pass → [Lanczos downscale →] swapchain → present
|
||||
*/
|
||||
class SDL3GPUShader : public ShaderBackend {
|
||||
public:
|
||||
SDL3GPUShader() = default;
|
||||
~SDL3GPUShader() override;
|
||||
|
||||
auto init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) -> bool override;
|
||||
|
||||
void render() override;
|
||||
void setTextureSize(float width, float height) override {}
|
||||
void cleanup() final;
|
||||
void destroy();
|
||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
|
||||
|
||||
void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
|
||||
void uploadPixels(const Uint32* pixels, int width, int height) override;
|
||||
void setPostFXParams(const PostFXParams& p) override;
|
||||
void setVSync(bool vsync) override;
|
||||
void setScaleMode(bool integer_scale) override;
|
||||
void setOversample(int factor) override;
|
||||
|
||||
void setLinearUpscale(bool linear) override;
|
||||
[[nodiscard]] auto isLinearUpscale() const -> bool override { return linear_upscale_; }
|
||||
void setDownscaleAlgo(int algo) override;
|
||||
[[nodiscard]] auto getDownscaleAlgo() const -> int override { return downscale_algo_; }
|
||||
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
|
||||
|
||||
void setActiveShader(ShaderType type) override;
|
||||
void setCrtPiParams(const CrtPiParams& p) override;
|
||||
[[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }
|
||||
|
||||
private:
|
||||
static auto createShaderMSL(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||
|
||||
static auto createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||
|
||||
auto createPipeline() -> bool;
|
||||
auto createCrtPiPipeline() -> bool;
|
||||
auto reinitTexturesAndBuffer() -> bool;
|
||||
auto recreateScaledTexture(int factor) -> bool;
|
||||
static auto calcSsFactor(float zoom) -> int;
|
||||
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
|
||||
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr;
|
||||
SDL_GPUTexture* scene_texture_ = nullptr;
|
||||
SDL_GPUTexture* scaled_texture_ = nullptr;
|
||||
SDL_GPUTexture* postfx_texture_ = nullptr;
|
||||
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr;
|
||||
SDL_GPUSampler* linear_sampler_ = nullptr;
|
||||
|
||||
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
|
||||
CrtPiUniforms crtpi_uniforms_{.scanline_weight = 6.0F, .scanline_gap_brightness = 0.12F, .bloom_factor = 3.5F, .input_gamma = 2.4F, .output_gamma = 2.2F, .mask_brightness = 0.80F, .curvature_x = 0.05F, .curvature_y = 0.10F, .mask_type = 2, .enable_scanlines = 1, .enable_multisample = 1, .enable_gamma = 1};
|
||||
ShaderType active_shader_ = ShaderType::POSTFX;
|
||||
|
||||
int game_width_ = 0;
|
||||
int game_height_ = 0;
|
||||
int ss_factor_ = 0;
|
||||
int oversample_ = 1;
|
||||
int downscale_algo_ = 1;
|
||||
std::string driver_name_;
|
||||
std::string preferred_driver_;
|
||||
bool is_initialized_ = false;
|
||||
bool vsync_ = true;
|
||||
bool integer_scale_ = false;
|
||||
bool linear_upscale_ = false;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
@@ -0,0 +1,633 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
static const uint8_t kupscale_frag_spv[] = {
|
||||
0x03,
|
||||
0x02,
|
||||
0x23,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x14,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x53,
|
||||
0x4c,
|
||||
0x2e,
|
||||
0x73,
|
||||
0x74,
|
||||
0x64,
|
||||
0x2e,
|
||||
0x34,
|
||||
0x35,
|
||||
0x30,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0e,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x6d,
|
||||
0x61,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x10,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xc2,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x5f,
|
||||
0x47,
|
||||
0x4f,
|
||||
0x4f,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x45,
|
||||
0x5f,
|
||||
0x63,
|
||||
0x70,
|
||||
0x70,
|
||||
0x5f,
|
||||
0x73,
|
||||
0x74,
|
||||
0x79,
|
||||
0x6c,
|
||||
0x65,
|
||||
0x5f,
|
||||
0x6c,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x65,
|
||||
0x5f,
|
||||
0x64,
|
||||
0x69,
|
||||
0x72,
|
||||
0x65,
|
||||
0x63,
|
||||
0x74,
|
||||
0x69,
|
||||
0x76,
|
||||
0x65,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x08,
|
||||
0x00,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x5f,
|
||||
0x47,
|
||||
0x4f,
|
||||
0x4f,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x45,
|
||||
0x5f,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x63,
|
||||
0x6c,
|
||||
0x75,
|
||||
0x64,
|
||||
0x65,
|
||||
0x5f,
|
||||
0x64,
|
||||
0x69,
|
||||
0x72,
|
||||
0x65,
|
||||
0x63,
|
||||
0x74,
|
||||
0x69,
|
||||
0x76,
|
||||
0x65,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x6d,
|
||||
0x61,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x6f,
|
||||
0x75,
|
||||
0x74,
|
||||
0x5f,
|
||||
0x63,
|
||||
0x6f,
|
||||
0x6c,
|
||||
0x6f,
|
||||
0x72,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x73,
|
||||
0x63,
|
||||
0x65,
|
||||
0x6e,
|
||||
0x65,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x76,
|
||||
0x5f,
|
||||
0x75,
|
||||
0x76,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x21,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x22,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x13,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x21,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x16,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x17,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x08,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3b,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x08,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x19,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1b,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0c,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3b,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0c,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x17,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x10,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3b,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x10,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x36,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xf8,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3d,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3d,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x12,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x57,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x13,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x12,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3e,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x13,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xfd,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x38,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00};
|
||||
static const size_t kupscale_frag_spv_size = 628;
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/** @brief Identificador del shader de post-procesado activo */
|
||||
enum class ShaderType { POSTFX,
|
||||
CRTPI };
|
||||
|
||||
/**
|
||||
* @brief Parámetros de intensidad de los efectos PostFX
|
||||
*/
|
||||
struct PostFXParams {
|
||||
float vignette = 0.0F;
|
||||
float scanlines = 0.0F;
|
||||
float chroma = 0.0F;
|
||||
float mask = 0.0F;
|
||||
float gamma = 0.0F;
|
||||
float curvature = 0.0F;
|
||||
float bleeding = 0.0F;
|
||||
float flicker = 0.0F;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Parámetros del shader CRT-Pi (algoritmo de scanlines continuas)
|
||||
*/
|
||||
struct CrtPiParams {
|
||||
float scanline_weight{6.0F};
|
||||
float scanline_gap_brightness{0.12F};
|
||||
float bloom_factor{3.5F};
|
||||
float input_gamma{2.4F};
|
||||
float output_gamma{2.2F};
|
||||
float mask_brightness{0.80F};
|
||||
float curvature_x{0.05F};
|
||||
float curvature_y{0.10F};
|
||||
int mask_type{2};
|
||||
bool enable_scanlines{true};
|
||||
bool enable_multisample{true};
|
||||
bool enable_gamma{true};
|
||||
bool enable_curvature{false};
|
||||
bool enable_sharper{false};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Interfaz abstracta para backends de renderizado con shaders
|
||||
*/
|
||||
class ShaderBackend {
|
||||
public:
|
||||
virtual ~ShaderBackend() = default;
|
||||
|
||||
virtual auto init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) -> bool = 0;
|
||||
|
||||
virtual void render() = 0;
|
||||
virtual void setTextureSize(float width, float height) = 0;
|
||||
virtual void cleanup() = 0;
|
||||
|
||||
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
|
||||
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
||||
virtual void setVSync(bool /*vsync*/) {}
|
||||
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||
virtual void setOversample(int /*factor*/) {}
|
||||
|
||||
virtual void setLinearUpscale(bool /*linear*/) {}
|
||||
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
|
||||
|
||||
virtual void setDownscaleAlgo(int /*algo*/) {}
|
||||
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
|
||||
|
||||
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
|
||||
|
||||
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||
|
||||
[[nodiscard]] virtual auto getDriverName() const -> std::string { return {}; }
|
||||
virtual void setPreferredDriver(const std::string& /*driver*/) {}
|
||||
|
||||
virtual void setActiveShader(ShaderType /*type*/) {}
|
||||
virtual void setCrtPiParams(const CrtPiParams& /*p*/) {}
|
||||
[[nodiscard]] virtual auto getActiveShader() const -> ShaderType { return ShaderType::POSTFX; }
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
@@ -0,0 +1,304 @@
|
||||
#include "animated_sprite.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError, SDL_FRect
|
||||
|
||||
#include <algorithm> // Para min
|
||||
#include <cstddef> // Para size_t
|
||||
#include <fstream> // Para basic_istream, basic_ifstream, istream, basic_ios, ifstream, istringstream, stringstream
|
||||
#include <iostream> // Para std::cout
|
||||
#include <sstream> // Para basic_istringstream, basic_stringstream
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <utility> // Para move, pair
|
||||
|
||||
#include "resource_helper.hpp" // Para loadFile
|
||||
#include "texture.hpp" // Para Texture
|
||||
|
||||
// Carga las animaciones en un vector(Animations) desde un fichero
|
||||
auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffer {
|
||||
// Intentar cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
std::istringstream stream;
|
||||
bool using_resource_data = false;
|
||||
|
||||
if (!resource_data.empty()) {
|
||||
std::string content(resource_data.begin(), resource_data.end());
|
||||
stream.str(content);
|
||||
using_resource_data = true;
|
||||
}
|
||||
|
||||
// Fallback a archivo directo
|
||||
std::ifstream file;
|
||||
if (!using_resource_data) {
|
||||
file.open(file_path);
|
||||
if (!file) {
|
||||
std::cout << "Error: Fichero no encontrado " << file_path << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + file_path);
|
||||
}
|
||||
}
|
||||
|
||||
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
||||
|
||||
std::vector<std::string> buffer;
|
||||
std::string line;
|
||||
while (std::getline(input_stream, line)) {
|
||||
// Eliminar caracteres de retorno de carro (\r) al final de la línea
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
if (!line.empty()) {
|
||||
buffer.push_back(line);
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, const std::string& file_path)
|
||||
: MovingSprite(std::move(texture)) {
|
||||
// Carga las animaciones
|
||||
if (!file_path.empty()) {
|
||||
auto buffer = loadAnimationsFromFile(file_path);
|
||||
loadFromAnimationsFileBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor
|
||||
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, const AnimationsFileBuffer& animations)
|
||||
: MovingSprite(std::move(texture)) {
|
||||
if (!animations.empty()) {
|
||||
loadFromAnimationsFileBuffer(animations);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el índice de la animación a partir del nombre
|
||||
auto AnimatedSprite::getAnimationIndex(const std::string& name) -> int {
|
||||
auto iterator = animation_indices_.find(name);
|
||||
if (iterator != animation_indices_.end()) {
|
||||
// Si se encuentra la animación en el mapa, devuelve su índice
|
||||
return iterator->second;
|
||||
}
|
||||
|
||||
// Si no se encuentra, muestra una advertencia y devuelve -1
|
||||
std::cout << "** Warning: could not find \"" << name << "\" animation" << '\n';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calcula el frame correspondiente a la animación (time-based)
|
||||
void AnimatedSprite::animate(float delta_time) {
|
||||
if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Acumular tiempo transcurrido
|
||||
animations_[current_animation_].time_accumulator += delta_time;
|
||||
|
||||
// Verificar si es momento de cambiar frame
|
||||
if (animations_[current_animation_].time_accumulator >= animations_[current_animation_].speed) {
|
||||
animations_[current_animation_].time_accumulator -= animations_[current_animation_].speed;
|
||||
animations_[current_animation_].current_frame++;
|
||||
|
||||
// Si alcanza el final de la animación
|
||||
if (animations_[current_animation_].current_frame >= animations_[current_animation_].frames.size()) {
|
||||
if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
|
||||
animations_[current_animation_].current_frame = animations_[current_animation_].frames.size() - 1;
|
||||
animations_[current_animation_].completed = true;
|
||||
} else { // Si hay loop, vuelve al frame indicado
|
||||
animations_[current_animation_].time_accumulator = 0.0F;
|
||||
animations_[current_animation_].current_frame = animations_[current_animation_].loop;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar el sprite clip
|
||||
updateSpriteClip();
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado la animación
|
||||
auto AnimatedSprite::animationIsCompleted() -> bool {
|
||||
return animations_[current_animation_].completed;
|
||||
}
|
||||
|
||||
// Establece la animacion actual
|
||||
void AnimatedSprite::setCurrentAnimation(const std::string& name, bool reset) {
|
||||
const auto NEW_ANIMATION = getAnimationIndex(name);
|
||||
if (current_animation_ != NEW_ANIMATION) {
|
||||
const auto OLD_ANIMATION = current_animation_;
|
||||
current_animation_ = NEW_ANIMATION;
|
||||
if (reset) {
|
||||
animations_[current_animation_].current_frame = 0;
|
||||
animations_[current_animation_].time_accumulator = 0.0F;
|
||||
animations_[current_animation_].completed = false;
|
||||
} else {
|
||||
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size() - 1);
|
||||
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator;
|
||||
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
|
||||
}
|
||||
updateSpriteClip();
|
||||
}
|
||||
}
|
||||
|
||||
// Establece la animacion actual
|
||||
void AnimatedSprite::setCurrentAnimation(int index, bool reset) {
|
||||
const auto NEW_ANIMATION = index;
|
||||
if (current_animation_ != NEW_ANIMATION) {
|
||||
const auto OLD_ANIMATION = current_animation_;
|
||||
current_animation_ = NEW_ANIMATION;
|
||||
if (reset) {
|
||||
animations_[current_animation_].current_frame = 0;
|
||||
animations_[current_animation_].time_accumulator = 0.0F;
|
||||
animations_[current_animation_].completed = false;
|
||||
} else {
|
||||
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size());
|
||||
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator;
|
||||
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
|
||||
}
|
||||
updateSpriteClip();
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables del objeto (time-based)
|
||||
void AnimatedSprite::update(float delta_time) {
|
||||
animate(delta_time);
|
||||
MovingSprite::update(delta_time);
|
||||
}
|
||||
|
||||
// Reinicia la animación
|
||||
void AnimatedSprite::resetAnimation() {
|
||||
animations_[current_animation_].current_frame = 0;
|
||||
animations_[current_animation_].time_accumulator = 0.0F;
|
||||
animations_[current_animation_].completed = false;
|
||||
animations_[current_animation_].paused = false;
|
||||
updateSpriteClip();
|
||||
}
|
||||
|
||||
// Carga la animación desde un vector de cadenas
|
||||
void AnimatedSprite::loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source) {
|
||||
AnimationConfig config;
|
||||
|
||||
size_t index = 0;
|
||||
while (index < source.size()) {
|
||||
const std::string& line = source.at(index);
|
||||
|
||||
if (line == "[animation]") {
|
||||
index = processAnimationBlock(source, index, config);
|
||||
} else {
|
||||
processConfigLine(line, config);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
// Pone un valor por defecto
|
||||
setWidth(config.frame_width);
|
||||
setHeight(config.frame_height);
|
||||
|
||||
// Establece el primer frame inmediatamente si hay animaciones
|
||||
if (!animations_.empty()) {
|
||||
current_animation_ = 0;
|
||||
updateSpriteClip();
|
||||
}
|
||||
}
|
||||
|
||||
// Procesa una línea de configuración
|
||||
void AnimatedSprite::processConfigLine(const std::string& line, AnimationConfig& config) {
|
||||
size_t pos = line.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string key = line.substr(0, pos);
|
||||
int value = std::stoi(line.substr(pos + 1));
|
||||
|
||||
if (key == "frame_width") {
|
||||
config.frame_width = value;
|
||||
updateFrameCalculations(config);
|
||||
} else if (key == "frame_height") {
|
||||
config.frame_height = value;
|
||||
updateFrameCalculations(config);
|
||||
} else {
|
||||
std::cout << "Warning: unknown parameter " << key << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza los cálculos basados en las dimensiones del frame
|
||||
void AnimatedSprite::updateFrameCalculations(AnimationConfig& config) {
|
||||
config.frames_per_row = getTexture()->getWidth() / config.frame_width;
|
||||
const int WIDTH = getTexture()->getWidth() / config.frame_width;
|
||||
const int HEIGHT = getTexture()->getHeight() / config.frame_height;
|
||||
config.max_tiles = WIDTH * HEIGHT;
|
||||
}
|
||||
|
||||
// Procesa un bloque completo de animación
|
||||
auto AnimatedSprite::processAnimationBlock(const AnimationsFileBuffer& source, size_t start_index, const AnimationConfig& config) -> size_t {
|
||||
Animation animation;
|
||||
size_t index = start_index + 1; // Salta la línea "[animation]"
|
||||
|
||||
while (index < source.size()) {
|
||||
const std::string& line = source.at(index);
|
||||
|
||||
if (line == "[/animation]") {
|
||||
break;
|
||||
}
|
||||
|
||||
processAnimationParameter(line, animation, config);
|
||||
index++;
|
||||
}
|
||||
|
||||
// Añade la animación al vector de animaciones
|
||||
animations_.emplace_back(animation);
|
||||
|
||||
// Rellena el mapa con el nombre y el nuevo índice
|
||||
animation_indices_[animation.name] = animations_.size() - 1;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
// Procesa un parámetro individual de animación
|
||||
void AnimatedSprite::processAnimationParameter(const std::string& line, Animation& animation, const AnimationConfig& config) {
|
||||
size_t pos = line.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string key = line.substr(0, pos);
|
||||
std::string value = line.substr(pos + 1);
|
||||
|
||||
if (key == "name") {
|
||||
animation.name = value;
|
||||
} else if (key == "speed") {
|
||||
animation.speed = std::stof(value);
|
||||
} else if (key == "loop") {
|
||||
animation.loop = std::stoi(value);
|
||||
} else if (key == "frames") {
|
||||
parseFramesParameter(value, animation, config);
|
||||
} else {
|
||||
std::cout << "Warning: unknown parameter " << key << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Parsea el parámetro de frames (lista separada por comas)
|
||||
void AnimatedSprite::parseFramesParameter(const std::string& frames_str, Animation& animation, const AnimationConfig& config) {
|
||||
std::stringstream stream(frames_str);
|
||||
std::string tmp;
|
||||
SDL_FRect rect = {.x = 0, .y = 0, .w = config.frame_width, .h = config.frame_height};
|
||||
|
||||
while (getline(stream, tmp, ',')) {
|
||||
const int NUM_TILE = std::stoi(tmp);
|
||||
if (NUM_TILE <= config.max_tiles) {
|
||||
rect.x = (NUM_TILE % config.frames_per_row) * config.frame_width;
|
||||
rect.y = (NUM_TILE / config.frames_per_row) * config.frame_height;
|
||||
animation.frames.emplace_back(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Establece la velocidad de la animación
|
||||
void AnimatedSprite::setAnimationSpeed(float value) {
|
||||
animations_[current_animation_].speed = value;
|
||||
}
|
||||
|
||||
// Actualiza el spriteClip con el frame correspondiente
|
||||
void AnimatedSprite::updateSpriteClip() {
|
||||
setSpriteClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect
|
||||
|
||||
#include <cstddef> // Para size_t
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para basic_string, string, hash
|
||||
#include <unordered_map> // Para unordered_map
|
||||
#include <utility> // Para move
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "moving_sprite.hpp" // for MovingSprite
|
||||
|
||||
// Declaración adelantada
|
||||
class Texture;
|
||||
|
||||
// --- Estructuras ---
|
||||
struct Animation {
|
||||
static constexpr float DEFAULT_SPEED = 80.0F;
|
||||
|
||||
std::string name; // Nombre de la animación
|
||||
std::vector<SDL_FRect> frames; // Frames que componen la animación
|
||||
float speed{DEFAULT_SPEED}; // Velocidad de reproducción (ms entre frames)
|
||||
int loop{0}; // Frame de vuelta al terminar (-1 para no repetir)
|
||||
bool completed{false}; // Indica si la animación ha finalizado
|
||||
size_t current_frame{0}; // Frame actual en reproducción
|
||||
float time_accumulator{0.0F}; // Acumulador de tiempo para animaciones time-based
|
||||
bool paused{false}; // La animación no avanza
|
||||
|
||||
Animation() = default;
|
||||
};
|
||||
|
||||
struct AnimationConfig {
|
||||
float frame_width = 1.0F;
|
||||
float frame_height = 1.0F;
|
||||
int frames_per_row = 1;
|
||||
int max_tiles = 1;
|
||||
};
|
||||
|
||||
// --- Tipos ---
|
||||
using AnimationsFileBuffer = std::vector<std::string>; // Buffer de animaciones
|
||||
|
||||
// --- Funciones ---
|
||||
auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffer; // Carga las animaciones desde un fichero
|
||||
|
||||
// --- Clase AnimatedSprite: sprite animado que hereda de MovingSprite ---
|
||||
class AnimatedSprite : public MovingSprite {
|
||||
public:
|
||||
// --- Constructores y destructor ---
|
||||
AnimatedSprite(std::shared_ptr<Texture> texture, const std::string& file_path);
|
||||
AnimatedSprite(std::shared_ptr<Texture> texture, const AnimationsFileBuffer& animations);
|
||||
explicit AnimatedSprite(std::shared_ptr<Texture> texture)
|
||||
: MovingSprite(std::move(texture)) {}
|
||||
~AnimatedSprite() override = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time) override; // Actualiza la animación (time-based)
|
||||
|
||||
// --- Control de animaciones ---
|
||||
void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre
|
||||
void setCurrentAnimation(int index = 0, bool reset = true); // Establece la animación por índice
|
||||
void resetAnimation(); // Reinicia la animación actual
|
||||
void setAnimationSpeed(float value); // Establece la velocidad de la animación
|
||||
[[nodiscard]] auto getAnimationSpeed() const -> float { return animations_[current_animation_].speed; } // Obtiene la velocidad de la animación actual
|
||||
void animtionPause() { animations_[current_animation_].paused = true; } // Detiene la animación
|
||||
void animationResume() { animations_[current_animation_].paused = false; } // Reanuda la animación
|
||||
[[nodiscard]] auto getCurrentAnimationFrame() const -> size_t { return animations_[current_animation_].current_frame; } // Obtiene el numero de frame de la animación actual
|
||||
|
||||
// --- Consultas ---
|
||||
auto animationIsCompleted() -> bool; // Comprueba si la animación ha terminado
|
||||
auto getAnimationIndex(const std::string& name) -> int; // Obtiene el índice de una animación por nombre
|
||||
|
||||
protected:
|
||||
// --- Variables de estado ---
|
||||
std::vector<Animation> animations_; // Vector de animaciones disponibles
|
||||
std::unordered_map<std::string, int> animation_indices_; // Mapa para búsqueda rápida por nombre
|
||||
int current_animation_ = 0; // Índice de la animación activa
|
||||
|
||||
// --- Métodos internos ---
|
||||
void animate(float delta_time); // Calcula el frame correspondiente a la animación (time-based)
|
||||
void loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source); // Carga la animación desde un vector de cadenas
|
||||
void processConfigLine(const std::string& line, AnimationConfig& config); // Procesa una línea de configuración
|
||||
void updateFrameCalculations(AnimationConfig& config); // Actualiza los cálculos basados en las dimensiones del frame
|
||||
auto processAnimationBlock(const AnimationsFileBuffer& source, size_t start_index, const AnimationConfig& config) -> size_t; // Procesa un bloque completo de animación
|
||||
static void processAnimationParameter(const std::string& line, Animation& animation, const AnimationConfig& config); // Procesa un parámetro individual de animación
|
||||
static void parseFramesParameter(const std::string& frames_str, Animation& animation, const AnimationConfig& config); // Parsea el parámetro de frames (lista separada por comas)
|
||||
void updateSpriteClip(); // Actualiza el spriteClip con el frame correspondiente
|
||||
};
|
||||
@@ -0,0 +1,255 @@
|
||||
#include "card_sprite.hpp"
|
||||
|
||||
#include <algorithm> // Para std::clamp
|
||||
#include <functional> // Para function
|
||||
#include <utility> // Para move
|
||||
|
||||
#include "texture.hpp" // Para Texture
|
||||
#include "utils.hpp" // Para easeOutBounce, easeOutCubic
|
||||
|
||||
// Constructor
|
||||
CardSprite::CardSprite(std::shared_ptr<Texture> texture)
|
||||
: MovingSprite(std::move(texture)),
|
||||
entry_easing_(easeOutBounce) {}
|
||||
|
||||
// Inicia la animación de entrada (solo si está en IDLE)
|
||||
auto CardSprite::enable() -> bool {
|
||||
if (state_ != CardState::IDLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = CardState::ENTERING;
|
||||
entry_elapsed_ = 0.0F;
|
||||
first_touch_ = false;
|
||||
|
||||
// Posición inicial (borde de pantalla)
|
||||
setPos(entry_start_x_, entry_start_y_);
|
||||
|
||||
// Zoom inicial grande (como si estuviera cerca de la cámara)
|
||||
horizontal_zoom_ = start_zoom_;
|
||||
vertical_zoom_ = start_zoom_;
|
||||
|
||||
// Ángulo inicial
|
||||
rotate_.angle = start_angle_;
|
||||
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
|
||||
|
||||
shadow_visible_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Inicia la animación de salida (solo si está en LANDED)
|
||||
void CardSprite::startExit() {
|
||||
if (state_ != CardState::LANDED) {
|
||||
return;
|
||||
}
|
||||
|
||||
state_ = CardState::EXITING;
|
||||
shadow_visible_ = true;
|
||||
|
||||
// Velocidad y aceleración de salida
|
||||
vx_ = exit_vx_;
|
||||
vy_ = exit_vy_;
|
||||
ax_ = exit_ax_;
|
||||
ay_ = exit_ay_;
|
||||
|
||||
// Rotación continua
|
||||
rotate_.enabled = true;
|
||||
rotate_.amount = exit_rotate_amount_;
|
||||
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
|
||||
}
|
||||
|
||||
// Actualiza según el estado
|
||||
void CardSprite::update(float delta_time) {
|
||||
switch (state_) {
|
||||
case CardState::ENTERING:
|
||||
updateEntering(delta_time);
|
||||
break;
|
||||
case CardState::EXITING:
|
||||
updateExiting(delta_time);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Animación de entrada: interpola posición, zoom y ángulo
|
||||
void CardSprite::updateEntering(float delta_time) {
|
||||
entry_elapsed_ += delta_time;
|
||||
|
||||
float progress = std::clamp(entry_elapsed_ / entry_duration_s_, 0.0F, 1.0F);
|
||||
double eased = entry_easing_(static_cast<double>(progress));
|
||||
|
||||
// Zoom: de start_zoom_ a 1.0 con rebote
|
||||
auto current_zoom = static_cast<float>(start_zoom_ + (1.0 - start_zoom_) * eased);
|
||||
horizontal_zoom_ = current_zoom;
|
||||
vertical_zoom_ = current_zoom;
|
||||
|
||||
// Ángulo: de start_angle_ a 0 con rebote
|
||||
rotate_.angle = start_angle_ * (1.0 - eased);
|
||||
|
||||
// Posición: de entry_start a landing con easing suave (sin rebote)
|
||||
// Usamos easeOutCubic para que el desplazamiento sea fluido
|
||||
double pos_eased = easeOutCubic(static_cast<double>(progress));
|
||||
auto current_x = static_cast<float>(entry_start_x_ + (landing_x_ - entry_start_x_) * pos_eased);
|
||||
auto current_y = static_cast<float>(entry_start_y_ + (landing_y_ - entry_start_y_) * pos_eased);
|
||||
setPos(current_x, current_y);
|
||||
|
||||
// Detecta el primer toque (cuando el easing alcanza ~1.0 por primera vez)
|
||||
if (!first_touch_ && eased >= FIRST_TOUCH_THRESHOLD) {
|
||||
first_touch_ = true;
|
||||
}
|
||||
|
||||
// Transición a LANDED cuando termina la animación completa
|
||||
if (progress >= 1.0F) {
|
||||
horizontal_zoom_ = 1.0F;
|
||||
vertical_zoom_ = 1.0F;
|
||||
rotate_.angle = 0.0;
|
||||
setPos(landing_x_, landing_y_);
|
||||
state_ = CardState::LANDED;
|
||||
first_touch_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Animación de salida: movimiento + rotación continua + zoom opcional
|
||||
void CardSprite::updateExiting(float delta_time) {
|
||||
move(delta_time);
|
||||
rotate(delta_time);
|
||||
|
||||
// Ganar altura gradualmente (zoom hacia el objetivo)
|
||||
if (exit_zoom_speed_ > 0.0F && horizontal_zoom_ < exit_target_zoom_) {
|
||||
float new_zoom = horizontal_zoom_ + exit_zoom_speed_ * delta_time;
|
||||
if (new_zoom > exit_target_zoom_) {
|
||||
new_zoom = exit_target_zoom_;
|
||||
}
|
||||
horizontal_zoom_ = new_zoom;
|
||||
vertical_zoom_ = new_zoom;
|
||||
}
|
||||
|
||||
if (isOffScreen()) {
|
||||
state_ = CardState::FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza el sprite y su sombra
|
||||
void CardSprite::render() {
|
||||
if (state_ == CardState::IDLE || state_ == CardState::FINISHED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sombra primero (debajo de la tarjeta)
|
||||
if (shadow_visible_ && shadow_texture_) {
|
||||
renderShadow();
|
||||
}
|
||||
|
||||
// Tarjeta
|
||||
MovingSprite::render();
|
||||
}
|
||||
|
||||
// Renderiza la sombra con efecto de perspectiva 2D→3D (efecto helicóptero)
|
||||
//
|
||||
// Fuente de luz en la esquina superior izquierda (0,0).
|
||||
// La sombra se mueve con la tarjeta pero desplazada en dirección opuesta a la luz
|
||||
// (abajo-derecha a 45°). Cuanto más alta la tarjeta (zoom > 1.0):
|
||||
// - Más separada de la tarjeta (offset grande)
|
||||
// - Más pequeña (proyección lejana)
|
||||
// Cuando la tarjeta está en la mesa (zoom=1.0):
|
||||
// - Sombra pegada con offset base
|
||||
// - Tamaño real
|
||||
void CardSprite::renderShadow() {
|
||||
// Altura sobre la mesa: 0.0 = en la mesa, 0.8 = alta (zoom 1.8)
|
||||
float height = horizontal_zoom_ - 1.0F;
|
||||
|
||||
// Escala: más pequeña cuanto más alta
|
||||
float shadow_zoom = 1.0F / horizontal_zoom_;
|
||||
|
||||
// Offset respecto a la tarjeta: base + extra proporcional a la altura
|
||||
// La sombra se aleja en diagonal abajo-derecha (opuesta a la luz en 0,0)
|
||||
float offset_x = shadow_offset_x_ + height * SHADOW_HEIGHT_MULTIPLIER;
|
||||
float offset_y = shadow_offset_y_ + height * SHADOW_HEIGHT_MULTIPLIER;
|
||||
|
||||
shadow_texture_->render(
|
||||
pos_.x + offset_x,
|
||||
pos_.y + offset_y,
|
||||
&sprite_clip_,
|
||||
shadow_zoom,
|
||||
shadow_zoom,
|
||||
rotate_.angle,
|
||||
&rotate_.center,
|
||||
flip_);
|
||||
}
|
||||
|
||||
// Comprueba si el sprite está fuera de pantalla
|
||||
auto CardSprite::isOffScreen() const -> bool {
|
||||
float effective_width = pos_.w * horizontal_zoom_;
|
||||
float effective_height = pos_.h * vertical_zoom_;
|
||||
return (pos_.x + effective_width < -OFF_SCREEN_MARGIN ||
|
||||
pos_.x > screen_width_ + OFF_SCREEN_MARGIN ||
|
||||
pos_.y + effective_height < -OFF_SCREEN_MARGIN ||
|
||||
pos_.y > screen_height_ + OFF_SCREEN_MARGIN);
|
||||
}
|
||||
|
||||
// --- Consultas de estado ---
|
||||
auto CardSprite::hasLanded() const -> bool {
|
||||
return state_ == CardState::LANDED || state_ == CardState::EXITING || state_ == CardState::FINISHED;
|
||||
}
|
||||
|
||||
auto CardSprite::hasFirstTouch() const -> bool {
|
||||
return first_touch_;
|
||||
}
|
||||
|
||||
auto CardSprite::hasFinished() const -> bool {
|
||||
return state_ == CardState::FINISHED;
|
||||
}
|
||||
|
||||
auto CardSprite::isExiting() const -> bool {
|
||||
return state_ == CardState::EXITING;
|
||||
}
|
||||
|
||||
auto CardSprite::getState() const -> CardState {
|
||||
return state_;
|
||||
}
|
||||
|
||||
// --- Configuración ---
|
||||
void CardSprite::setEntryParams(float start_zoom, double start_angle, float duration_s, std::function<double(double)> easing) {
|
||||
start_zoom_ = start_zoom;
|
||||
start_angle_ = start_angle;
|
||||
entry_duration_s_ = duration_s;
|
||||
entry_easing_ = std::move(easing);
|
||||
}
|
||||
|
||||
void CardSprite::setEntryPosition(float start_x, float start_y) {
|
||||
entry_start_x_ = start_x;
|
||||
entry_start_y_ = start_y;
|
||||
}
|
||||
|
||||
void CardSprite::setLandingPosition(float x, float y) {
|
||||
landing_x_ = x;
|
||||
landing_y_ = y;
|
||||
}
|
||||
|
||||
void CardSprite::setExitParams(float vx, float vy, float ax, float ay, double rotate_amount) {
|
||||
exit_vx_ = vx;
|
||||
exit_vy_ = vy;
|
||||
exit_ax_ = ax;
|
||||
exit_ay_ = ay;
|
||||
exit_rotate_amount_ = rotate_amount;
|
||||
}
|
||||
|
||||
void CardSprite::setExitLift(float target_zoom, float zoom_speed) {
|
||||
exit_target_zoom_ = target_zoom;
|
||||
exit_zoom_speed_ = zoom_speed;
|
||||
}
|
||||
|
||||
void CardSprite::setShadowTexture(std::shared_ptr<Texture> texture) {
|
||||
shadow_texture_ = std::move(texture);
|
||||
}
|
||||
|
||||
void CardSprite::setShadowOffset(float offset_x, float offset_y) {
|
||||
shadow_offset_x_ = offset_x;
|
||||
shadow_offset_y_ = offset_y;
|
||||
}
|
||||
|
||||
void CardSprite::setScreenBounds(float width, float height) {
|
||||
screen_width_ = width;
|
||||
screen_height_ = height;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FPoint
|
||||
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para shared_ptr
|
||||
|
||||
#include "moving_sprite.hpp" // Para MovingSprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Estados de la tarjeta ---
|
||||
enum class CardState {
|
||||
IDLE, // No activada todavía
|
||||
ENTERING, // Animación de entrada (zoom + rotación + desplazamiento con rebote)
|
||||
LANDED, // En reposo sobre la mesa
|
||||
EXITING, // Saliendo de pantalla girando
|
||||
FINISHED, // Fuera de pantalla
|
||||
};
|
||||
|
||||
// --- Clase CardSprite: tarjeta animada con zoom, rotación y sombra integrada ---
|
||||
//
|
||||
// Simula una tarjeta lanzada sobre una mesa desde un borde de la pantalla.
|
||||
// Durante la entrada, interpola posición, zoom y rotación con easing (rebote).
|
||||
// Durante la salida, se desplaza fuera de pantalla girando, sin sombra.
|
||||
class CardSprite : public MovingSprite {
|
||||
public:
|
||||
explicit CardSprite(std::shared_ptr<Texture> texture);
|
||||
~CardSprite() override = default;
|
||||
|
||||
// --- Ciclo principal ---
|
||||
void update(float delta_time) override;
|
||||
void render() override;
|
||||
|
||||
// --- Control de estado ---
|
||||
auto enable() -> bool; // Inicia la animación de entrada (true si se activó)
|
||||
void startExit(); // Inicia la animación de salida
|
||||
|
||||
// --- Consultas de estado ---
|
||||
[[nodiscard]] auto hasLanded() const -> bool; // ¿Ha aterrizado definitivamente?
|
||||
[[nodiscard]] auto hasFirstTouch() const -> bool; // ¿Ha tocado la mesa por primera vez? (primer rebote)
|
||||
[[nodiscard]] auto hasFinished() const -> bool; // ¿Ha terminado completamente?
|
||||
[[nodiscard]] auto isExiting() const -> bool; // ¿Está saliendo de pantalla?
|
||||
[[nodiscard]] auto getState() const -> CardState; // Estado actual
|
||||
|
||||
// --- Configuración de entrada ---
|
||||
void setEntryParams(float start_zoom, double start_angle, float duration_s, std::function<double(double)> easing);
|
||||
void setEntryPosition(float start_x, float start_y); // Posición inicial (borde de pantalla)
|
||||
void setLandingPosition(float x, float y); // Posición final centrada
|
||||
|
||||
// --- Configuración de salida ---
|
||||
void setExitParams(float vx, float vy, float ax, float ay, double rotate_amount);
|
||||
void setExitLift(float target_zoom, float zoom_speed); // Ganar altura al salir (zoom > 1.0)
|
||||
|
||||
// --- Sombra ---
|
||||
void setShadowTexture(std::shared_ptr<Texture> texture);
|
||||
void setShadowOffset(float offset_x, float offset_y);
|
||||
|
||||
// --- Limites de pantalla (para detectar salida) ---
|
||||
void setScreenBounds(float width, float height);
|
||||
|
||||
private:
|
||||
// --- Estado ---
|
||||
CardState state_ = CardState::IDLE;
|
||||
bool first_touch_ = false; // Primer contacto con la mesa (eased >= umbral)
|
||||
|
||||
// --- Umbral para detectar el primer toque ---
|
||||
static constexpr double FIRST_TOUCH_THRESHOLD = 0.98;
|
||||
|
||||
// --- Parámetros de entrada ---
|
||||
float start_zoom_ = 1.8F;
|
||||
double start_angle_ = 15.0;
|
||||
float entry_duration_s_ = 1.5F;
|
||||
float entry_elapsed_ = 0.0F;
|
||||
std::function<double(double)> entry_easing_;
|
||||
float entry_start_x_ = 0.0F; // Posición inicial X (borde)
|
||||
float entry_start_y_ = 0.0F; // Posición inicial Y (borde)
|
||||
float landing_x_ = 0.0F;
|
||||
float landing_y_ = 0.0F;
|
||||
|
||||
// --- Parámetros de salida ---
|
||||
float exit_vx_ = 0.0F;
|
||||
float exit_vy_ = 0.0F;
|
||||
float exit_ax_ = 0.0F;
|
||||
float exit_ay_ = 0.0F;
|
||||
double exit_rotate_amount_ = 0.0;
|
||||
float exit_target_zoom_ = 1.0F; // Zoom objetivo al salir (>1.0 = se eleva)
|
||||
float exit_zoom_speed_ = 0.0F; // Velocidad de cambio de zoom por segundo
|
||||
|
||||
// --- Sombra ---
|
||||
std::shared_ptr<Texture> shadow_texture_;
|
||||
float shadow_offset_x_ = 8.0F;
|
||||
float shadow_offset_y_ = 8.0F;
|
||||
bool shadow_visible_ = true;
|
||||
|
||||
// --- Límites de pantalla ---
|
||||
float screen_width_ = 320.0F;
|
||||
float screen_height_ = 240.0F;
|
||||
|
||||
// --- Constantes ---
|
||||
static constexpr float OFF_SCREEN_MARGIN = 50.0F; // Margen fuera de pantalla para considerar FINISHED
|
||||
static constexpr float SHADOW_HEIGHT_MULTIPLIER = 400.0F; // Pixels de separación de sombra por unidad de altura
|
||||
|
||||
// --- Métodos internos ---
|
||||
void updateEntering(float delta_time);
|
||||
void updateExiting(float delta_time);
|
||||
void renderShadow();
|
||||
[[nodiscard]] auto isOffScreen() const -> bool;
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
#include "moving_sprite.hpp"
|
||||
|
||||
#include <cmath> // Para std::abs
|
||||
#include <utility>
|
||||
|
||||
#include "texture.hpp" // Para Texture
|
||||
|
||||
// Constructor
|
||||
MovingSprite::MovingSprite(std::shared_ptr<Texture> texture, SDL_FRect pos, Rotate rotate, float horizontal_zoom, float vertical_zoom, SDL_FlipMode flip)
|
||||
: Sprite(std::move(texture), pos),
|
||||
rotate_(rotate),
|
||||
flip_(flip),
|
||||
x_(pos.x),
|
||||
y_(pos.y),
|
||||
horizontal_zoom_(horizontal_zoom),
|
||||
vertical_zoom_(vertical_zoom) {}
|
||||
|
||||
MovingSprite::MovingSprite(std::shared_ptr<Texture> texture, SDL_FRect pos)
|
||||
: Sprite(std::move(texture), pos),
|
||||
flip_(SDL_FLIP_NONE),
|
||||
x_(pos.x),
|
||||
y_(pos.y),
|
||||
horizontal_zoom_(1.0F),
|
||||
vertical_zoom_(1.0F) {}
|
||||
|
||||
MovingSprite::MovingSprite(std::shared_ptr<Texture> texture)
|
||||
: Sprite(std::move(texture)),
|
||||
flip_(SDL_FLIP_NONE),
|
||||
horizontal_zoom_(1.0F),
|
||||
vertical_zoom_(1.0F) { Sprite::clear(); }
|
||||
|
||||
// Reinicia todas las variables
|
||||
void MovingSprite::clear() {
|
||||
stop();
|
||||
Sprite::clear();
|
||||
}
|
||||
|
||||
// Elimina el movimiento del sprite
|
||||
void MovingSprite::stop() {
|
||||
x_ = 0.0F; // Posición en el eje X
|
||||
y_ = 0.0F; // Posición en el eje Y
|
||||
|
||||
vx_ = 0.0F; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
|
||||
vy_ = 0.0F; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
|
||||
|
||||
ax_ = 0.0F; // Aceleración en el eje X. Variación de la velocidad
|
||||
ay_ = 0.0F; // Aceleración en el eje Y. Variación de la velocidad
|
||||
|
||||
rotate_ = Rotate(); // Inicializa la estructura
|
||||
|
||||
horizontal_zoom_ = 1.0F; // Zoom aplicado a la anchura
|
||||
vertical_zoom_ = 1.0F; // Zoom aplicado a la altura
|
||||
|
||||
flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite
|
||||
}
|
||||
|
||||
// Mueve el sprite (time-based)
|
||||
void MovingSprite::move(float delta_time) {
|
||||
// DeltaTime puro: velocidad (pixels/ms) * tiempo (ms)
|
||||
x_ += vx_ * delta_time;
|
||||
y_ += vy_ * delta_time;
|
||||
|
||||
// Aceleración (pixels/ms²) * tiempo (ms)
|
||||
vx_ += ax_ * delta_time;
|
||||
vy_ += ay_ * delta_time;
|
||||
|
||||
pos_.x = static_cast<int>(x_);
|
||||
pos_.y = static_cast<int>(y_);
|
||||
}
|
||||
|
||||
// Actualiza las variables internas del objeto (time-based)
|
||||
void MovingSprite::update(float delta_time) {
|
||||
move(delta_time);
|
||||
rotate(delta_time);
|
||||
}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void MovingSprite::render() {
|
||||
getTexture()->render(pos_.x, pos_.y, &sprite_clip_, horizontal_zoom_, vertical_zoom_, rotate_.angle, &rotate_.center, flip_);
|
||||
}
|
||||
|
||||
// Establece la rotacion (time-based)
|
||||
void MovingSprite::rotate(float delta_time) {
|
||||
if (rotate_.enabled) {
|
||||
rotate_.angle += rotate_.amount * delta_time;
|
||||
}
|
||||
}
|
||||
|
||||
// Activa o desactiva el efecto de rotación
|
||||
void MovingSprite::setRotate(bool enable) {
|
||||
rotate_.enabled = enable;
|
||||
}
|
||||
|
||||
// Habilita la rotación y establece el centro en el centro del sprite
|
||||
void MovingSprite::startRotate() {
|
||||
rotate_.enabled = true;
|
||||
rotate_.center.x = pos_.w / 2.0F;
|
||||
rotate_.center.y = pos_.h / 2.0F;
|
||||
}
|
||||
|
||||
// Detiene la rotación y resetea el ángulo a cero
|
||||
void MovingSprite::stopRotate(float threshold) {
|
||||
if (threshold == 0.0F || std::abs(rotate_.amount) <= threshold) {
|
||||
rotate_.enabled = false;
|
||||
rotate_.angle = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece la posición y_ el tamaño del objeto
|
||||
void MovingSprite::setPos(SDL_FRect rect) {
|
||||
x_ = rect.x;
|
||||
y_ = rect.y;
|
||||
|
||||
pos_ = rect;
|
||||
}
|
||||
|
||||
// Establece el valor de las variables
|
||||
void MovingSprite::setPos(float pos_x, float pos_y) {
|
||||
x_ = pos_x;
|
||||
y_ = pos_y;
|
||||
|
||||
pos_.x = static_cast<int>(x_);
|
||||
pos_.y = static_cast<int>(y_);
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setPosX(float pos_x) {
|
||||
x_ = pos_x;
|
||||
pos_.x = static_cast<int>(x_);
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setPosY(float pos_y) {
|
||||
y_ = pos_y;
|
||||
pos_.y = static_cast<int>(y_);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FlipMode, SDL_FPoint, SDL_FRect
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
|
||||
#include "sprite.hpp" // for Sprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Clase MovingSprite: añade movimiento y efectos de rotación, zoom y flip al sprite ---
|
||||
class MovingSprite : public Sprite {
|
||||
public:
|
||||
// --- Estructuras ---
|
||||
struct Rotate {
|
||||
bool enabled{false}; // Indica si ha de rotar
|
||||
double angle{0.0}; // Ángulo para dibujarlo
|
||||
float amount{0.0F}; // Cantidad de grados a girar en cada iteración
|
||||
SDL_FPoint center{.x = 0.0F, .y = 0.0F}; // Centro de rotación
|
||||
};
|
||||
|
||||
// --- Constructores y destructor ---
|
||||
MovingSprite(std::shared_ptr<Texture> texture, SDL_FRect pos, MovingSprite::Rotate rotate, float horizontal_zoom, float vertical_zoom, SDL_FlipMode flip);
|
||||
MovingSprite(std::shared_ptr<Texture> texture, SDL_FRect pos);
|
||||
explicit MovingSprite(std::shared_ptr<Texture> texture);
|
||||
~MovingSprite() override = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
virtual void update(float delta_time); // Actualiza las variables internas del objeto (time-based)
|
||||
void clear() override; // Reinicia todas las variables a cero
|
||||
void stop(); // Elimina el movimiento del sprite
|
||||
void render() override; // Muestra el sprite por pantalla
|
||||
|
||||
// --- Configuración ---
|
||||
void setPos(SDL_FRect rect); // Establece la posición y el tamaño del objeto
|
||||
void setPos(float pos_x, float pos_y); // Establece la posición del objeto
|
||||
void setPosX(float pos_x); // Establece la posición X
|
||||
void setPosY(float pos_y); // Establece la posición Y
|
||||
void setVelX(float value) { vx_ = value; } // Establece la velocidad X
|
||||
void setVelY(float value) { vy_ = value; } // Establece la velocidad Y
|
||||
void setAccelX(float value) { ax_ = value; } // Establece la aceleración X
|
||||
void setAccelY(float value) { ay_ = value; } // Establece la aceleración Y
|
||||
void setHorizontalZoom(float value) { horizontal_zoom_ = value; } // Establece el zoom horizontal
|
||||
void setVerticalZoom(float value) { vertical_zoom_ = value; } // Establece el zoom vertical
|
||||
void setAngle(double value) { rotate_.angle = value; } // Establece el ángulo
|
||||
void setRotatingCenter(SDL_FPoint point) { rotate_.center = point; } // Establece el centro de rotación
|
||||
void setRotate(bool enable); // Activa o desactiva el efecto de rotación
|
||||
void startRotate(); // Habilita la rotación con centro automático
|
||||
void stopRotate(float threshold = 0.0F); // Detiene la rotación y resetea ángulo
|
||||
void setRotateAmount(double value) { rotate_.amount = value; } // Establece la velocidad de rotación
|
||||
void scaleRotateAmount(float value) { rotate_.amount *= value; } // Modifica la velocidad de rotacion
|
||||
void switchRotate() { rotate_.amount *= -1; } // Cambia el sentido de la rotación
|
||||
void setFlip(SDL_FlipMode flip) { flip_ = flip; } // Establece el flip
|
||||
void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; } // Cambia el flip
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getPosX() const -> float { return x_; } // Obtiene la posición X
|
||||
[[nodiscard]] auto getPosY() const -> float { return y_; } // Obtiene la posición Y
|
||||
[[nodiscard]] auto getVelX() const -> float { return vx_; } // Obtiene la velocidad X
|
||||
[[nodiscard]] auto getVelY() const -> float { return vy_; } // Obtiene la velocidad Y
|
||||
[[nodiscard]] auto getAccelX() const -> float { return ax_; } // Obtiene la aceleración X
|
||||
[[nodiscard]] auto getAccelY() const -> float { return ay_; } // Obtiene la aceleración Y
|
||||
[[nodiscard]] auto isRotating() const -> bool { return rotate_.enabled; } // Verifica si está rotando
|
||||
auto getFlip() -> SDL_FlipMode { return flip_; } // Obtiene el flip
|
||||
|
||||
protected:
|
||||
// --- Variables de estado ---
|
||||
Rotate rotate_; // Variables usadas para controlar la rotación del sprite
|
||||
SDL_FlipMode flip_; // Indica cómo se voltea el sprite
|
||||
float x_ = 0.0F; // Posición en el eje X
|
||||
float y_ = 0.0F; // Posición en el eje Y
|
||||
float vx_ = 0.0F; // Velocidad en el eje X
|
||||
float vy_ = 0.0F; // Velocidad en el eje Y
|
||||
float ax_ = 0.0F; // Aceleración en el eje X
|
||||
float ay_ = 0.0F; // Aceleración en el eje Y
|
||||
float horizontal_zoom_; // Zoom aplicado a la anchura
|
||||
float vertical_zoom_; // Zoom aplicado a la altura
|
||||
|
||||
// --- Métodos internos ---
|
||||
void updateAngle() { rotate_.angle += rotate_.amount; } // Incrementa el valor del ángulo
|
||||
void move(float delta_time); // Mueve el sprite según velocidad y aceleración (time-based)
|
||||
void rotate(float delta_time); // Rota el sprite según los parámetros de rotación (time-based)
|
||||
};
|
||||
@@ -0,0 +1,233 @@
|
||||
// IWYU pragma: no_include <bits/std_abs.h>
|
||||
#include "path_sprite.hpp"
|
||||
|
||||
#include <cmath> // Para abs
|
||||
#include <functional> // Para function
|
||||
#include <utility> // Para move
|
||||
|
||||
// Constructor para paths por puntos (convertido a segundos)
|
||||
Path::Path(const std::vector<SDL_FPoint>& spots_init, float waiting_time_s_init)
|
||||
: spots(spots_init),
|
||||
is_point_path(true) {
|
||||
waiting_time_s = waiting_time_s_init;
|
||||
}
|
||||
|
||||
// Devuelve un vector con los puntos que conforman la ruta
|
||||
auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)>& easing_function) -> std::vector<SDL_FPoint> {
|
||||
std::vector<SDL_FPoint> v;
|
||||
v.reserve(steps);
|
||||
|
||||
for (int i = 0; i < steps; ++i) {
|
||||
double t = static_cast<double>(i) / (steps - 1);
|
||||
double value = start + ((end - start) * easing_function(t));
|
||||
|
||||
if ((start > 0 && end < 0) || (start < 0 && end > 0)) {
|
||||
value = start + ((end > 0 ? 1 : -1) * std::abs(end - start) * easing_function(t));
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case PathType::HORIZONTAL:
|
||||
v.emplace_back(SDL_FPoint{.x = static_cast<float>(value), .y = fixed_pos});
|
||||
break;
|
||||
case PathType::VERTICAL:
|
||||
v.emplace_back(SDL_FPoint{.x = fixed_pos, .y = static_cast<float>(value)});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
// Actualiza la posición y comprueba si ha llegado a su destino
|
||||
void PathSprite::update(float delta_time) {
|
||||
if (enabled_ && !has_finished_) {
|
||||
moveThroughCurrentPath(delta_time);
|
||||
goToNextPathOrDie();
|
||||
}
|
||||
}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void PathSprite::render() {
|
||||
if (enabled_) {
|
||||
Sprite::render();
|
||||
}
|
||||
}
|
||||
|
||||
// Determina el tipo de centrado basado en el tipo de path
|
||||
auto PathSprite::determineCenteringType(const Path& path, bool centered) -> PathCentered {
|
||||
if (!centered) {
|
||||
return PathCentered::NONE;
|
||||
}
|
||||
|
||||
if (path.is_point_path) {
|
||||
// Lógica de centrado para paths por PUNTOS
|
||||
if (!path.spots.empty()) {
|
||||
// Si X es constante, es un path Vertical, centramos en X
|
||||
return (path.spots.back().x == path.spots.front().x) ? PathCentered::ON_X : PathCentered::ON_Y;
|
||||
}
|
||||
return PathCentered::NONE;
|
||||
}
|
||||
|
||||
// Lógica de centrado para paths GENERADOS
|
||||
// Si el tipo es Vertical, centramos en X
|
||||
return (path.type == PathType::VERTICAL) ? PathCentered::ON_X : PathCentered::ON_Y;
|
||||
}
|
||||
|
||||
// Aplica centrado en el eje X (para paths verticales)
|
||||
void PathSprite::centerPathOnX(Path& path, float offset) {
|
||||
if (path.is_point_path) {
|
||||
const float X_BASE = !path.spots.empty() ? path.spots.front().x : 0.0F;
|
||||
const float X = X_BASE - offset;
|
||||
for (auto& spot : path.spots) {
|
||||
spot.x = X;
|
||||
}
|
||||
} else {
|
||||
// Es un path generado, ajustamos la posición fija (que es X)
|
||||
path.fixed_pos -= offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica centrado en el eje Y (para paths horizontales)
|
||||
void PathSprite::centerPathOnY(Path& path, float offset) {
|
||||
if (path.is_point_path) {
|
||||
const float Y_BASE = !path.spots.empty() ? path.spots.front().y : 0.0F;
|
||||
const float Y = Y_BASE - offset;
|
||||
for (auto& spot : path.spots) {
|
||||
spot.y = Y;
|
||||
}
|
||||
} else {
|
||||
// Es un path generado, ajustamos la posición fija (que es Y)
|
||||
path.fixed_pos -= offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Añade un recorrido
|
||||
void PathSprite::addPath(Path path, bool centered) {
|
||||
PathCentered path_centered = determineCenteringType(path, centered);
|
||||
|
||||
switch (path_centered) {
|
||||
case PathCentered::ON_X:
|
||||
centerPathOnX(path, pos_.w / 2.0F);
|
||||
break;
|
||||
case PathCentered::ON_Y:
|
||||
centerPathOnY(path, pos_.h / 2.0F);
|
||||
break;
|
||||
case PathCentered::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
paths_.emplace_back(std::move(path));
|
||||
}
|
||||
|
||||
// Añade un recorrido generado (en segundos)
|
||||
void PathSprite::addPath(float start, float end, PathType type, float fixed_pos, float duration_s, const std::function<double(double)>& easing_function, float waiting_time_s) {
|
||||
paths_.emplace_back(start, end, type, fixed_pos, duration_s, waiting_time_s, easing_function);
|
||||
}
|
||||
|
||||
// Añade un recorrido por puntos (en segundos)
|
||||
void PathSprite::addPath(const std::vector<SDL_FPoint>& spots, float waiting_time_s) {
|
||||
paths_.emplace_back(spots, waiting_time_s);
|
||||
}
|
||||
|
||||
// Habilita el objeto
|
||||
void PathSprite::enable() {
|
||||
if (paths_.empty() || enabled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabled_ = true;
|
||||
|
||||
// Establece la posición inicial
|
||||
auto& path = paths_.at(current_path_);
|
||||
if (path.is_point_path) {
|
||||
const auto& p = path.spots.at(path.counter);
|
||||
setPosition(p);
|
||||
} else {
|
||||
// Para paths generados, establecer posición inicial
|
||||
SDL_FPoint initial_pos;
|
||||
if (path.type == PathType::HORIZONTAL) {
|
||||
initial_pos = {.x = path.start_pos, .y = path.fixed_pos};
|
||||
} else {
|
||||
initial_pos = {.x = path.fixed_pos, .y = path.start_pos};
|
||||
}
|
||||
setPosition(initial_pos);
|
||||
}
|
||||
}
|
||||
|
||||
// Coloca el sprite en los diferentes puntos del recorrido
|
||||
void PathSprite::moveThroughCurrentPath(float delta_time) {
|
||||
auto& path = paths_.at(current_path_);
|
||||
|
||||
if (path.is_point_path) {
|
||||
// Lógica para paths por puntos (compatibilidad)
|
||||
const auto& p = path.spots.at(path.counter);
|
||||
setPosition(p);
|
||||
|
||||
if (!path.on_destination) {
|
||||
++path.counter;
|
||||
if (std::cmp_greater_equal(path.counter, path.spots.size())) {
|
||||
path.on_destination = true;
|
||||
path.counter = static_cast<int>(path.spots.size()) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.on_destination) {
|
||||
path.waiting_elapsed += delta_time;
|
||||
if (path.waiting_elapsed >= path.waiting_time_s) {
|
||||
path.finished = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Lógica para paths generados en tiempo real
|
||||
if (!path.on_destination) {
|
||||
path.elapsed_time += delta_time;
|
||||
|
||||
// Calcular progreso (0.0 a 1.0)
|
||||
float progress = path.elapsed_time / path.duration_s;
|
||||
if (progress >= 1.0F) {
|
||||
progress = 1.0F;
|
||||
path.on_destination = true;
|
||||
}
|
||||
|
||||
// Aplicar función de easing
|
||||
double eased_progress = path.easing_function(progress);
|
||||
|
||||
// Calcular posición actual
|
||||
float current_pos = path.start_pos + ((path.end_pos - path.start_pos) * static_cast<float>(eased_progress));
|
||||
|
||||
// Establecer posición según el tipo
|
||||
SDL_FPoint position;
|
||||
if (path.type == PathType::HORIZONTAL) {
|
||||
position = {.x = current_pos, .y = path.fixed_pos};
|
||||
} else {
|
||||
position = {.x = path.fixed_pos, .y = current_pos};
|
||||
}
|
||||
setPosition(position);
|
||||
} else {
|
||||
// Esperar en destino
|
||||
path.waiting_elapsed += delta_time;
|
||||
if (path.waiting_elapsed >= path.waiting_time_s) {
|
||||
path.finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia de recorrido o finaliza
|
||||
void PathSprite::goToNextPathOrDie() {
|
||||
// Comprueba si ha terminado el recorrdo actual
|
||||
if (paths_.at(current_path_).finished) {
|
||||
++current_path_;
|
||||
}
|
||||
|
||||
// Comprueba si quedan mas recorridos
|
||||
if (std::cmp_greater_equal(current_path_, paths_.size())) {
|
||||
has_finished_ = true;
|
||||
current_path_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Indica si ha terminado todos los recorridos
|
||||
auto PathSprite::hasFinished() const -> bool { return has_finished_; }
|
||||
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FPoint
|
||||
|
||||
#include <functional> // Para std::function
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "sprite.hpp" // Para Sprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Enums ---
|
||||
enum class PathType { // Tipos de recorrido
|
||||
VERTICAL,
|
||||
HORIZONTAL,
|
||||
};
|
||||
|
||||
enum class PathCentered { // Centrado del recorrido
|
||||
ON_X,
|
||||
ON_Y,
|
||||
NONE,
|
||||
};
|
||||
|
||||
// --- Estructuras ---
|
||||
struct Path { // Define un recorrido para el sprite
|
||||
float start_pos; // Posición inicial
|
||||
float end_pos; // Posición final
|
||||
PathType type; // Tipo de movimiento (horizontal/vertical)
|
||||
float fixed_pos; // Posición fija en el eje contrario
|
||||
float duration_s; // Duración de la animación en segundos
|
||||
float waiting_time_s; // Tiempo de espera una vez en el destino
|
||||
std::function<double(double)> easing_function; // Función de easing
|
||||
float elapsed_time = 0.0F; // Tiempo transcurrido
|
||||
float waiting_elapsed = 0.0F; // Tiempo de espera transcurrido
|
||||
bool on_destination = false; // Indica si ha llegado al destino
|
||||
bool finished = false; // Indica si ha terminado de esperarse
|
||||
|
||||
// Constructor para paths generados
|
||||
Path(float start, float end, PathType path_type, float fixed, float duration, float waiting, std::function<double(double)> easing)
|
||||
: start_pos(start),
|
||||
end_pos(end),
|
||||
type(path_type),
|
||||
fixed_pos(fixed),
|
||||
duration_s(duration),
|
||||
waiting_time_s(waiting),
|
||||
easing_function(std::move(easing)) {}
|
||||
|
||||
// Constructor para paths por puntos (convertido a segundos)
|
||||
Path(const std::vector<SDL_FPoint>& spots_init, float waiting_time_s_init);
|
||||
|
||||
// Variables para paths por puntos
|
||||
std::vector<SDL_FPoint> spots; // Solo para paths por puntos
|
||||
int counter = 0; // Solo para paths por puntos
|
||||
bool is_point_path = false; // Indica si es un path por puntos
|
||||
};
|
||||
|
||||
// --- Funciones ---
|
||||
auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)>& easing_function) -> std::vector<SDL_FPoint>; // Devuelve un vector con los puntos que conforman la ruta
|
||||
|
||||
// --- Clase PathSprite: Sprite que sigue uno o varios recorridos ---
|
||||
class PathSprite : public Sprite {
|
||||
public:
|
||||
// --- Constructor y destructor ---
|
||||
explicit PathSprite(std::shared_ptr<Texture> texture)
|
||||
: Sprite(std::move(texture)) {}
|
||||
~PathSprite() override = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Actualiza la posición del sprite según el recorrido (delta_time en segundos)
|
||||
void render() override; // Muestra el sprite por pantalla
|
||||
|
||||
// --- Gestión de recorridos ---
|
||||
void addPath(Path path, bool centered = false); // Añade un recorrido (Path)
|
||||
void addPath(const std::vector<SDL_FPoint>& spots, float waiting_time_s = 0.0F); // Añade un recorrido a partir de puntos
|
||||
void addPath(float start, float end, PathType type, float fixed_pos, float duration_s, const std::function<double(double)>& easing_function, float waiting_time_s = 0.0F); // Añade un recorrido generado
|
||||
|
||||
// --- Estado y control ---
|
||||
void enable(); // Habilita el objeto
|
||||
[[nodiscard]] auto hasFinished() const -> bool; // Indica si ha terminado todos los recorridos
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getCurrentPath() const -> int { return current_path_; } // Devuelve el índice del recorrido actual
|
||||
|
||||
private:
|
||||
// --- Variables internas ---
|
||||
bool enabled_ = false; // Indica si el objeto está habilitado
|
||||
bool has_finished_ = false; // Indica si el objeto ha finalizado el recorrido
|
||||
int current_path_ = 0; // Recorrido que se está recorriendo actualmente
|
||||
std::vector<Path> paths_; // Caminos a recorrer por el sprite
|
||||
|
||||
// --- Métodos internos ---
|
||||
void moveThroughCurrentPath(float delta_time); // Coloca el sprite en los diferentes puntos del recorrido
|
||||
void goToNextPathOrDie(); // Cambia de recorrido o finaliza
|
||||
|
||||
// --- Métodos auxiliares para addPath ---
|
||||
[[nodiscard]] static auto determineCenteringType(const Path& path, bool centered) -> PathCentered; // Determina el tipo de centrado
|
||||
static void centerPathOnX(Path& path, float offset); // Aplica centrado en el eje X
|
||||
static void centerPathOnY(Path& path, float offset); // Aplica centrado en el eje Y
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "smart_sprite.hpp"
|
||||
|
||||
#include "moving_sprite.hpp" // Para MovingSprite
|
||||
|
||||
// Actualiza la posición y comprueba si ha llegado a su destino (time-based)
|
||||
void SmartSprite::update(float delta_time) {
|
||||
if (enabled_) {
|
||||
MovingSprite::update(delta_time);
|
||||
checkMove();
|
||||
checkFinished(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el sprite
|
||||
void SmartSprite::render() {
|
||||
if (enabled_) {
|
||||
MovingSprite::render();
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba el movimiento
|
||||
void SmartSprite::checkMove() {
|
||||
// Comprueba si se desplaza en el eje X hacia la derecha
|
||||
if (getAccelX() > 0 || getVelX() > 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosX() > dest_x_) {
|
||||
// Lo coloca en posición
|
||||
setPosX(dest_x_);
|
||||
|
||||
// Lo detiene
|
||||
setVelX(0.0F);
|
||||
setAccelX(0.0F);
|
||||
}
|
||||
}
|
||||
// Comprueba si se desplaza en el eje X hacia la izquierda
|
||||
else if (getAccelX() < 0 || getVelX() < 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosX() < dest_x_) {
|
||||
// Lo coloca en posición
|
||||
setPosX(dest_x_);
|
||||
|
||||
// Lo detiene
|
||||
setVelX(0.0F);
|
||||
setAccelX(0.0F);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si se desplaza en el eje Y hacia abajo
|
||||
if (getAccelY() > 0 || getVelY() > 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosY() > dest_y_) {
|
||||
// Lo coloca en posición
|
||||
setPosY(dest_y_);
|
||||
|
||||
// Lo detiene
|
||||
setVelY(0.0F);
|
||||
setAccelY(0.0F);
|
||||
}
|
||||
}
|
||||
// Comprueba si se desplaza en el eje Y hacia arriba
|
||||
else if (getAccelY() < 0 || getVelY() < 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosY() < dest_y_) {
|
||||
// Lo coloca en posición
|
||||
setPosY(dest_y_);
|
||||
|
||||
// Lo detiene
|
||||
setVelY(0.0F);
|
||||
setAccelY(0.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado (time-based)
|
||||
void SmartSprite::checkFinished(float delta_time) {
|
||||
// Comprueba si ha llegado a su destino
|
||||
on_destination_ = (getPosX() == dest_x_ && getPosY() == dest_y_);
|
||||
|
||||
if (on_destination_) {
|
||||
if (finished_delay_ms_ == 0.0F) {
|
||||
finished_ = true;
|
||||
} else {
|
||||
finished_timer_ += delta_time;
|
||||
if (finished_timer_ >= finished_delay_ms_) {
|
||||
finished_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <utility>
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Clase SmartSprite: sprite animado que se mueve hacia un destino y puede deshabilitarse automáticamente ---
|
||||
class SmartSprite : public AnimatedSprite {
|
||||
public:
|
||||
// --- Constructor y destructor ---
|
||||
explicit SmartSprite(std::shared_ptr<Texture> texture)
|
||||
: AnimatedSprite(std::move(texture)) {}
|
||||
~SmartSprite() override = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time) override; // Actualiza la posición y comprueba si ha llegado a su destino (time-based)
|
||||
void render() override; // Dibuja el sprite
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getDestX() const -> int { return dest_x_; } // Obtiene la posición de destino en X
|
||||
[[nodiscard]] auto getDestY() const -> int { return dest_y_; } // Obtiene la posición de destino en Y
|
||||
[[nodiscard]] auto isOnDestination() const -> bool { return on_destination_; } // Indica si está en el destino
|
||||
[[nodiscard]] auto hasFinished() const -> bool { return finished_; } // Indica si ya ha terminado
|
||||
|
||||
// --- Setters ---
|
||||
void setFinishedDelay(float value) { finished_delay_ms_ = value; } // Establece el retraso para deshabilitarlo (ms)
|
||||
void setDestX(int x) { dest_x_ = x; } // Establece la posición de destino en X
|
||||
void setDestY(int y) { dest_y_ = y; } // Establece la posición de destino en Y
|
||||
void setEnabled(bool value) { enabled_ = value; } // Habilita o deshabilita el objeto
|
||||
|
||||
private:
|
||||
// --- Variables de estado ---
|
||||
int dest_x_ = 0; // Posición de destino en el eje X
|
||||
int dest_y_ = 0; // Posición de destino en el eje Y
|
||||
float finished_delay_ms_ = 0.0F; // Retraso para deshabilitarlo (ms)
|
||||
float finished_timer_ = 0.0F; // Timer acumulado (ms)
|
||||
bool on_destination_ = false; // Indica si está en el destino
|
||||
bool finished_ = false; // Indica si ya ha terminado
|
||||
bool enabled_ = false; // Indica si el objeto está habilitado
|
||||
|
||||
// --- Métodos internos ---
|
||||
void checkFinished(float delta_time); // Comprueba si ha terminado (time-based)
|
||||
void checkMove(); // Comprueba el movimiento
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "sprite.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "texture.hpp" // Para Texture
|
||||
|
||||
// Constructor
|
||||
Sprite::Sprite(std::shared_ptr<Texture> texture, float pos_x, float pos_y, float width, float height)
|
||||
: textures_{std::move(texture)},
|
||||
pos_((SDL_FRect){.x = pos_x, .y = pos_y, .w = width, .h = height}),
|
||||
sprite_clip_((SDL_FRect){.x = 0, .y = 0, .w = pos_.w, .h = pos_.h}) {}
|
||||
|
||||
Sprite::Sprite(std::shared_ptr<Texture> texture, SDL_FRect rect)
|
||||
: textures_{std::move(texture)},
|
||||
pos_(rect),
|
||||
sprite_clip_((SDL_FRect){.x = 0, .y = 0, .w = pos_.w, .h = pos_.h}) {}
|
||||
|
||||
Sprite::Sprite(std::shared_ptr<Texture> texture)
|
||||
: textures_{std::move(texture)},
|
||||
|
||||
pos_(SDL_FRect{.x = 0, .y = 0, .w = static_cast<float>(textures_.at(texture_index_)->getWidth()), .h = static_cast<float>(textures_.at(texture_index_)->getHeight())}),
|
||||
sprite_clip_(pos_) {}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void Sprite::render() {
|
||||
textures_.at(texture_index_)->render(pos_.x, pos_.y, &sprite_clip_, zoom_, zoom_);
|
||||
}
|
||||
|
||||
// Establece la posición del objeto
|
||||
void Sprite::setPosition(float pos_x, float pos_y) {
|
||||
pos_.x = pos_x;
|
||||
pos_.y = pos_y;
|
||||
}
|
||||
|
||||
// Establece la posición del objeto
|
||||
void Sprite::setPosition(SDL_FPoint point) {
|
||||
pos_.x = point.x;
|
||||
pos_.y = point.y;
|
||||
}
|
||||
|
||||
// Reinicia las variables a cero
|
||||
void Sprite::clear() {
|
||||
pos_ = {.x = 0, .y = 0, .w = 0, .h = 0};
|
||||
sprite_clip_ = {.x = 0, .y = 0, .w = 0, .h = 0};
|
||||
}
|
||||
|
||||
// Cambia la textura activa por índice
|
||||
auto Sprite::setActiveTexture(size_t index) -> bool {
|
||||
if (index < textures_.size()) {
|
||||
texture_index_ = index;
|
||||
}
|
||||
return index < textures_.size();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_FPoint
|
||||
|
||||
#include <cstddef> // Para size_t
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Clase Sprite: representa un objeto gráfico básico con posición, tamaño y textura ---
|
||||
class Sprite {
|
||||
public:
|
||||
// --- Constructores y destructor ---
|
||||
Sprite(std::shared_ptr<Texture> texture, float pos_x, float pos_y, float width, float height);
|
||||
Sprite(std::shared_ptr<Texture> texture, SDL_FRect rect);
|
||||
explicit Sprite(std::shared_ptr<Texture> texture);
|
||||
virtual ~Sprite() = default;
|
||||
|
||||
// --- Renderizado y control ---
|
||||
virtual void render(); // Muestra el sprite por pantalla
|
||||
virtual void clear(); // Reinicia las variables a cero
|
||||
|
||||
// --- Getters de posición y tamaño ---
|
||||
[[nodiscard]] auto getX() const -> float { return pos_.x; }
|
||||
[[nodiscard]] auto getY() const -> float { return pos_.y; }
|
||||
[[nodiscard]] auto getWidth() const -> float { return pos_.w; }
|
||||
[[nodiscard]] auto getHeight() const -> float { return pos_.h; }
|
||||
[[nodiscard]] auto getPosition() const -> SDL_FRect { return pos_; }
|
||||
auto getRect() -> SDL_FRect& { return pos_; }
|
||||
|
||||
// --- Setters de posición y tamaño ---
|
||||
void setX(float pos_x) { pos_.x = pos_x; }
|
||||
void setY(float pos_y) { pos_.y = pos_y; }
|
||||
void setWidth(float width) { pos_.w = width; }
|
||||
void setHeight(float height) { pos_.h = height; }
|
||||
void setPosition(float pos_x, float pos_y);
|
||||
void setPosition(SDL_FPoint point);
|
||||
void setPosition(SDL_FRect rect) { pos_ = rect; }
|
||||
|
||||
// --- Zoom ---
|
||||
void setZoom(float zoom) { zoom_ = zoom; }
|
||||
|
||||
// --- Modificación de posición ---
|
||||
void incX(float value) { pos_.x += value; }
|
||||
void incY(float value) { pos_.y += value; }
|
||||
|
||||
// --- Sprite clip ---
|
||||
[[nodiscard]] auto getSpriteClip() const -> SDL_FRect { return sprite_clip_; }
|
||||
void setSpriteClip(SDL_FRect rect) { sprite_clip_ = rect; }
|
||||
void setSpriteClip(float pos_x, float pos_y, float width, float height) { sprite_clip_ = SDL_FRect{.x = pos_x, .y = pos_y, .w = width, .h = height}; }
|
||||
|
||||
// --- Textura ---
|
||||
[[nodiscard]] auto getTexture() const -> std::shared_ptr<Texture> { return textures_.at(texture_index_); }
|
||||
void setTexture(std::shared_ptr<Texture> texture) { textures_.at(texture_index_) = std::move(texture); }
|
||||
void addTexture(const std::shared_ptr<Texture>& texture) { textures_.push_back(texture); }
|
||||
auto setActiveTexture(size_t index) -> bool; // Cambia la textura activa por índice
|
||||
[[nodiscard]] auto getActiveTexture() const -> size_t { return texture_index_; } // Alias para getActiveTextureIndex
|
||||
[[nodiscard]] auto getTextureCount() const -> size_t { return textures_.size(); } // Obtiene el número total de texturas
|
||||
|
||||
protected:
|
||||
// --- Métodos internos ---
|
||||
auto getTextureRef() -> std::shared_ptr<Texture>& { return textures_.at(texture_index_); } // Obtiene referencia a la textura activa
|
||||
|
||||
// --- Variables internas ---
|
||||
std::vector<std::shared_ptr<Texture>> textures_; // Lista de texturas
|
||||
size_t texture_index_ = 0; // Índice de la textura activa
|
||||
SDL_FRect pos_; // Posición y tamaño donde dibujar el sprite
|
||||
SDL_FRect sprite_clip_; // Rectángulo de origen de la textura que se dibujará en pantalla
|
||||
double zoom_ = 1.0F; // Zoom aplicado a la textura
|
||||
};
|
||||
@@ -0,0 +1,453 @@
|
||||
#include "text.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, Uint8, SDL_GetRenderTarget, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_TextureAccess, SDL_GetTextureAlphaMod
|
||||
|
||||
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream, operator<<, istream, ifstream, istringstream
|
||||
#include <iostream> // Para cerr
|
||||
#include <sstream> // Para basic_istringstream
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <string_view> // Para string_view
|
||||
#include <utility> // Para std::cmp_less_equal
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
#include "resource_helper.hpp" // Para loadFile
|
||||
#include "screen.hpp" // Para Screen
|
||||
#include "sprite.hpp" // Para Sprite
|
||||
#include "texture.hpp" // Para Texture
|
||||
#include "utils.hpp" // Para getFileName
|
||||
|
||||
// Constructor
|
||||
Text::Text(const std::shared_ptr<Texture>& texture, const std::string& text_file) {
|
||||
// Carga los offsets desde el fichero
|
||||
auto tf = loadFile(text_file);
|
||||
|
||||
// Inicializa variables desde la estructura
|
||||
box_height_ = tf->box_height;
|
||||
box_width_ = tf->box_width;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset_[i].x = tf->offset[i].x;
|
||||
offset_[i].y = tf->offset[i].y;
|
||||
offset_[i].w = tf->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
|
||||
// Inicializa variables
|
||||
fixed_width_ = false;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Text::Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Text::File>& text_file) {
|
||||
// Inicializa variables desde la estructura
|
||||
box_height_ = text_file->box_height;
|
||||
box_width_ = text_file->box_width;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset_[i].x = text_file->offset[i].x;
|
||||
offset_[i].y = text_file->offset[i].y;
|
||||
offset_[i].w = text_file->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
|
||||
// Inicializa variables
|
||||
fixed_width_ = false;
|
||||
}
|
||||
|
||||
// Constructor con textura blanca opcional
|
||||
Text::Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Texture>& white_texture, const std::string& text_file) {
|
||||
// Carga los offsets desde el fichero
|
||||
auto tf = loadFile(text_file);
|
||||
|
||||
// Inicializa variables desde la estructura
|
||||
box_height_ = tf->box_height;
|
||||
box_width_ = tf->box_width;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset_[i].x = tf->offset[i].x;
|
||||
offset_[i].y = tf->offset[i].y;
|
||||
offset_[i].w = tf->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
white_sprite_ = std::make_unique<Sprite>(white_texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
|
||||
// Inicializa variables
|
||||
fixed_width_ = false;
|
||||
}
|
||||
|
||||
// Constructor con textura blanca opcional
|
||||
Text::Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Texture>& white_texture, const std::shared_ptr<Text::File>& text_file) {
|
||||
// Inicializa variables desde la estructura
|
||||
box_height_ = text_file->box_height;
|
||||
box_width_ = text_file->box_width;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset_[i].x = text_file->offset[i].x;
|
||||
offset_[i].y = text_file->offset[i].y;
|
||||
offset_[i].w = text_file->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
white_sprite_ = std::make_unique<Sprite>(white_texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
|
||||
// Inicializa variables
|
||||
fixed_width_ = false;
|
||||
}
|
||||
|
||||
// Escribe texto en pantalla
|
||||
void Text::write(int x, int y, const std::string& text, int kerning, int length) {
|
||||
int shift = 0;
|
||||
const std::string_view VISIBLE_TEXT = (length == -1) ? std::string_view(text) : std::string_view(text).substr(0, length);
|
||||
|
||||
sprite_->setY(y);
|
||||
for (const auto CH : VISIBLE_TEXT) {
|
||||
const auto INDEX = static_cast<unsigned char>(CH);
|
||||
|
||||
if (INDEX < offset_.size()) {
|
||||
sprite_->setSpriteClip(offset_[INDEX].x, offset_[INDEX].y, box_width_, box_height_);
|
||||
sprite_->setX(x + shift);
|
||||
sprite_->render();
|
||||
shift += offset_[INDEX].w + kerning;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe el texto al doble de tamaño
|
||||
void Text::write2X(int x, int y, const std::string& text, int kerning, int length) {
|
||||
int shift = 0;
|
||||
const std::string_view VISIBLE_TEXT = (length == -1) ? std::string_view(text) : std::string_view(text).substr(0, length);
|
||||
|
||||
for (const auto CH : VISIBLE_TEXT) {
|
||||
const auto INDEX = static_cast<unsigned char>(CH);
|
||||
|
||||
if (INDEX < offset_.size()) {
|
||||
SDL_FRect rect = {
|
||||
.x = static_cast<float>(offset_[INDEX].x),
|
||||
.y = static_cast<float>(offset_[INDEX].y),
|
||||
.w = static_cast<float>(box_width_),
|
||||
.h = static_cast<float>(box_height_)};
|
||||
|
||||
sprite_->getTexture()->render(x + shift, y, &rect, 2.0F, 2.0F);
|
||||
shift += (offset_[INDEX].w + kerning) * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe el texto en una textura
|
||||
auto Text::writeToTexture(const std::string& text, int zoom, int kerning, int length) -> std::shared_ptr<Texture> {
|
||||
auto* renderer = Screen::get()->getRenderer();
|
||||
auto texture = std::make_shared<Texture>(renderer);
|
||||
auto width = Text::length(text, kerning) * zoom;
|
||||
auto height = box_height_ * zoom;
|
||||
auto* temp = SDL_GetRenderTarget(renderer);
|
||||
|
||||
texture->createBlank(width, height, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
|
||||
texture->setBlendMode(SDL_BLENDMODE_BLEND);
|
||||
texture->setAsRenderTarget(renderer);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
zoom == 1 ? write(0, 0, text, kerning) : write2X(0, 0, text, kerning);
|
||||
SDL_SetRenderTarget(renderer, temp);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
// Escribe el texto con extras en una textura
|
||||
auto Text::writeDXToTexture(Uint8 flags, const std::string& text, int kerning, Color text_color, Uint8 shadow_distance, Color shadow_color, int length) -> std::shared_ptr<Texture> {
|
||||
auto* renderer = Screen::get()->getRenderer();
|
||||
auto texture = std::make_shared<Texture>(renderer);
|
||||
|
||||
// Calcula las dimensiones considerando los efectos
|
||||
auto base_width = Text::length(text, kerning);
|
||||
auto base_height = box_height_;
|
||||
auto width = base_width;
|
||||
auto height = base_height;
|
||||
auto offset_x = 0;
|
||||
auto offset_y = 0;
|
||||
|
||||
const auto STROKED = ((flags & Text::STROKE) == Text::STROKE);
|
||||
const auto SHADOWED = ((flags & Text::SHADOW) == Text::SHADOW);
|
||||
|
||||
if (STROKED) {
|
||||
// Para stroke, el texto se expande en todas las direcciones por shadow_distance
|
||||
width = base_width + (shadow_distance * 2);
|
||||
height = base_height + (shadow_distance * 2);
|
||||
offset_x = shadow_distance;
|
||||
offset_y = shadow_distance;
|
||||
} else if (SHADOWED) {
|
||||
// Para shadow, solo se añade espacio a la derecha y abajo
|
||||
width = base_width + shadow_distance;
|
||||
height = base_height + shadow_distance;
|
||||
}
|
||||
|
||||
auto* temp = SDL_GetRenderTarget(renderer);
|
||||
|
||||
texture->createBlank(width, height, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
|
||||
texture->setBlendMode(SDL_BLENDMODE_BLEND);
|
||||
texture->setAsRenderTarget(renderer);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
writeDX(flags, offset_x, offset_y, text, kerning, text_color, shadow_distance, shadow_color, length);
|
||||
SDL_SetRenderTarget(renderer, temp);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
// Escribe el texto con colores
|
||||
void Text::writeColored(int x, int y, const std::string& text, Color color, int kerning, int length) {
|
||||
writeColoredWithSprite(sprite_.get(), x, y, text, color, kerning, length);
|
||||
}
|
||||
|
||||
// Escribe el texto con colores usando un sprite específico
|
||||
void Text::writeColoredWithSprite(Sprite* sprite, int x, int y, const std::string& text, Color color, int kerning, int length) {
|
||||
int shift = 0;
|
||||
const std::string_view VISIBLE_TEXT = (length == -1) ? std::string_view(text) : std::string_view(text).substr(0, length);
|
||||
|
||||
auto* texture = sprite->getTexture().get();
|
||||
|
||||
// Guarda el alpha original y aplica el nuevo
|
||||
Uint8 original_alpha;
|
||||
SDL_GetTextureAlphaMod(texture->getSDLTexture(), &original_alpha);
|
||||
texture->setAlpha(color.a);
|
||||
texture->setColor(color.r, color.g, color.b);
|
||||
|
||||
sprite->setY(y);
|
||||
for (const auto CH : VISIBLE_TEXT) {
|
||||
const auto INDEX = static_cast<unsigned char>(CH);
|
||||
|
||||
if (INDEX < offset_.size()) {
|
||||
sprite->setSpriteClip(offset_[INDEX].x, offset_[INDEX].y, box_width_, box_height_);
|
||||
sprite->setX(x + shift);
|
||||
sprite->render();
|
||||
shift += offset_[INDEX].w + kerning;
|
||||
}
|
||||
}
|
||||
|
||||
// Restaura los valores originales
|
||||
texture->setColor(255, 255, 255);
|
||||
texture->setAlpha(255);
|
||||
}
|
||||
|
||||
// Escribe stroke con alpha correcto usando textura temporal
|
||||
void Text::writeStrokeWithAlpha(int x, int y, const std::string& text, int kerning, Color stroke_color, Uint8 shadow_distance, int length) {
|
||||
auto* renderer = Screen::get()->getRenderer();
|
||||
auto* original_target = SDL_GetRenderTarget(renderer);
|
||||
|
||||
// Calcula dimensiones de la textura temporal
|
||||
auto text_width = Text::length(text, kerning);
|
||||
auto text_height = box_height_;
|
||||
auto temp_width = text_width + (shadow_distance * 2);
|
||||
auto temp_height = text_height + (shadow_distance * 2);
|
||||
|
||||
// Crea textura temporal
|
||||
auto temp_texture = std::make_shared<Texture>(renderer);
|
||||
temp_texture->createBlank(temp_width, temp_height, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
|
||||
temp_texture->setBlendMode(SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Renderiza el stroke en la textura temporal
|
||||
temp_texture->setAsRenderTarget(renderer);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
// Selecciona el sprite apropiado para el stroke
|
||||
auto* stroke_sprite = white_sprite_ ? white_sprite_.get() : sprite_.get();
|
||||
|
||||
// Renderiza stroke sin alpha (sólido) en textura temporal
|
||||
Color solid_color = Color(stroke_color.r, stroke_color.g, stroke_color.b, 255);
|
||||
for (int dist = 1; std::cmp_less_equal(dist, shadow_distance); ++dist) {
|
||||
for (int dy = -dist; dy <= dist; ++dy) {
|
||||
for (int dx = -dist; dx <= dist; ++dx) {
|
||||
writeColoredWithSprite(stroke_sprite, shadow_distance + dx, shadow_distance + dy, text, solid_color, kerning, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restaura render target original
|
||||
SDL_SetRenderTarget(renderer, original_target);
|
||||
|
||||
// Renderiza la textura temporal con el alpha deseado
|
||||
temp_texture->setAlpha(stroke_color.a);
|
||||
temp_texture->render(x - shadow_distance, y - shadow_distance);
|
||||
}
|
||||
|
||||
// Escribe el texto con sombra
|
||||
void Text::writeShadowed(int x, int y, const std::string& text, Color color, Uint8 shadow_distance, int kerning, int length) {
|
||||
writeDX(Text::SHADOW, x, y, text, kerning, color, shadow_distance, color, length);
|
||||
write(x, y, text, kerning, length); // Dibuja el texto principal encima
|
||||
}
|
||||
|
||||
// Escribe el texto centrado en un punto x
|
||||
void Text::writeCentered(int x, int y, const std::string& text, int kerning, int length) {
|
||||
x -= (Text::length(text, kerning) / 2);
|
||||
write(x, y, text, kerning, length);
|
||||
}
|
||||
|
||||
// Renderiza sombra del texto
|
||||
void Text::renderShadow(int x, int y, const std::string& text, Color shadow_color, int kerning, int length, Uint8 shadow_distance) {
|
||||
if (white_sprite_) {
|
||||
writeColoredWithSprite(white_sprite_.get(), x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, length);
|
||||
} else {
|
||||
writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, length);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza stroke sólido (método tradicional para stroke sin alpha)
|
||||
void Text::renderSolidStroke(int x, int y, const std::string& text, Color stroke_color, int kerning, int length, Uint8 shadow_distance) {
|
||||
for (int dist = 1; std::cmp_less_equal(dist, shadow_distance); ++dist) {
|
||||
for (int dy = -dist; dy <= dist; ++dy) {
|
||||
for (int dx = -dist; dx <= dist; ++dx) {
|
||||
if (white_sprite_) {
|
||||
writeColoredWithSprite(white_sprite_.get(), x + dx, y + dy, text, stroke_color, kerning, length);
|
||||
} else {
|
||||
writeColored(x + dx, y + dy, text, stroke_color, kerning, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe texto con extras
|
||||
void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Color text_color, Uint8 shadow_distance, Color shadow_color, int length) {
|
||||
const auto CENTERED = ((flags & Text::CENTER) == Text::CENTER);
|
||||
const auto SHADOWED = ((flags & Text::SHADOW) == Text::SHADOW);
|
||||
const auto COLORED = ((flags & Text::COLOR) == Text::COLOR);
|
||||
const auto STROKED = ((flags & Text::STROKE) == Text::STROKE);
|
||||
|
||||
if (CENTERED) {
|
||||
x -= (Text::length(text, kerning) / 2);
|
||||
}
|
||||
|
||||
if (SHADOWED) {
|
||||
renderShadow(x, y, text, shadow_color, kerning, length, shadow_distance);
|
||||
}
|
||||
|
||||
if (STROKED) {
|
||||
if (shadow_color.a < 255) {
|
||||
// Usa textura temporal para alpha correcto
|
||||
writeStrokeWithAlpha(x, y, text, kerning, shadow_color, shadow_distance, length);
|
||||
} else {
|
||||
// Método tradicional para stroke sólido
|
||||
renderSolidStroke(x, y, text, shadow_color, kerning, length, shadow_distance);
|
||||
}
|
||||
}
|
||||
|
||||
if (COLORED) {
|
||||
writeColored(x, y, text, text_color, kerning, length);
|
||||
} else {
|
||||
write(x, y, text, kerning, length);
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe texto a partir de un TextStyle
|
||||
void Text::writeStyle(int x, int y, const std::string& text, const Style& style, int length) {
|
||||
writeDX(style.flags, x, y, text, style.kerning, style.text_color, style.shadow_distance, style.shadow_color);
|
||||
}
|
||||
|
||||
// Obtiene la longitud en pixels de una cadena
|
||||
auto Text::length(const std::string& text, int kerning) const -> int {
|
||||
int shift = 0;
|
||||
for (const auto& ch : text) {
|
||||
// Convertimos a unsigned char para obtener el valor ASCII correcto (0-255)
|
||||
const auto INDEX = static_cast<unsigned char>(ch);
|
||||
|
||||
// Verificamos si el carácter está dentro de los límites del array
|
||||
if (INDEX < offset_.size()) {
|
||||
shift += (offset_[INDEX].w + kerning);
|
||||
}
|
||||
}
|
||||
|
||||
// Descuenta el kerning del último caracter si el texto no está vacío
|
||||
return text.empty() ? 0 : shift - kerning;
|
||||
}
|
||||
|
||||
// Devuelve el valor de la variable
|
||||
auto Text::getCharacterSize() const -> int {
|
||||
return box_width_;
|
||||
}
|
||||
|
||||
// Establece si se usa un tamaño fijo de letra
|
||||
void Text::setFixedWidth(bool value) {
|
||||
fixed_width_ = value;
|
||||
}
|
||||
|
||||
// Llena una estructuta TextFile desde un fichero
|
||||
auto Text::loadFile(const std::string& file_path) -> std::shared_ptr<Text::File> {
|
||||
auto tf = std::make_shared<Text::File>();
|
||||
|
||||
// Inicializa a cero el vector con las coordenadas
|
||||
for (auto& i : tf->offset) {
|
||||
i.x = 0;
|
||||
i.y = 0;
|
||||
i.w = 0;
|
||||
tf->box_width = 0;
|
||||
tf->box_height = 0;
|
||||
}
|
||||
|
||||
// Intenta cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
std::istringstream stream;
|
||||
bool using_resource_data = false;
|
||||
|
||||
if (!resource_data.empty()) {
|
||||
std::string content(resource_data.begin(), resource_data.end());
|
||||
stream.str(content);
|
||||
using_resource_data = true;
|
||||
}
|
||||
|
||||
// Fallback a archivo directo
|
||||
std::ifstream file;
|
||||
if (!using_resource_data) {
|
||||
file.open(file_path);
|
||||
}
|
||||
|
||||
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
||||
|
||||
if ((using_resource_data && stream.good()) || (!using_resource_data && file.is_open() && file.good())) {
|
||||
std::string buffer;
|
||||
|
||||
// Lee los dos primeros valores del fichero
|
||||
std::getline(input_stream, buffer);
|
||||
std::getline(input_stream, buffer);
|
||||
tf->box_width = std::stoi(buffer);
|
||||
|
||||
std::getline(input_stream, buffer);
|
||||
std::getline(input_stream, buffer);
|
||||
tf->box_height = std::stoi(buffer);
|
||||
|
||||
// lee el resto de datos del fichero
|
||||
auto index = 32;
|
||||
auto line_read = 0;
|
||||
while (std::getline(input_stream, buffer)) {
|
||||
// Almacena solo las lineas impares
|
||||
if (line_read % 2 == 1) {
|
||||
tf->offset[index++].w = std::stoi(buffer);
|
||||
}
|
||||
|
||||
// Limpia el buffer
|
||||
buffer.clear();
|
||||
line_read++;
|
||||
};
|
||||
|
||||
// Cierra el fichero si se usó
|
||||
if (!using_resource_data && file.is_open()) {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
// El fichero no se puede abrir
|
||||
else {
|
||||
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
|
||||
}
|
||||
|
||||
// Establece las coordenadas para cada caracter ascii de la cadena y su ancho
|
||||
for (int i = 32; i < 128; ++i) {
|
||||
tf->offset[i].x = ((i - 32) % 15) * tf->box_width;
|
||||
tf->offset[i].y = ((i - 32) / 15) * tf->box_height;
|
||||
}
|
||||
|
||||
return tf;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para Uint8
|
||||
|
||||
#include <array> // Para array
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
#include "sprite.hpp" // Para Sprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Clase Text: pinta texto en pantalla a partir de un bitmap ---
|
||||
class Text {
|
||||
public:
|
||||
// --- Constantes para flags de texto ---
|
||||
static constexpr int COLOR = 1;
|
||||
static constexpr int SHADOW = 2;
|
||||
static constexpr int CENTER = 4;
|
||||
static constexpr int STROKE = 8;
|
||||
|
||||
// --- Estructuras ---
|
||||
struct Offset {
|
||||
int x, y, w;
|
||||
};
|
||||
|
||||
struct File {
|
||||
int box_width; // Anchura de la caja de cada caracter en el png
|
||||
int box_height; // Altura de la caja de cada caracter en el png
|
||||
std::array<Offset, 128> offset = {}; // Vector con las posiciones y ancho de cada letra
|
||||
};
|
||||
|
||||
struct Style {
|
||||
Uint8 flags;
|
||||
Color text_color;
|
||||
Color shadow_color;
|
||||
Uint8 shadow_distance;
|
||||
int kerning;
|
||||
|
||||
// Constructor con argumentos por defecto
|
||||
Style(Uint8 flags = 0,
|
||||
Color text = Color(),
|
||||
Color shadow = Color(),
|
||||
Uint8 distance = 1,
|
||||
int kern = 1)
|
||||
: flags(flags),
|
||||
text_color(text),
|
||||
shadow_color(shadow),
|
||||
shadow_distance(distance),
|
||||
kerning(kern) {}
|
||||
};
|
||||
|
||||
// --- Constructores y destructor ---
|
||||
Text(const std::shared_ptr<Texture>& texture, const std::string& text_file);
|
||||
Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Text::File>& text_file);
|
||||
Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Texture>& white_texture, const std::string& text_file);
|
||||
Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Texture>& white_texture, const std::shared_ptr<Text::File>& text_file);
|
||||
~Text() = default;
|
||||
|
||||
// --- Métodos de escritura en pantalla ---
|
||||
void write(int x, int y, const std::string& text, int kerning = 1, int length = -1); // Escribe el texto en pantalla
|
||||
void write2X(int x, int y, const std::string& text, int kerning = 1, int length = -1); // Escribe el texto al doble de tamaño
|
||||
|
||||
// --- Escritura en textura ---
|
||||
auto writeToTexture(const std::string& text, int zoom = 1, int kerning = 1, int length = -1) -> std::shared_ptr<Texture>; // Escribe el texto en una textura
|
||||
auto writeDXToTexture(Uint8 flags, const std::string& text, int kerning = 1, Color text_color = Color(), Uint8 shadow_distance = 1, Color shadow_color = Color(), int length = -1) -> std::shared_ptr<Texture>; // Escribe el texto con extras en una textura
|
||||
|
||||
// --- Métodos de escritura avanzada ---
|
||||
void writeColored(int x, int y, const std::string& text, Color color, int kerning = 1, int length = -1); // Escribe el texto con colores
|
||||
void writeShadowed(int x, int y, const std::string& text, Color color, Uint8 shadow_distance = 1, int kerning = 1, int length = -1); // Escribe el texto con sombra
|
||||
void writeCentered(int x, int y, const std::string& text, int kerning = 1, int length = -1); // Escribe el texto centrado en un punto x
|
||||
void writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning = 1, Color text_color = Color(), Uint8 shadow_distance = 1, Color shadow_color = Color(), int length = -1); // Escribe texto con extras
|
||||
void writeStyle(int x, int y, const std::string& text, const Style& style, int length = -1); // Escribe texto a partir de un TextStyle
|
||||
|
||||
// --- Utilidades ---
|
||||
[[nodiscard]] auto length(const std::string& text, int kerning = 1) const -> int; // Obtiene la longitud en pixels de una cadena
|
||||
[[nodiscard]] auto getCharacterSize() const -> int; // Devuelve el tamaño de caracter actual
|
||||
|
||||
// --- Configuración ---
|
||||
void setFixedWidth(bool value); // Establece si se usa un tamaño fijo de letra
|
||||
|
||||
// --- Métodos estáticos ---
|
||||
static auto loadFile(const std::string& file_path) -> std::shared_ptr<Text::File>; // Llena una estructura Text::File desde un fichero
|
||||
|
||||
// --- Métodos privados ---
|
||||
void writeColoredWithSprite(Sprite* sprite, int x, int y, const std::string& text, Color color, int kerning = 1, int length = -1); // Escribe con un sprite específico
|
||||
void writeStrokeWithAlpha(int x, int y, const std::string& text, int kerning, Color stroke_color, Uint8 shadow_distance, int length = -1); // Escribe stroke con alpha correcto
|
||||
void renderShadow(int x, int y, const std::string& text, Color shadow_color, int kerning, int length, Uint8 shadow_distance); // Renderiza sombra del texto
|
||||
void renderSolidStroke(int x, int y, const std::string& text, Color stroke_color, int kerning, int length, Uint8 shadow_distance); // Renderiza stroke sólido
|
||||
|
||||
private:
|
||||
// --- Objetos y punteros ---
|
||||
std::unique_ptr<Sprite> sprite_ = nullptr; // Objeto con los gráficos para el texto
|
||||
std::unique_ptr<Sprite> white_sprite_ = nullptr; // Objeto con los gráficos en blanco para efectos
|
||||
|
||||
// --- Variables de estado ---
|
||||
std::array<Offset, 128> offset_ = {}; // Vector con las posiciones y ancho de cada letra
|
||||
int box_width_ = 0; // Anchura de la caja de cada caracter en el png
|
||||
int box_height_ = 0; // Altura de la caja de cada caracter en el png
|
||||
bool fixed_width_ = false; // Indica si el texto se ha de escribir con longitud fija
|
||||
};
|
||||
@@ -0,0 +1,430 @@
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "texture.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_LogError, SDL_LogCategory, Uint8, SDL_...
|
||||
|
||||
#include <cstdint> // Para uint32_t
|
||||
#include <cstring> // Para memcpy
|
||||
#include <fstream> // Para basic_ifstream, basic_istream, basic_ios
|
||||
#include <iostream> // Para std::cout
|
||||
#include <sstream> // Para basic_istringstream
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <string> // Para basic_string, char_traits, operator+, string
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "color.hpp" // Para getFileName, Color
|
||||
#include "external/gif.hpp" // Para Gif
|
||||
#include "resource_helper.hpp" // Para ResourceHelper
|
||||
#include "stb_image.h" // Para stbi_image_free, stbi_load, STBI_rgb_alpha
|
||||
#include "utils.hpp"
|
||||
|
||||
// Constructor
|
||||
Texture::Texture(SDL_Renderer* renderer, std::string path)
|
||||
: renderer_(renderer),
|
||||
path_(std::move(path)) {
|
||||
// Carga el fichero en la textura
|
||||
if (!path_.empty()) {
|
||||
// Obtiene la extensión
|
||||
const std::string EXTENSION = path_.substr(path_.find_last_of('.') + 1);
|
||||
|
||||
// .png
|
||||
if (EXTENSION == "png") {
|
||||
loadFromFile(path_);
|
||||
}
|
||||
|
||||
// .gif
|
||||
else if (EXTENSION == "gif") {
|
||||
// Crea la surface desde un fichero
|
||||
surface_ = loadSurface(path_);
|
||||
|
||||
// Añade la propia paleta del fichero a la lista
|
||||
addPaletteFromGifFile(path_);
|
||||
|
||||
// Crea la textura, establece el BlendMode y copia la surface a la textura
|
||||
createBlank(width_, height_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING);
|
||||
SDL_SetTextureBlendMode(texture_, SDL_BLENDMODE_BLEND);
|
||||
flipSurface();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Texture::~Texture() {
|
||||
unloadTexture();
|
||||
unloadSurface();
|
||||
palettes_.clear();
|
||||
}
|
||||
|
||||
// Carga una imagen desde un fichero
|
||||
auto Texture::loadFromFile(const std::string& file_path) -> bool {
|
||||
if (file_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int req_format = STBI_rgb_alpha;
|
||||
int width;
|
||||
int height;
|
||||
int orig_format;
|
||||
unsigned char* data = nullptr;
|
||||
|
||||
// Intentar cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
if (!resource_data.empty()) {
|
||||
data = stbi_load_from_memory(resource_data.data(), resource_data.size(), &width, &height, &orig_format, req_format);
|
||||
}
|
||||
|
||||
// Fallback a filesystem directo
|
||||
if (data == nullptr) {
|
||||
data = stbi_load(file_path.c_str(), &width, &height, &orig_format, req_format);
|
||||
}
|
||||
|
||||
if (data == nullptr) {
|
||||
std::cout << "Error: Fichero no encontrado " << getFileName(file_path) << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
|
||||
}
|
||||
|
||||
int pitch;
|
||||
SDL_PixelFormat pixel_format;
|
||||
pitch = 4 * width;
|
||||
pixel_format = SDL_PIXELFORMAT_RGBA32;
|
||||
|
||||
// Limpia
|
||||
unloadTexture();
|
||||
|
||||
// La textura final
|
||||
SDL_Texture* new_texture = nullptr;
|
||||
|
||||
// Carga la imagen desde una ruta específica
|
||||
auto* loaded_surface = SDL_CreateSurfaceFrom(width, height, pixel_format, static_cast<void*>(data), pitch);
|
||||
if (loaded_surface == nullptr) {
|
||||
std::cout << "Unable to load image " << file_path << '\n';
|
||||
} else {
|
||||
// Crea la textura desde los pixels de la surface
|
||||
new_texture = SDL_CreateTextureFromSurface(renderer_, loaded_surface);
|
||||
if (new_texture == nullptr) {
|
||||
std::cout << "Unable to create texture from " << file_path << "! SDL Error: " << SDL_GetError() << '\n';
|
||||
} else {
|
||||
// Obtiene las dimensiones de la imagen
|
||||
width_ = loaded_surface->w;
|
||||
height_ = loaded_surface->h;
|
||||
}
|
||||
|
||||
// Elimina la textura cargada
|
||||
SDL_DestroySurface(loaded_surface);
|
||||
}
|
||||
|
||||
// Return success
|
||||
stbi_image_free(data);
|
||||
texture_ = new_texture;
|
||||
return texture_ != nullptr;
|
||||
}
|
||||
|
||||
// Crea una textura en blanco
|
||||
auto Texture::createBlank(int width, int height, SDL_PixelFormat format, SDL_TextureAccess access) -> bool {
|
||||
// Crea una textura sin inicializar
|
||||
texture_ = SDL_CreateTexture(renderer_, format, access, width, height);
|
||||
if (texture_ == nullptr) {
|
||||
std::cout << "Unable to create blank texture! SDL Error: " << SDL_GetError() << '\n';
|
||||
} else {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
}
|
||||
|
||||
return texture_ != nullptr;
|
||||
}
|
||||
|
||||
// Libera la memoria de la textura
|
||||
void Texture::unloadTexture() {
|
||||
// Libera la textura
|
||||
if (texture_ != nullptr) {
|
||||
SDL_DestroyTexture(texture_);
|
||||
texture_ = nullptr;
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el color para la modulacion
|
||||
void Texture::setColor(Uint8 red, Uint8 green, Uint8 blue) {
|
||||
SDL_SetTextureColorMod(texture_, red, green, blue);
|
||||
}
|
||||
void Texture::setColor(Color color) {
|
||||
SDL_SetTextureColorMod(texture_, color.r, color.g, color.b);
|
||||
}
|
||||
|
||||
// Establece el blending
|
||||
void Texture::setBlendMode(SDL_BlendMode blending) {
|
||||
SDL_SetTextureBlendMode(texture_, blending);
|
||||
}
|
||||
|
||||
// Establece el alpha para la modulación
|
||||
void Texture::setAlpha(Uint8 alpha) {
|
||||
SDL_SetTextureAlphaMod(texture_, alpha);
|
||||
}
|
||||
|
||||
// Renderiza la textura en un punto específico
|
||||
void Texture::render(int x, int y, SDL_FRect* clip, float horizontal_zoom, float vertical_zoom, double angle, SDL_FPoint* center, SDL_FlipMode flip) {
|
||||
// Establece el destino de renderizado en la pantalla
|
||||
SDL_FRect render_quad = {.x = static_cast<float>(x), .y = static_cast<float>(y), .w = static_cast<float>(width_), .h = static_cast<float>(height_)};
|
||||
|
||||
// Obtiene las dimesiones del clip de renderizado
|
||||
if (clip != nullptr) {
|
||||
render_quad.w = clip->w;
|
||||
render_quad.h = clip->h;
|
||||
}
|
||||
|
||||
// Calcula el zoom y las coordenadas
|
||||
if (vertical_zoom != 1.0F || horizontal_zoom != 1.0F) {
|
||||
render_quad.x = render_quad.x + (render_quad.w / 2);
|
||||
render_quad.y = render_quad.y + (render_quad.h / 2);
|
||||
render_quad.w = render_quad.w * horizontal_zoom;
|
||||
render_quad.h = render_quad.h * vertical_zoom;
|
||||
render_quad.x = render_quad.x - (render_quad.w / 2);
|
||||
render_quad.y = render_quad.y - (render_quad.h / 2);
|
||||
}
|
||||
|
||||
// Renderiza a pantalla
|
||||
SDL_RenderTextureRotated(renderer_, texture_, clip, &render_quad, angle, center, flip);
|
||||
}
|
||||
|
||||
// Establece la textura como objetivo de renderizado
|
||||
void Texture::setAsRenderTarget(SDL_Renderer* renderer) {
|
||||
SDL_SetRenderTarget(renderer, texture_);
|
||||
}
|
||||
|
||||
// Obtiene el ancho de la imagen
|
||||
auto Texture::getWidth() const -> int {
|
||||
return width_;
|
||||
}
|
||||
|
||||
// Obtiene el alto de la imagen
|
||||
auto Texture::getHeight() const -> int {
|
||||
return height_;
|
||||
}
|
||||
|
||||
// Recarga la textura
|
||||
auto Texture::reLoad() -> bool {
|
||||
return loadFromFile(path_);
|
||||
}
|
||||
|
||||
// Obtiene la textura
|
||||
auto Texture::getSDLTexture() -> SDL_Texture* {
|
||||
return texture_;
|
||||
}
|
||||
|
||||
// Desencadenar la superficie actual
|
||||
void Texture::unloadSurface() {
|
||||
surface_.reset(); // Resetea el shared_ptr
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
}
|
||||
|
||||
// Crea una surface desde un fichero .gif
|
||||
auto Texture::loadSurface(const std::string& file_path) -> std::shared_ptr<Surface> {
|
||||
// Libera la superficie actual
|
||||
unloadSurface();
|
||||
|
||||
std::vector<Uint8> buffer;
|
||||
|
||||
// Intentar cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
if (!resource_data.empty()) {
|
||||
buffer = resource_data;
|
||||
} else {
|
||||
// Fallback a filesystem directo
|
||||
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
std::cout << "Error: Fichero no encontrado " << file_path << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + file_path);
|
||||
}
|
||||
|
||||
// Obtener el tamaño del archivo
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
// Leer el contenido del archivo en un buffer
|
||||
buffer.resize(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
std::cout << "Error al leer el fichero " << file_path << '\n';
|
||||
throw std::runtime_error("Error al leer el fichero: " + file_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Crear un objeto Gif y llamar a la función loadGif
|
||||
GIF::Gif gif;
|
||||
Uint16 w = 0;
|
||||
Uint16 h = 0;
|
||||
std::vector<Uint8> raw_pixels = gif.loadGif(buffer.data(), w, h);
|
||||
if (raw_pixels.empty()) {
|
||||
std::cout << "Error: No se pudo cargar el GIF " << file_path << '\n';
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Si el constructor de Surface espera un std::shared_ptr<Uint8[]>:
|
||||
size_t pixel_count = raw_pixels.size();
|
||||
auto pixels = std::shared_ptr<Uint8[]>(new Uint8[pixel_count], std::default_delete<Uint8[]>()); // NOLINT(modernize-avoid-c-arrays)
|
||||
std::memcpy(pixels.get(), raw_pixels.data(), pixel_count);
|
||||
|
||||
auto surface = std::make_shared<Surface>(w, h, pixels);
|
||||
|
||||
// Actualizar las dimensiones
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
// Vuelca la surface en la textura
|
||||
void Texture::flipSurface() {
|
||||
// Limpia la textura
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, texture_);
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
|
||||
// Vuelca los datos
|
||||
Uint32* pixels;
|
||||
int pitch;
|
||||
SDL_LockTexture(texture_, nullptr, reinterpret_cast<void**>(&pixels), &pitch);
|
||||
for (int i = 0; i < width_ * height_; ++i) {
|
||||
pixels[i] = palettes_[current_palette_][surface_->data[i]];
|
||||
}
|
||||
SDL_UnlockTexture(texture_);
|
||||
}
|
||||
|
||||
// Establece un color de la paleta
|
||||
void Texture::setPaletteColor(int palette, int index, Uint32 color) {
|
||||
palettes_.at(palette)[index] = color;
|
||||
}
|
||||
|
||||
// Carga una paleta desde un fichero
|
||||
auto Texture::loadPaletteFromFile(const std::string& file_path) -> Palette {
|
||||
Palette palette;
|
||||
|
||||
std::vector<Uint8> buffer;
|
||||
|
||||
// Intentar cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
if (!resource_data.empty()) {
|
||||
buffer = resource_data;
|
||||
} else {
|
||||
// Fallback a filesystem directo
|
||||
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
std::cout << "Error: Fichero no encontrado " << file_path << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + file_path);
|
||||
}
|
||||
|
||||
// Obtener el tamaño del archivo y leerlo en un buffer
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
buffer.resize(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
std::cout << "Error: No se pudo leer completamente el fichero " << file_path << '\n';
|
||||
throw std::runtime_error("Error al leer el fichero: " + file_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Usar la nueva función loadPalette, que devuelve un vector<uint32_t>
|
||||
GIF::Gif gif;
|
||||
std::vector<uint32_t> pal = gif.loadPalette(buffer.data());
|
||||
if (pal.empty()) {
|
||||
std::cout << "Advertencia: No se encontró paleta en el archivo " << file_path << '\n';
|
||||
return palette; // Devuelve un vector vacío si no hay paleta
|
||||
}
|
||||
|
||||
// Modificar la conversión para obtener formato RGBA (0xRRGGBBAA)
|
||||
for (size_t i = 0; i < pal.size() && i < palette.size(); ++i) {
|
||||
palette[i] = (pal[i] << 8) | 0xFF; // Resultado: 0xRRGGBBAA
|
||||
}
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
// Añade una paleta a la lista
|
||||
void Texture::addPaletteFromGifFile(const std::string& path) {
|
||||
palettes_.emplace_back(loadPaletteFromFile(path));
|
||||
setPaletteColor(palettes_.size() - 1, 0, 0x00000000);
|
||||
}
|
||||
|
||||
// Añade una paleta a la lista
|
||||
void Texture::addPaletteFromPalFile(const std::string& path) {
|
||||
palettes_.emplace_back(readPalFile(path));
|
||||
setPaletteColor(palettes_.size() - 1, 0, 0x00000000);
|
||||
}
|
||||
|
||||
// Cambia la paleta de la textura
|
||||
void Texture::setPalette(size_t palette) {
|
||||
if (palette < palettes_.size()) {
|
||||
current_palette_ = palette;
|
||||
flipSurface();
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el renderizador
|
||||
auto Texture::getRenderer() -> SDL_Renderer* { return renderer_; }
|
||||
|
||||
// Carga una paleta desde un archivo .pal
|
||||
auto Texture::readPalFile(const std::string& file_path) -> Palette {
|
||||
Palette palette{};
|
||||
palette.fill(0); // Inicializar todo con 0 (transparente por defecto)
|
||||
|
||||
// Intentar cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
std::istringstream stream;
|
||||
bool using_resource_data = false;
|
||||
|
||||
if (!resource_data.empty()) {
|
||||
std::string content(resource_data.begin(), resource_data.end());
|
||||
stream.str(content);
|
||||
using_resource_data = true;
|
||||
}
|
||||
|
||||
// Fallback a archivo directo
|
||||
std::ifstream file;
|
||||
if (!using_resource_data) {
|
||||
file.open(file_path);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("No se pudo abrir el archivo .pal");
|
||||
}
|
||||
}
|
||||
|
||||
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
||||
|
||||
std::string line;
|
||||
int line_number = 0;
|
||||
int color_index = 0;
|
||||
|
||||
while (std::getline(input_stream, line)) {
|
||||
++line_number;
|
||||
|
||||
// Ignorar las tres primeras líneas del archivo
|
||||
if (line_number <= 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Procesar las líneas restantes con valores RGB
|
||||
std::istringstream ss(line);
|
||||
int r;
|
||||
int g;
|
||||
int b;
|
||||
if (ss >> r >> g >> b) {
|
||||
// Construir el color RGBA (A = 255 por defecto)
|
||||
Uint32 color = (r << 24) | (g << 16) | (b << 8) | 255;
|
||||
palette[color_index++] = color;
|
||||
|
||||
// Limitar a un máximo de 256 colores (opcional)
|
||||
if (color_index >= 256) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!using_resource_data && file.is_open()) {
|
||||
file.close();
|
||||
}
|
||||
return palette;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para Uint8, SDL_Renderer, Uint16, SDL_FlipMode, SDL_PixelFormat, SDL_TextureAccess, SDL_Texture, Uint32, SDL_BlendMode, SDL_FPoint, SDL_FRect
|
||||
|
||||
#include <array> // Para array
|
||||
#include <cstddef> // Para size_t
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string, basic_string
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
struct Color;
|
||||
|
||||
// Alias
|
||||
using Palette = std::array<Uint32, 256>;
|
||||
|
||||
// Definición de Surface para imágenes con paleta
|
||||
struct Surface {
|
||||
std::shared_ptr<Uint8[]> data; // NOLINT(modernize-avoid-c-arrays)
|
||||
Uint16 w, h;
|
||||
|
||||
// Constructor
|
||||
Surface(Uint16 width, Uint16 height, std::shared_ptr<Uint8[]> pixels) // NOLINT(modernize-avoid-c-arrays)
|
||||
: data(std::move(pixels)),
|
||||
w(width),
|
||||
h(height) {}
|
||||
};
|
||||
|
||||
// Clase Texture: gestiona texturas, paletas y renderizado
|
||||
class Texture {
|
||||
public:
|
||||
// --- Constructores y destructor ---
|
||||
explicit Texture(SDL_Renderer* renderer, std::string path = std::string());
|
||||
~Texture();
|
||||
|
||||
// --- Carga y creación ---
|
||||
auto loadFromFile(const std::string& path) -> bool; // Carga una imagen desde un fichero
|
||||
auto createBlank(int width, int height, SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA8888, SDL_TextureAccess access = SDL_TEXTUREACCESS_STREAMING) -> bool; // Crea una textura en blanco
|
||||
auto reLoad() -> bool; // Recarga la textura
|
||||
|
||||
// --- Renderizado ---
|
||||
void render(int x, int y, SDL_FRect* clip = nullptr, float horizontal_zoom = 1, float vertical_zoom = 1, double angle = 0.0, SDL_FPoint* center = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE); // Renderiza la textura en un punto específico
|
||||
void setAsRenderTarget(SDL_Renderer* renderer); // Establece la textura como objetivo de renderizado
|
||||
|
||||
// --- Modificadores de color y blending ---
|
||||
void setColor(Uint8 red, Uint8 green, Uint8 blue); // Establece el color para la modulación
|
||||
void setColor(Color color); // Establece el color para la modulación
|
||||
void setBlendMode(SDL_BlendMode blending); // Establece el blending
|
||||
void setAlpha(Uint8 alpha); // Establece el alpha para la modulación
|
||||
|
||||
// --- Paletas ---
|
||||
void addPaletteFromGifFile(const std::string& path); // Añade una paleta a la lista
|
||||
void addPaletteFromPalFile(const std::string& path); // Añade una paleta a la lista
|
||||
void setPaletteColor(int palette, int index, Uint32 color); // Establece un color de la paleta
|
||||
void setPalette(size_t palette); // Cambia la paleta de la textura
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getWidth() const -> int; // Obtiene el ancho de la imagen
|
||||
[[nodiscard]] auto getHeight() const -> int; // Obtiene el alto de la imagen
|
||||
auto getSDLTexture() -> SDL_Texture*; // Obtiene la textura SDL
|
||||
auto getRenderer() -> SDL_Renderer*; // Obtiene el renderizador
|
||||
|
||||
private:
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Renderer* renderer_; // Renderizador donde dibujar la textura
|
||||
SDL_Texture* texture_ = nullptr; // La textura
|
||||
std::shared_ptr<Surface> surface_ = nullptr; // Surface para usar imágenes en formato gif con paleta
|
||||
|
||||
// --- Variables ---
|
||||
std::string path_; // Ruta de la imagen de la textura
|
||||
int width_ = 0; // Ancho de la imagen
|
||||
int height_ = 0; // Alto de la imagen
|
||||
std::vector<Palette> palettes_; // Vector con las diferentes paletas
|
||||
int current_palette_ = 0; // Índice de la paleta en uso
|
||||
|
||||
// --- Métodos internos ---
|
||||
auto loadSurface(const std::string& file_path) -> std::shared_ptr<Surface>; // Crea una surface desde un fichero .gif
|
||||
void flipSurface(); // Vuelca la surface en la textura
|
||||
static auto loadPaletteFromFile(const std::string& file_path) -> Palette; // Carga una paleta desde un fichero
|
||||
void unloadTexture(); // Libera la memoria de la textura
|
||||
void unloadSurface(); // Libera la surface actual
|
||||
static auto readPalFile(const std::string& file_path) -> Palette; // Carga una paleta desde un archivo .pal
|
||||
};
|
||||
@@ -0,0 +1,165 @@
|
||||
#include "tiled_bg.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_FRect, SDL_GetRenderTarget, SDL_RenderTexture, SDL_PixelFormat, SDL_TextureAccess
|
||||
|
||||
#include <algorithm> // Para max
|
||||
#include <cmath> // Para cos, pow, sin
|
||||
#include <cstdlib> // Para rand
|
||||
#include <memory> // Para unique_ptr, make_unique
|
||||
#include <numbers> // Para pi
|
||||
#include <string> // Para basic_string
|
||||
|
||||
#include "resource.hpp" // Para Resource
|
||||
#include "screen.hpp" // Para Screen
|
||||
#include "sprite.hpp" // Para Sprite
|
||||
|
||||
// Constructor
|
||||
TiledBG::TiledBG(SDL_FRect pos, TiledBGMode mode)
|
||||
: renderer_(Screen::get()->getRenderer()),
|
||||
pos_(pos),
|
||||
mode_(mode == TiledBGMode::RANDOM ? static_cast<TiledBGMode>(rand() % 2) : mode) {
|
||||
// Crea la textura para el mosaico de fondo
|
||||
canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, pos_.w * 2, pos_.h * 2);
|
||||
|
||||
// Rellena la textura con el contenido
|
||||
fillTexture();
|
||||
|
||||
// Inicializa variables
|
||||
switch (mode_) {
|
||||
case TiledBGMode::STATIC:
|
||||
window_ = {.x = 0, .y = 0, .w = pos_.w, .h = pos_.h};
|
||||
speed_ = 0.0F;
|
||||
break;
|
||||
case TiledBGMode::DIAGONAL:
|
||||
window_ = {.x = 0, .y = 0, .w = pos_.w, .h = pos_.h};
|
||||
break;
|
||||
case TiledBGMode::CIRCLE:
|
||||
window_ = {.x = 128, .y = 128, .w = pos_.w, .h = pos_.h};
|
||||
break;
|
||||
default:
|
||||
window_ = {.x = 0, .y = 0, .w = pos_.w, .h = pos_.h};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
TiledBG::~TiledBG() {
|
||||
SDL_DestroyTexture(canvas_);
|
||||
}
|
||||
|
||||
// Rellena la textura con el contenido
|
||||
void TiledBG::fillTexture() {
|
||||
// Crea los objetos para pintar en la textura de fondo
|
||||
auto tile = std::make_unique<Sprite>(Resource::get()->getTexture("title_bg_tile.png"), (SDL_FRect){.x = 0, .y = 0, .w = TILE_WIDTH, .h = TILE_HEIGHT});
|
||||
|
||||
// Prepara para dibujar sobre la textura
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, canvas_);
|
||||
|
||||
// Rellena la textura con el tile
|
||||
const auto I_MAX = pos_.w * 2 / TILE_WIDTH;
|
||||
const auto J_MAX = pos_.h * 2 / TILE_HEIGHT;
|
||||
tile->setSpriteClip(0, 0, TILE_WIDTH, TILE_HEIGHT);
|
||||
for (int i = 0; i < I_MAX; ++i) {
|
||||
for (int j = 0; j < J_MAX; ++j) {
|
||||
tile->setX(i * TILE_WIDTH);
|
||||
tile->setY(j * TILE_HEIGHT);
|
||||
tile->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Vuelve a colocar el renderizador como estaba
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
// Pinta la clase en pantalla
|
||||
void TiledBG::render() {
|
||||
SDL_RenderTexture(renderer_, canvas_, &window_, &pos_);
|
||||
}
|
||||
|
||||
// Actualiza la lógica de la clase (time-based)
|
||||
void TiledBG::update(float delta_time) {
|
||||
updateSpeedChange(delta_time);
|
||||
updateDesp(delta_time);
|
||||
updateStop(delta_time);
|
||||
|
||||
switch (mode_) {
|
||||
case TiledBGMode::DIAGONAL: {
|
||||
// El tileado de fondo se desplaza en diagonal
|
||||
window_.x = static_cast<int>(desp_) % TILE_WIDTH;
|
||||
window_.y = static_cast<int>(desp_) % TILE_HEIGHT;
|
||||
|
||||
break;
|
||||
}
|
||||
case TiledBGMode::CIRCLE: {
|
||||
// El tileado de fondo se desplaza en circulo
|
||||
const float ANGLE_RAD = (desp_ * std::numbers::pi / 180.0F);
|
||||
|
||||
window_.x = 128 + static_cast<int>(std::cos(ANGLE_RAD) * 128);
|
||||
window_.y = 128 + static_cast<int>(std::sin(-ANGLE_RAD) * 96);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Detiene el desplazamiento de forma ordenada (time-based)
|
||||
void TiledBG::updateStop(float delta_time) {
|
||||
if (stopping_) {
|
||||
const int UMBRAL = STOP_THRESHOLD_FACTOR * speed_;
|
||||
|
||||
// Desacelerar si estamos cerca de completar el ciclo (ventana a punto de regresar a 0)
|
||||
if (window_.x >= TILE_WIDTH - UMBRAL) {
|
||||
// Aplicar desaceleración time-based
|
||||
float frame_rate = 60.0F;
|
||||
float deceleration_per_ms = std::pow(DECELERATION_FACTOR, frame_rate * delta_time / 1000.0F);
|
||||
speed_ /= deceleration_per_ms;
|
||||
|
||||
// Asegura que no baje demasiado
|
||||
speed_ = std::max(speed_, MIN_SPEED);
|
||||
}
|
||||
|
||||
// Si estamos en 0, detener
|
||||
if (window_.x == 0) {
|
||||
speed_ = 0.0F;
|
||||
stopping_ = false; // Desactivamos el estado de "stopping"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia la velocidad gradualmente en X segundos
|
||||
void TiledBG::changeSpeedTo(float target_speed, float duration_s) {
|
||||
if (duration_s <= 0.0F) {
|
||||
// Si la duración es 0 o negativa, cambia inmediatamente
|
||||
speed_ = target_speed;
|
||||
changing_speed_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Configurar el cambio gradual
|
||||
changing_speed_ = true;
|
||||
initial_speed_ = speed_;
|
||||
target_speed_ = target_speed;
|
||||
change_duration_s_ = duration_s;
|
||||
change_timer_s_ = 0.0F;
|
||||
}
|
||||
|
||||
// Actualiza el cambio gradual de velocidad (time-based)
|
||||
void TiledBG::updateSpeedChange(float delta_time) {
|
||||
if (!changing_speed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
change_timer_s_ += delta_time;
|
||||
|
||||
if (change_timer_s_ >= change_duration_s_) {
|
||||
// Cambio completado
|
||||
speed_ = target_speed_;
|
||||
changing_speed_ = false;
|
||||
} else {
|
||||
// Interpolación lineal entre velocidad inicial y objetivo
|
||||
float progress = change_timer_s_ / change_duration_s_;
|
||||
speed_ = initial_speed_ + ((target_speed_ - initial_speed_) * progress);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_SetTextureColorMod, SDL_Renderer, SDL_Texture
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
|
||||
// --- Enums ---
|
||||
enum class TiledBGMode : int { // Modos de funcionamiento para el tileado de fondo
|
||||
CIRCLE = 0,
|
||||
DIAGONAL = 1,
|
||||
RANDOM = 2,
|
||||
STATIC = 3,
|
||||
};
|
||||
|
||||
// --- Clase TiledBG: dibuja un tileado de fondo con efectos de movimiento ---
|
||||
// Esta clase se sirve de una textura "canvas", que rellena con los tiles.
|
||||
// El rectángulo "window" recorre la textura de diferentes formas para generar el efecto de movimiento.
|
||||
class TiledBG {
|
||||
public:
|
||||
// --- Constructores y destructor ---
|
||||
TiledBG(SDL_FRect pos, TiledBGMode mode);
|
||||
~TiledBG();
|
||||
|
||||
// --- Métodos principales ---
|
||||
void render(); // Pinta la clase en pantalla
|
||||
void update(float delta_time); // Actualiza la lógica de la clase
|
||||
|
||||
// --- Configuración ---
|
||||
void setSpeed(float speed) { speed_ = speed; } // Establece la velocidad
|
||||
void changeSpeedTo(float target_speed, float duration_s); // Cambia la velocidad gradualmente en X segundos
|
||||
void stopGracefully() { stopping_ = true; } // Detiene el desplazamiento de forma ordenada
|
||||
void setColor(Color color) { SDL_SetTextureColorMod(canvas_, color.r, color.g, color.b); } // Cambia el color de la textura
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto isStopped() const -> bool { return speed_ == 0.0F; } // Indica si está parado
|
||||
[[nodiscard]] auto isChangingSpeed() const -> bool { return changing_speed_; } // Indica si está cambiando velocidad gradualmente
|
||||
|
||||
private:
|
||||
// --- Constantes ---
|
||||
static constexpr int TILE_WIDTH = 64; // Ancho del tile
|
||||
static constexpr int TILE_HEIGHT = 64; // Alto del tile
|
||||
static constexpr float STOP_THRESHOLD_FACTOR = 20.0F; // Factor para umbral de parada
|
||||
static constexpr float DECELERATION_FACTOR = 1.05F; // Factor de desaceleración
|
||||
static constexpr float MIN_SPEED = 0.1F; // Velocidad mínima
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Renderer* renderer_; // El renderizador de la ventana
|
||||
SDL_Texture* canvas_; // Textura donde dibujar el fondo formado por tiles
|
||||
|
||||
// --- Variables de estado ---
|
||||
SDL_FRect pos_; // Posición y tamaño del mosaico
|
||||
SDL_FRect window_; // Ventana visible para la textura de fondo del título
|
||||
TiledBGMode mode_; // Tipo de movimiento del mosaico
|
||||
float desp_ = 0.0F; // Desplazamiento aplicado
|
||||
float speed_ = 1.0F; // Incremento que se añade al desplazamiento a cada bucle
|
||||
bool stopping_ = false; // Indica si se está deteniendo
|
||||
|
||||
// --- Variables para cambio gradual de velocidad ---
|
||||
bool changing_speed_ = false; // Indica si está cambiando velocidad gradualmente
|
||||
float initial_speed_ = 0.0F; // Velocidad inicial del cambio
|
||||
float target_speed_ = 0.0F; // Velocidad objetivo del cambio
|
||||
float change_duration_s_ = 0.0F; // Duración total del cambio en segundos
|
||||
float change_timer_s_ = 0.0F; // Tiempo transcurrido del cambio
|
||||
|
||||
// --- Métodos internos ---
|
||||
void fillTexture(); // Rellena la textura con el contenido
|
||||
void updateDesp(float delta_time) { desp_ += speed_ * delta_time; } // Actualiza el desplazamiento (time-based)
|
||||
void updateStop(float delta_time); // Detiene el desplazamiento de forma ordenada (time-based)
|
||||
void updateSpeedChange(float delta_time); // Actualiza el cambio gradual de velocidad (time-based)
|
||||
};
|
||||
@@ -0,0 +1,106 @@
|
||||
#include "writer.hpp"
|
||||
|
||||
#include "text.hpp" // Para Text
|
||||
|
||||
// Actualiza el objeto (delta_time en ms)
|
||||
void Writer::update(float delta_time) {
|
||||
if (enabled_) {
|
||||
if (!completed_) {
|
||||
// No completado
|
||||
writing_timer_ += delta_time;
|
||||
if (writing_timer_ >= speed_interval_) {
|
||||
index_++;
|
||||
writing_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
if (index_ == length_) {
|
||||
completed_ = true;
|
||||
}
|
||||
} else {
|
||||
// Completado
|
||||
enabled_timer_ += delta_time;
|
||||
finished_ = enabled_timer_ >= enabled_timer_target_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el objeto (delta_time en segundos)
|
||||
void Writer::updateS(float delta_time) {
|
||||
// Convierte segundos a milisegundos y usa la lógica normal
|
||||
update(delta_time * 1000.0F);
|
||||
}
|
||||
|
||||
// Dibuja el objeto en pantalla
|
||||
void Writer::render() const {
|
||||
if (enabled_) {
|
||||
text_->write(pos_x_, pos_y_, caption_, kerning_, index_);
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setPosX(int value) {
|
||||
pos_x_ = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setPosY(int value) {
|
||||
pos_y_ = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setKerning(int value) {
|
||||
kerning_ = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setCaption(const std::string& text) {
|
||||
caption_ = text;
|
||||
length_ = text.length();
|
||||
}
|
||||
|
||||
// Establece el valor de la variable (frames)
|
||||
void Writer::setSpeed(int value) {
|
||||
// Convierte frames a milisegundos (frames * 16.67ms)
|
||||
constexpr float FRAME_TIME_MS = 16.67F;
|
||||
speed_interval_ = static_cast<float>(value) * FRAME_TIME_MS;
|
||||
writing_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
// Establece la velocidad en segundos entre caracteres
|
||||
void Writer::setSpeedS(float value) {
|
||||
// Convierte segundos a milisegundos para consistencia interna
|
||||
speed_interval_ = value * 1000.0F;
|
||||
writing_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setEnabled(bool value) {
|
||||
enabled_ = value;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
auto Writer::isEnabled() const -> bool {
|
||||
return enabled_;
|
||||
}
|
||||
|
||||
// Establece el temporizador para deshabilitar el objeto (en milisegundos)
|
||||
void Writer::setFinishedTimerMs(float time_ms) {
|
||||
enabled_timer_target_ = time_ms;
|
||||
enabled_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
// Establece el temporizador para deshabilitar el objeto (en segundos)
|
||||
void Writer::setFinishedTimerS(float time_s) {
|
||||
enabled_timer_target_ = time_s * 1000.0F; // Convertir segundos a milisegundos
|
||||
enabled_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
// Centra la cadena de texto a un punto X
|
||||
void Writer::center(int x) {
|
||||
setPosX(x - (text_->length(caption_, kerning_) / 2));
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
auto Writer::hasFinished() const -> bool {
|
||||
return finished_;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <utility>
|
||||
|
||||
class Text;
|
||||
|
||||
// --- Clase Writer: pinta texto en pantalla letra a letra ---
|
||||
class Writer {
|
||||
public:
|
||||
// --- Constructor y destructor ---
|
||||
explicit Writer(std::shared_ptr<Text> text)
|
||||
: text_(std::move(text)) {}
|
||||
~Writer() = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Actualiza el objeto (delta_time en ms)
|
||||
void updateS(float delta_time); // Actualiza el objeto (delta_time en segundos)
|
||||
void render() const; // Dibuja el objeto en pantalla
|
||||
|
||||
// --- Setters ---
|
||||
void setPosX(int value); // Establece la posición X
|
||||
void setPosY(int value); // Establece la posición Y
|
||||
void setKerning(int value); // Establece el kerning (espaciado entre caracteres)
|
||||
void setCaption(const std::string& text); // Establece el texto a escribir
|
||||
void setSpeed(int value); // Establece la velocidad de escritura (frames)
|
||||
void setSpeedS(float value); // Establece la velocidad de escritura (segundos entre caracteres)
|
||||
void setEnabled(bool value); // Habilita o deshabilita el objeto
|
||||
void setFinishedTimerMs(float time_ms); // Establece el temporizador para deshabilitar el objeto (en ms)
|
||||
void setFinishedTimerS(float time_s); // Establece el temporizador para deshabilitar el objeto (en segundos)
|
||||
|
||||
void center(int x); // Centra la cadena de texto a un punto X
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto isEnabled() const -> bool; // Indica si el objeto está habilitado
|
||||
[[nodiscard]] auto hasFinished() const -> bool; // Indica si ya ha terminado
|
||||
|
||||
private:
|
||||
// --- Objetos y punteros ---
|
||||
std::shared_ptr<Text> text_; // Objeto encargado de escribir el texto
|
||||
|
||||
// --- Variables de estado ---
|
||||
std::string caption_; // El texto para escribir
|
||||
int pos_x_ = 0; // Posición en el eje X donde empezar a escribir el texto
|
||||
int pos_y_ = 0; // Posición en el eje Y donde empezar a escribir el texto
|
||||
int kerning_ = 0; // Kerning del texto, es decir, espaciado entre caracteres
|
||||
float speed_interval_ = 0.0F; // Intervalo entre caracteres (ms para compatibilidad)
|
||||
float writing_timer_ = 0.0F; // 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
|
||||
float enabled_timer_ = 0.0F; // Temporizador para deshabilitar el objeto
|
||||
float enabled_timer_target_ = 0.0F; // Tiempo objetivo para deshabilitar el objeto
|
||||
bool completed_ = false; // Indica si se ha escrito todo el texto
|
||||
bool enabled_ = false; // Indica si el objeto está habilitado
|
||||
bool finished_ = false; // Indica si ya ha terminado
|
||||
};
|
||||
Reference in New Issue
Block a user