diff --git a/data/config/assets.txt b/data/config/assets.txt index 7d63b59..7f25b3b 100644 --- a/data/config/assets.txt +++ b/data/config/assets.txt @@ -177,6 +177,7 @@ BITMAP|${PREFIX}/data/font/04b_25_grey.png BITMAP|${PREFIX}/data/font/04b_25_metal.png BITMAP|${PREFIX}/data/font/04b_25_reversed_2x.png BITMAP|${PREFIX}/data/font/04b_25_reversed.png +BITMAP|${PREFIX}/data/font/04b_25_white.png BITMAP|${PREFIX}/data/font/04b_25.png BITMAP|${PREFIX}/data/font/8bithud.png BITMAP|${PREFIX}/data/font/aseprite.png diff --git a/data/config/param_320x256.txt b/data/config/param_320x256.txt index 79afb62..d3a1038 100644 --- a/data/config/param_320x256.txt +++ b/data/config/param_320x256.txt @@ -112,7 +112,7 @@ tabe.max_spawn_time 3.0f # Tiempo máximo en segundos para que aparezca el Ta # Jugador 1 - Camiseta por defecto player.default_shirt[0].darkest 028ECFFF # Tono más oscuro - bordes y contornos (Jugador 1, por defecto) player.default_shirt[0].dark 0297DBFF # Tono oscuro - sombras (Jugador 1, por defecto) -player.default_shirt[0].base 000000FF # Tono principal - color base (Jugador 1, por defecto) +player.default_shirt[0].base 029FE8FF # Tono principal - color base (Jugador 1, por defecto) player.default_shirt[0].light 03A9F4FF # Tono claro - zonas iluminadas (Jugador 1, por defecto) # Jugador 2 - Camiseta por defecto diff --git a/data/font/04b_25_white.png b/data/font/04b_25_white.png new file mode 100644 index 0000000..0e15717 Binary files /dev/null and b/data/font/04b_25_white.png differ diff --git a/source/resource.cpp b/source/resource.cpp index bca423a..e80f6d7 100644 --- a/source/resource.cpp +++ b/source/resource.cpp @@ -626,9 +626,9 @@ void Resource::createTextTextures() { {"game_text_stop", Lang::getText("[GAME_TEXT] 6")}, {"game_text_1000000_points", Lang::getText("[GAME_TEXT] 8")}}; - auto text = getText("04b_25"); + auto text = getText("04b_25_enhanced"); for (const auto &s : strings) { - textures_.emplace_back(s.name, text->writeToTexture(s.text, 1, -2)); + textures_.emplace_back(s.name, text->writeDXToTexture(Text::STROKE, s.text, -2, NO_TEXT_COLOR, 1, Color(255, 255, 0, 255))); printWithDots("Texture : ", s.name, "[ DONE ]"); } @@ -653,15 +653,18 @@ void Resource::createText() { std::string key; std::string texture_file; std::string text_file; + std::string white_texture_file; // Textura blanca opcional - ResourceInfo(std::string k, std::string t_file, std::string txt_file) - : key(std::move(k)), texture_file(std::move(t_file)), text_file(std::move(txt_file)) {} + ResourceInfo(std::string k, std::string t_file, std::string txt_file, std::string w_file = "") + : key(std::move(k)), texture_file(std::move(t_file)), text_file(std::move(txt_file)), white_texture_file(std::move(w_file)) {} }; SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> CREATING TEXT OBJECTS"); std::vector resources = { {"04b_25", "04b_25.png", "04b_25.txt"}, + {"04b_25_enhanced", "04b_25.png", "04b_25.txt", "04b_25_white.png"}, // Nueva fuente con textura blanca + {"04b_25_white", "04b_25_white.png", "04b_25.txt"}, {"04b_25_2x", "04b_25_2x.png", "04b_25_2x.txt"}, {"04b_25_metal", "04b_25_metal.png", "04b_25.txt"}, {"04b_25_grey", "04b_25_grey.png", "04b_25.txt"}, @@ -675,7 +678,13 @@ void Resource::createText() { {"smb2_grad", "smb2_grad.png", "smb2.txt"}}; for (const auto &resource : resources) { - texts_.emplace_back(resource.key, std::make_shared(getTexture(resource.texture_file), getTextFile(resource.text_file))); + if (!resource.white_texture_file.empty()) { + // Crear texto con textura blanca + texts_.emplace_back(resource.key, std::make_shared(getTexture(resource.texture_file), getTexture(resource.white_texture_file), getTextFile(resource.text_file))); + } else { + // Crear texto normal + texts_.emplace_back(resource.key, std::make_shared(getTexture(resource.texture_file), getTextFile(resource.text_file))); + } printWithDots("Text : ", resource.key, "[ DONE ]"); } } diff --git a/source/sections/game.cpp b/source/sections/game.cpp index 4812d17..736b8d5 100644 --- a/source/sections/game.cpp +++ b/source/sections/game.cpp @@ -687,7 +687,8 @@ void Game::renderItems() { // Devuelve un item al azar y luego segun sus probabilidades auto Game::dropItem() -> ItemType { - const auto LUCKY_NUMBER = rand() % 100; + //const auto LUCKY_NUMBER = rand() % 100; + const auto LUCKY_NUMBER = 0; const auto ITEM = rand() % 6; switch (ITEM) { diff --git a/source/text.cpp b/source/text.cpp index 3714937..f138f60 100644 --- a/source/text.cpp +++ b/source/text.cpp @@ -52,6 +52,47 @@ Text::Text(const std::shared_ptr &texture, const std::shared_ptr &texture, const std::shared_ptr &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(texture, (SDL_FRect){0, 0, static_cast(box_width_), static_cast(box_height_)}); + white_sprite_ = std::make_unique(white_texture, (SDL_FRect){0, 0, static_cast(box_width_), static_cast(box_height_)}); + + // Inicializa variables + fixed_width_ = false; +} + +// Constructor con textura blanca opcional +Text::Text(const std::shared_ptr &texture, const std::shared_ptr &white_texture, const std::shared_ptr &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(texture, (SDL_FRect){0, 0, static_cast(box_width_), static_cast(box_height_)}); + white_sprite_ = std::make_unique(white_texture, (SDL_FRect){0, 0, static_cast(box_width_), static_cast(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; @@ -114,8 +155,30 @@ auto Text::writeToTexture(const std::string &text, int zoom, int kerning, int le 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 { auto *renderer = Screen::get()->getRenderer(); auto texture = std::make_shared(renderer); - auto width = Text::length(text, kerning) + shadow_distance; - auto height = box_height_ + shadow_distance; + + // 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); @@ -123,7 +186,7 @@ auto Text::writeDXToTexture(Uint8 flags, const std::string &text, int kerning, C texture->setAsRenderTarget(renderer); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); SDL_RenderClear(renderer); - writeDX(flags, 0, 0, text, kerning, text_color, shadow_distance, shadow_color, length); + writeDX(flags, offset_x, offset_y, text, kerning, text_color, shadow_distance, shadow_color, length); SDL_SetRenderTarget(renderer, temp); return texture; @@ -131,9 +194,79 @@ auto Text::writeDXToTexture(Uint8 flags, const std::string &text, int kerning, C // Escribe el texto con colores void Text::writeColored(int x, int y, const std::string &text, Color color, int kerning, int length) { - sprite_->getTexture()->setColor(color.r, color.g, color.b); - write(x, y, text, kerning, length); - sprite_->getTexture()->setColor(255, 255, 255); + 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(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(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; 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 @@ -160,14 +293,28 @@ void Text::writeDX(Uint8 flags, int x, int y, const std::string &text, int kerni } if (SHADOWED) { - writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, length); + 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); + } } if (STROKED) { - for (int dist = 1; dist <= shadow_distance; ++dist) { - for (int dy = -dist; dy <= dist; ++dy) { - for (int dx = -dist; dx <= dist; ++dx) { - writeColored(x + dx, y + dy, text, shadow_color, kerning, length); + 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 + for (int dist = 1; 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, shadow_color, kerning, length); + } else { + writeColored(x + dx, y + dy, text, shadow_color, kerning, length); + } + } } } } diff --git a/source/text.h b/source/text.h index f8bc7ae..a3a05be 100644 --- a/source/text.h +++ b/source/text.h @@ -54,6 +54,8 @@ class Text { // --- Constructores y destructor --- Text(const std::shared_ptr &texture, const std::string &text_file); Text(const std::shared_ptr &texture, const std::shared_ptr &text_file); + Text(const std::shared_ptr &texture, const std::shared_ptr &white_texture, const std::string &text_file); + Text(const std::shared_ptr &texture, const std::shared_ptr &white_texture, const std::shared_ptr &text_file); ~Text() = default; // --- Métodos de escritura en pantalla --- @@ -81,9 +83,14 @@ class Text { // --- Métodos estáticos --- static auto loadFile(const std::string &file_path) -> std::shared_ptr; // 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 + private: // --- Objetos y punteros --- - std::unique_ptr sprite_ = nullptr; // Objeto con los gráficos para el texto + std::unique_ptr sprite_ = nullptr; // Objeto con los gráficos para el texto + std::unique_ptr white_sprite_ = nullptr; // Objeto con los gráficos en blanco para efectos // --- Variables de estado --- std::array offset_ = {}; // Vector con las posiciones y ancho de cada letra