Input: model upate/poll finalitzat

This commit is contained in:
2025-06-29 11:37:01 +02:00
parent ce1ae74e88
commit 5006289a5d
21 changed files with 183 additions and 155 deletions

541
source/sections/credits.cpp Normal file
View File

@@ -0,0 +1,541 @@
// IWYU pragma: no_include <bits/std_abs.h>
#include "credits.h"
#include <SDL3/SDL_blendmode.h> // Para SDL_BLENDMODE_BLEND
#include <SDL3/SDL_events.h> // Para SDL_PollEvent, SDL_Event
#include <SDL3/SDL_pixels.h> // Para SDL_PixelFormat
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks
#include <algorithm> // Para max, min, clamp
#include <array> // Para array
#include <stdexcept> // Para runtime_error
#include <string> // Para basic_string, string
#include <vector> // Para vector
#include "balloon_manager.h" // Para BalloonManager
#include "fade.h" // Para Fade, FadeType, FadeMode
#include "global_events.h" // Para check
#include "global_inputs.h" // Para check, update
#include "input.h" // Para Input, INPUT_ALLOW_REPEAT
#include "audio.h" // Para JA_GetMusicState, JA_SetMusicVolume
#include "lang.h" // Para getText
#include "param.h" // Para Param, ParamGame, param
#include "player.h" // Para Player, PlayerState
#include "resource.h" // Para Resource
#include "screen.h" // Para Screen
#include "section.h" // Para Name, name
#include "sprite.h" // Para Sprite
#include "text.h" // Para Text, TEXT_CENTER, TEXT_SHADOW
#include "texture.h" // Para Texture
#include "tiled_bg.h" // Para TiledBG, TiledBGMode
#include "utils.h" // Para Color, Zone, shdw_txt_color, no_color
#include "ui/service_menu.h"
#include "input.h"
// Textos
constexpr const char TEXT_COPYRIGHT[] = "@2020,2025 JailDesigner";
// Constructor
Credits::Credits()
: balloon_manager_(std::make_unique<BalloonManager>()),
tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::DIAGONAL)),
fade_in_(std::make_unique<Fade>()),
fade_out_(std::make_unique<Fade>()),
text_texture_(SDL_CreateTexture(Screen::get()->getRenderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
canvas_(SDL_CreateTexture(Screen::get()->getRenderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height))
{
if (!text_texture_)
{
throw std::runtime_error("Failed to create SDL texture for text.");
}
Section::name = Section::Name::CREDITS;
balloon_manager_->setPlayArea(play_area_);
fade_in_->setColor(param.fade.color);
fade_in_->setType(FadeType::FULLSCREEN);
fade_in_->setPostDuration(50);
fade_in_->setMode(FadeMode::IN);
fade_in_->activate();
fade_out_->setColor(0, 0, 0);
fade_out_->setType(FadeType::FULLSCREEN);
fade_out_->setPostDuration(400);
updateRedRect();
tiled_bg_->setColor(Color(255, 96, 96));
initPlayers();
SDL_SetTextureBlendMode(text_texture_, SDL_BLENDMODE_BLEND);
fillTextTexture();
steps_ = std::abs((top_black_rect_.h - param.game.game_area.center_y - 1) + ((left_black_rect_.w - param.game.game_area.center_x) / 4));
}
// Destructor
Credits::~Credits()
{
SDL_DestroyTexture(text_texture_);
SDL_DestroyTexture(canvas_);
resetVolume();
Audio::get()->stopMusic();
}
// Bucle principal
void Credits::run()
{
while (Section::name == Section::Name::CREDITS)
{
checkInput();
update();
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Actualiza las variables
void Credits::update()
{
if (SDL_GetTicks() - ticks_ > param.game.speed)
{
ticks_ = SDL_GetTicks();
const int REPEAT = want_to_pass_ ? 4 : 1;
for (int i = 0; i < REPEAT; ++i)
{
tiled_bg_->update();
cycleColors();
balloon_manager_->update();
updateTextureDstRects();
throwBalloons();
for (auto &player : players_)
{
player->update();
}
updateAllFades();
++counter_;
}
Screen::get()->update();
fillCanvas();
}
}
// Dibuja Credits::en patalla
void Credits::render()
{
// Prepara para empezar a dibujar en la textura de juego
Screen::get()->start();
// Copia la textura con la zona de juego a la pantalla
SDL_RenderTexture(Screen::get()->getRenderer(), canvas_, nullptr, nullptr);
// Vuelca el contenido del renderizador en pantalla
Screen::get()->render();
}
// Comprueba el manejador de eventos
void Credits::checkEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
GlobalEvents::check(event);
}
}
// Comprueba las entradas
void Credits::checkInput()
{
Input::get()->update();
if (!ServiceMenu::get()->isEnabled())
{
// Comprueba si se ha pulsado cualquier botón (de los usados para jugar)
if (Input::get()->checkAnyButton(INPUT_ALLOW_REPEAT))
{
want_to_pass_ = true;
fading_ = mini_logo_on_position_;
}
else
{
want_to_pass_ = false;
}
}
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
GlobalInputs::check();
}
// Crea la textura con el texto
void Credits::fillTextTexture()
{
auto text = Resource::get()->getText("smb2");
SDL_SetRenderTarget(Screen::get()->getRenderer(), text_texture_);
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
SDL_RenderClear(Screen::get()->getRenderer());
const std::array<std::string, 10> TEXTS = {
Lang::getText("[CREDITS] PROGRAMMED_AND_DESIGNED_BY"),
Lang::getText("[CREDITS] PIXELART_DRAWN_BY"),
Lang::getText("[CREDITS] MUSIC_COMPOSED_BY"),
Lang::getText("[CREDITS] SOUND_EFFECTS"),
"JAILDESIGNER",
"JAILDOCTOR (INTRO)",
"ERIC MATYAS (SOUNDIMAGE.ORG)",
"WWW.THEMOTIONMONKEY.CO.UK",
"WWW.KENNEY.NL",
"JAILDOCTOR"};
const int SPACE_POST_TITLE = 3 + text->getCharacterSize();
const int SPACE_PRE_TITLE = text->getCharacterSize() * 4;
const int TEXTS_HEIGHT = 1 * text->getCharacterSize() + 7 * SPACE_POST_TITLE + 3 * SPACE_PRE_TITLE;
credits_rect_dst_.h = credits_rect_src_.h = TEXTS_HEIGHT;
int y = (param.game.height - TEXTS_HEIGHT) / 2;
y = 0;
text->setPalette(1);
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(0), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
text->setPalette(0);
y += SPACE_POST_TITLE;
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(4), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
y += SPACE_PRE_TITLE;
text->setPalette(1);
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(1), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
text->setPalette(0);
y += SPACE_POST_TITLE;
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(4), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
y += SPACE_PRE_TITLE;
text->setPalette(1);
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(2), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
text->setPalette(0);
y += SPACE_POST_TITLE;
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(5), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
y += SPACE_POST_TITLE;
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(6), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
y += SPACE_PRE_TITLE;
text->setPalette(1);
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(3), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
text->setPalette(0);
y += SPACE_POST_TITLE;
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(7), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
y += SPACE_POST_TITLE;
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(8), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
y += SPACE_POST_TITLE;
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXTS.at(9), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
// Mini logo
y += SPACE_PRE_TITLE;
mini_logo_rect_src_.y = y;
auto mini_logo_sprite = std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"));
mini_logo_sprite->setPosition(1 + param.game.game_area.center_x - mini_logo_sprite->getWidth() / 2, 1 + y);
Resource::get()->getTexture("logo_jailgames_mini.png")->setColor(SHADOW_TEXT_COLOR.r, SHADOW_TEXT_COLOR.g, SHADOW_TEXT_COLOR.b);
mini_logo_sprite->render();
mini_logo_sprite->setPosition(param.game.game_area.center_x - mini_logo_sprite->getWidth() / 2, y);
Resource::get()->getTexture("logo_jailgames_mini.png")->setColor(255, 255, 255);
mini_logo_sprite->render();
// Texto con el copyright
y += mini_logo_sprite->getHeight() + 3;
text->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, y, TEXT_COPYRIGHT, 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
// Resetea el renderizador
SDL_SetRenderTarget(Screen::get()->getRenderer(), nullptr);
// Actualiza las variables
mini_logo_rect_dst_.h = mini_logo_rect_src_.h = mini_logo_sprite->getHeight() + 3 + text->getCharacterSize();
credits_rect_dst_.y = param.game.game_area.rect.h;
mini_logo_rect_dst_.y = credits_rect_dst_.y + credits_rect_dst_.h + 30;
mini_logo_final_pos_ = param.game.game_area.center_y - mini_logo_rect_src_.h / 2;
}
// Dibuja todos los sprites en la textura
void Credits::fillCanvas()
{
// Cambia el destino del renderizador
auto temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
SDL_SetRenderTarget(Screen::get()->getRenderer(), canvas_);
// Dibuja el fondo, los globos y los jugadores
tiled_bg_->render();
balloon_manager_->render();
for (auto const &player : players_)
{
player->render();
}
// Dibuja los titulos de credito
SDL_RenderTexture(Screen::get()->getRenderer(), text_texture_, &credits_rect_src_, &credits_rect_dst_);
// Dibuja el mini_logo
SDL_RenderTexture(Screen::get()->getRenderer(), text_texture_, &mini_logo_rect_src_, &mini_logo_rect_dst_);
// Dibuja los rectangulos negros
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0xFF);
SDL_RenderFillRect(Screen::get()->getRenderer(), &top_black_rect_);
SDL_RenderFillRect(Screen::get()->getRenderer(), &bottom_black_rect_);
SDL_RenderFillRect(Screen::get()->getRenderer(), &left_black_rect_);
SDL_RenderFillRect(Screen::get()->getRenderer(), &right_black_rect_);
// Dibuja el rectangulo rojo
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0xFF, 0, 0, 0xFF);
SDL_RenderRect(Screen::get()->getRenderer(), &red_rect);
// Si el mini_logo está en su destino, lo dibuja encima de lo anterior
if (mini_logo_on_position_)
{
SDL_RenderTexture(Screen::get()->getRenderer(), text_texture_, &mini_logo_rect_src_, &mini_logo_rect_dst_);
}
// Dibuja el fade sobre el resto de elementos
fade_in_->render();
fade_out_->render();
// Deja el renderizador apuntando donde estaba
SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
}
// Actualiza el destino de los rectangulos de las texturas
void Credits::updateTextureDstRects()
{
if (counter_ % 10 == 0)
{
// Comprueba la posición de la textura con los titulos de credito
if (credits_rect_dst_.y + credits_rect_dst_.h > play_area_.y)
{
--credits_rect_dst_.y;
}
// Comprueba la posición de la textura con el mini_logo
if (mini_logo_rect_dst_.y == mini_logo_final_pos_)
{
mini_logo_on_position_ = true;
// Si el jugador quiere pasar los titulos de credito, el fade se inicia solo
if (want_to_pass_)
{
fading_ = true;
}
// Se activa el contador para evitar que la sección sea infinita
if (counter_prevent_endless_ == 1000)
{
fading_ = true;
}
else
{
++counter_prevent_endless_;
}
}
else
{
--mini_logo_rect_dst_.y;
}
}
}
// Tira globos al escenario
void Credits::throwBalloons()
{
constexpr int speed = 200;
const std::vector<int> sets = {0, 63, 25, 67, 17, 75, 13, 50};
if (counter_ > ((sets.size() - 1) * speed) * 3)
{
return;
}
if (counter_ % speed == 0)
{
const int index = (counter_ / speed) % sets.size();
balloon_manager_->deploySet(sets.at(index), -60);
}
if (counter_ % (speed * 4) == 0 && counter_ > 0)
{
balloon_manager_->createPowerBall();
}
}
// Inicializa los jugadores
void Credits::initPlayers()
{
std::vector<std::vector<std::shared_ptr<Texture>>> player_textures; // Vector con todas las texturas de los jugadores;
std::vector<std::vector<std::string>> player_animations; // Vector con las animaciones del jugador
// Texturas - Player1
{
std::vector<std::shared_ptr<Texture>> player_texture;
player_texture.emplace_back(Resource::get()->getTexture("player1.gif"));
player_texture.emplace_back(Resource::get()->getTexture("player1_power.png"));
player_textures.push_back(player_texture);
}
// Texturas - Player2
{
std::vector<std::shared_ptr<Texture>> player_texture;
player_texture.emplace_back(Resource::get()->getTexture("player2.gif"));
player_texture.emplace_back(Resource::get()->getTexture("player2_power.png"));
player_textures.push_back(player_texture);
}
// Animaciones -- Jugador
{
player_animations.emplace_back(Resource::get()->getAnimation("player.ani"));
player_animations.emplace_back(Resource::get()->getAnimation("player_power.ani"));
}
// Crea los dos jugadores
constexpr int player_width = 30;
const int y = play_area_.y + play_area_.h - player_width;
constexpr bool demo = false;
constexpr int away_distance = 700;
players_.emplace_back(std::make_unique<Player>(1, play_area_.x - away_distance - player_width, y, demo, play_area_, player_textures.at(0), player_animations));
players_.back()->setWalkingState(PlayerState::WALKING_RIGHT);
players_.back()->setPlayingState(PlayerState::CREDITS);
players_.back()->setInvulnerable(false);
players_.emplace_back(std::make_unique<Player>(2, play_area_.x + play_area_.w + away_distance, y, demo, play_area_, player_textures.at(1), player_animations));
players_.back()->setWalkingState(PlayerState::WALKING_LEFT);
players_.back()->setPlayingState(PlayerState::CREDITS);
players_.back()->setInvulnerable(false);
}
// Actualiza los rectangulos negros
void Credits::updateBlackRects()
{
static int current_step = steps_;
if (top_black_rect_.h != param.game.game_area.center_y - 1 && bottom_black_rect_.y != param.game.game_area.center_y + 1)
{
// Si los rectangulos superior e inferior no han llegado al centro
if (counter_ % 4 == 0)
{
// Incrementa la altura del rectangulo superior
top_black_rect_.h = std::min(top_black_rect_.h + 1, param.game.game_area.center_y - 1);
// Incrementa la altura y modifica la posición del rectangulo inferior
++bottom_black_rect_.h;
bottom_black_rect_.y = std::max(bottom_black_rect_.y - 1, param.game.game_area.center_y + 1);
--current_step;
setVolume(static_cast<int>(initial_volume_ * current_step / steps_));
}
}
else
{
// Si los rectangulos superior e inferior han llegado al centro
if (left_black_rect_.w != param.game.game_area.center_x && right_black_rect_.x != param.game.game_area.center_x)
{
constexpr int SPEED = 2;
// Si los rectangulos izquierdo y derecho no han llegado al centro
// Incrementa la anchura del rectangulo situado a la izquierda
left_black_rect_.w = std::min(left_black_rect_.w + SPEED, param.game.game_area.center_x);
// Incrementa la anchura y modifica la posición del rectangulo situado a la derecha
right_black_rect_.w += SPEED;
right_black_rect_.x = std::max(right_black_rect_.x - SPEED, param.game.game_area.center_x);
--current_step;
setVolume(static_cast<int>(initial_volume_ * current_step / steps_));
}
else
{
// Si los rectangulos izquierdo y derecho han llegado al centro
setVolume(0);
Audio::get()->stopMusic();
if (counter_pre_fade_ == 400)
{
fade_out_->activate();
}
else
{
++counter_pre_fade_;
}
}
}
}
// Actualiza el rectangulo rojo
void Credits::updateRedRect()
{
red_rect.x = left_black_rect_.x + left_black_rect_.w;
red_rect.y = top_black_rect_.y + top_black_rect_.h - 1;
red_rect.w = right_black_rect_.x - red_rect.x;
red_rect.h = bottom_black_rect_.y - red_rect.y + 1;
}
// Actualiza el estado de fade
void Credits::updateAllFades()
{
if (fading_)
{
updateBlackRects();
updateRedRect();
}
fade_in_->update();
if (fade_in_->hasEnded())
{
Audio::get()->playMusic("credits.ogg");
}
fade_out_->update();
if (fade_out_->hasEnded())
{
Section::name = Section::Name::HI_SCORE_TABLE;
}
}
// Establece el nivel de volumen
void Credits::setVolume(int amount)
{
Options::audio.music.volume = std::clamp(amount, 0, 100);
Audio::get()->setMusicVolume(Options::audio.music.volume);
}
// Reestablece el nivel de volumen
void Credits::resetVolume()
{
Options::audio.music.volume = initial_volume_;
Audio::get()->setMusicVolume(Options::audio.music.volume);
}
// Cambia el color del fondo
void Credits::cycleColors()
{
constexpr int UPPER_LIMIT = 255; // Límite superior
constexpr int LOWER_LIMIT = 80; // Límite inferior
static float r = static_cast<float>(UPPER_LIMIT);
static float g = static_cast<float>(LOWER_LIMIT);
static float b = static_cast<float>(LOWER_LIMIT);
static float stepR = -0.5f; // Paso flotante para transiciones suaves
static float stepG = 0.3f;
static float stepB = 0.7f;
// Ajustar valores de R
r += stepR;
if (r >= UPPER_LIMIT || r <= LOWER_LIMIT)
{
stepR = -stepR; // Cambia de dirección al alcanzar los límites
}
// Ajustar valores de G
g += stepG;
if (g >= UPPER_LIMIT || g <= LOWER_LIMIT)
{
stepG = -stepG; // Cambia de dirección al alcanzar los límites
}
// Ajustar valores de B
b += stepB;
if (b >= UPPER_LIMIT || b <= LOWER_LIMIT)
{
stepB = -stepB; // Cambia de dirección al alcanzar los límites
}
// Aplicar el color, redondeando a enteros antes de usar
tiled_bg_->setColor(Color(static_cast<int>(r), static_cast<int>(g), static_cast<int>(b)));
}

128
source/sections/credits.h Normal file
View File

@@ -0,0 +1,128 @@
#pragma once
#include <SDL3/SDL_rect.h>
#include <SDL3/SDL_render.h>
#include <SDL3/SDL_stdinc.h>
#include <memory>
#include <vector>
#include "options.h"
#include "param.h"
#include "utils.h"
// Declaraciones adelantadas
class BalloonManager;
class Fade;
class Player;
class TiledBG;
class Credits
{
public:
// --- Constructores y destructor ---
Credits();
~Credits();
// --- Bucle principal ---
void run();
private:
// --- Constantes de clase ---
static constexpr int PLAY_AREA_HEIGHT = 200;
// --- Objetos principales ---
std::unique_ptr<BalloonManager> balloon_manager_; // Gestión de globos
std::unique_ptr<TiledBG> tiled_bg_; // Mosaico animado de fondo
std::unique_ptr<Fade> fade_in_; // Fundido de entrada
std::unique_ptr<Fade> fade_out_; // Fundido de salida
std::vector<std::shared_ptr<Player>> players_; // Vector de jugadores
// --- Gestión de texturas ---
SDL_Texture *text_texture_; // Textura con el texto de créditos
SDL_Texture *canvas_; // Textura donde se dibuja todo
// --- Temporización y contadores ---
Uint64 ticks_ = 0; // Control de velocidad del programa
Uint32 counter_ = 0; // Contador principal de lógica
Uint32 counter_pre_fade_ = 0; // Activación del fundido final
Uint32 counter_prevent_endless_ = 0; // Prevención de bucle infinito
// --- Variables de estado ---
bool fading_ = false; // Estado del fade final
bool want_to_pass_ = false; // Jugador quiere saltarse créditos
bool mini_logo_on_position_ = false; // Minilogo en posición final
// --- Diseño y posicionamiento ---
float black_bars_size_ = (param.game.game_area.rect.h - PLAY_AREA_HEIGHT) / 2; // Tamaño de las barras negras
int mini_logo_final_pos_ = 0; // Posición final del minilogo
// --- Control de audio ---
int initial_volume_ = Options::audio.music.volume; // Volumen inicial
int steps_ = 0; // Pasos para reducir audio
// --- Rectángulos de renderizado ---
// Texto de créditos
SDL_FRect credits_rect_src_ = param.game.game_area.rect;
SDL_FRect credits_rect_dst_ = param.game.game_area.rect;
// Mini logo
SDL_FRect mini_logo_rect_src_ = param.game.game_area.rect;
SDL_FRect mini_logo_rect_dst_ = param.game.game_area.rect;
// Definición del área de juego
SDL_FRect play_area_ = {
param.game.game_area.rect.x,
param.game.game_area.rect.y + black_bars_size_,
param.game.game_area.rect.w,
PLAY_AREA_HEIGHT};
// Barras negras para efecto letterbox
SDL_FRect top_black_rect_ = {
play_area_.x,
param.game.game_area.rect.y,
play_area_.w,
black_bars_size_};
SDL_FRect bottom_black_rect_ = {
play_area_.x,
param.game.game_area.rect.h - black_bars_size_,
play_area_.w,
black_bars_size_};
SDL_FRect left_black_rect_ = {
play_area_.x,
param.game.game_area.center_y - 1,
0,
2};
SDL_FRect right_black_rect_ = {
play_area_.x + play_area_.w,
param.game.game_area.center_y - 1,
0,
2};
// Borde para la ventana
SDL_FRect red_rect = play_area_; // Delimitador de ventana
// --- Métodos del bucle principal ---
void update(); // Actualización principal de la lógica
void render(); // Renderizado de la escena
void checkEvents(); // Manejo de eventos
void checkInput(); // Procesamiento de entrada
// --- Métodos de renderizado ---
void fillTextTexture(); // Crear textura de texto de créditos
void fillCanvas(); // Renderizar todos los sprites y fondos
void updateTextureDstRects(); // Actualizar destinos de texturas
// --- Métodos de lógica del juego ---
void throwBalloons(); // Lanzar globos al escenario
void initPlayers(); // Inicializar jugadores
void updateAllFades(); // Actualizar estados de fade
void cycleColors(); // Cambiar colores de fondo
// --- Métodos de interfaz ---
void updateBlackRects(); // Actualizar rectángulos negros (letterbox)
void updateRedRect(); // Actualizar rectángulo rojo (borde)
// --- Métodos de audio ---
void setVolume(int amount); // Establecer volumen
void resetVolume(); // Restablecer volumen
};

2043
source/sections/game.cpp Normal file

File diff suppressed because it is too large Load Diff

248
source/sections/game.h Normal file
View File

@@ -0,0 +1,248 @@
#pragma once
#include <SDL3/SDL_events.h> // Para SDL_Event
#include <SDL3/SDL_render.h> // Para SDL_Renderer, SDL_Texture
#include <SDL3/SDL_stdinc.h> // Para Uint64, Uint8
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "manage_hiscore_table.h" // Para HiScoreEntry
#include "options.h" // Para GameOptions, Options, options
#include "player.h" // Para Player
#include "utils.h" // Para Demo
class Audio;
class Asset;
class Background;
class BalloonManager;
class Tabe;
class Bullet;
class Fade;
class Input;
class Item;
class PathSprite;
class Scoreboard;
class Screen;
class SmartSprite;
class Texture;
enum class BulletType : Uint8;
enum class ItemType;
struct Path;
// Modo demo
constexpr bool GAME_MODE_DEMO_OFF = false;
constexpr bool GAME_MODE_DEMO_ON = true;
// Cantidad de elementos a escribir en los ficheros de datos
constexpr int TOTAL_SCORE_DATA = 3;
// Clase Game
class Game
{
public:
// Constructor
Game(int playerID, int current_stage, bool demo);
// Destructor
~Game();
// Bucle principal del juego
void run();
private:
// --- Tipos internos ---
enum class GameState
{
FADE_IN,
ENTERING_PLAYER,
SHOWING_GET_READY_MESSAGE,
PLAYING,
COMPLETED,
GAME_OVER,
};
// --- Constantes internas ---
static constexpr int HELP_COUNTER_ = 1000;
static constexpr int GAME_COMPLETED_START_FADE_ = 500;
static constexpr int GAME_COMPLETED_END_ = 700;
static constexpr int GAME_OVER_COUNTER_ = 350;
static constexpr int TIME_STOPPED_COUNTER_ = 360;
static constexpr int ITEM_POINTS_1_DISK_ODDS_ = 10;
static constexpr int ITEM_POINTS_2_GAVINA_ODDS_ = 6;
static constexpr int ITEM_POINTS_3_PACMAR_ODDS_ = 3;
static constexpr int ITEM_CLOCK_ODDS_ = 5;
static constexpr int ITEM_COFFEE_ODDS_ = 5;
static constexpr int ITEM_POWER_BALL_ODDS_ = 0;
static constexpr int ITEM_COFFEE_MACHINE_ODDS_ = 4;
// --- Estructuras ---
struct Helper
{
bool need_coffee; // Indica si se necesitan cafes
bool need_coffee_machine; // Indica si se necesita PowerUp
bool need_power_ball; // Indica si se necesita una PowerBall
int counter; // Contador para no dar ayudas consecutivas
int item_disk_odds; // Probabilidad de aparición del objeto
int item_gavina_odds; // Probabilidad de aparición del objeto
int item_pacmar_odds; // Probabilidad de aparición del objeto
int item_clock_odds; // Probabilidad de aparición del objeto
int item_coffee_odds; // Probabilidad de aparición del objeto
int item_coffee_machine_odds; // Probabilidad de aparición del objeto
Helper()
: need_coffee(false),
need_coffee_machine(false),
need_power_ball(false),
counter(HELP_COUNTER_),
item_disk_odds(ITEM_POINTS_1_DISK_ODDS_),
item_gavina_odds(ITEM_POINTS_2_GAVINA_ODDS_),
item_pacmar_odds(ITEM_POINTS_3_PACMAR_ODDS_),
item_clock_odds(ITEM_CLOCK_ODDS_),
item_coffee_odds(ITEM_COFFEE_ODDS_),
item_coffee_machine_odds(ITEM_COFFEE_MACHINE_ODDS_) {}
};
// --- Objetos y punteros ---
SDL_Renderer *renderer_; // El renderizador de la ventana
Screen *screen_; // Objeto encargado de dibujar en pantalla
Input *input_; // Manejador de entrada
Scoreboard *scoreboard_; // Objeto para dibujar el marcador
std::unique_ptr<Background> background_; // Objeto para dibujar el fondo del juego
SDL_Texture *canvas_; // Textura para dibujar la zona de juego
std::vector<std::shared_ptr<Player>> players_; // Vector con los jugadores
std::vector<std::unique_ptr<Bullet>> bullets_; // Vector con las balas
std::vector<std::unique_ptr<Item>> items_; // Vector con los items
std::vector<std::unique_ptr<SmartSprite>> smart_sprites_; // Vector con los smartsprites
std::vector<std::unique_ptr<PathSprite>> path_sprites_; // Vector con los pathsprites
std::vector<std::shared_ptr<Texture>> item_textures_; // Vector con las texturas de los items
std::vector<std::vector<std::shared_ptr<Texture>>> player_textures_; // Vector con todas las texturas de los jugadores
std::vector<std::shared_ptr<Texture>> game_text_textures_; // Vector con las texturas para los sprites con textos
std::vector<std::vector<std::string>> item_animations_; // Vector con las animaciones de los items
std::vector<std::vector<std::string>> player_animations_; // Vector con las animaciones del jugador
std::unique_ptr<Fade> fade_in_; // Objeto para renderizar fades
std::unique_ptr<Fade> fade_out_; // Objeto para renderizar fades
std::unique_ptr<BalloonManager> balloon_manager_; // Objeto para gestionar los globos
std::unique_ptr<Tabe> tabe_; // Objeto para gestionar el Tabe Volaor
std::vector<Path> paths_; // Vector con los recorridos precalculados almacenados
// --- Variables de estado ---
HiScoreEntry hi_score_ = HiScoreEntry(
Options::settings.hi_score_table[0].name,
Options::settings.hi_score_table[0].score); // Máxima puntuación y nombre de quien la ostenta
Demo demo_; // Variable con todas las variables relacionadas con el modo demo
Options::DifficultyCode difficulty_ = Options::settings.difficulty; // Dificultad del juego
Helper helper_; // Variable para gestionar las ayudas
Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
bool coffee_machine_enabled_ = false; // Indica si hay una máquina de café en el terreno de juego
bool hi_score_achieved_ = false; // Indica si se ha superado la puntuación máxima
bool paused_ = false; // Indica si el juego está pausado (no se deberia de poder utilizar en el modo arcade)
float difficulty_score_multiplier_; // Multiplicador de puntos en función de la dificultad
int counter_ = 0; // Contador para el juego
int game_completed_counter_ = 0; // Contador para el tramo final, cuando se ha completado la partida y ya no aparecen más enemigos
int game_over_counter_ = GAME_OVER_COUNTER_; // Contador para el estado de fin de partida
int time_stopped_counter_ = 0; // Temporizador para llevar la cuenta del tiempo detenido
int total_power_to_complete_game_; // La suma del poder necesario para completar todas las fases
int menace_current_ = 0; // Nivel de amenaza actual
int menace_threshold_ = 0; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el número de globos
GameState state_ = GameState::FADE_IN; // Estado
#ifdef DEBUG
bool auto_pop_balloons_ = false; // Si es true, incrementa automaticamente los globos explotados
// Comprueba los eventos en el modo DEBUG
void checkDebugEvents(const SDL_Event &event);
#endif
// --- Métodos internos ---
void update(); // Actualiza el juego
void render(); // Dibuja el juego
void checkEvents(); // Comprueba los eventos que hay en cola
void setResources(); // Asigna texturas y animaciones
void updateHiScore(); // Actualiza el valor de HiScore en caso necesario
void updatePlayers(); // Actualiza las variables del jugador
void renderPlayers(); // Dibuja a los jugadores
void updateStage(); // Comprueba si hay cambio de fase y actualiza las variables
void updateGameStateGameOver(); // Actualiza el estado de fin de la partida
void destroyAllItems(); // Destruye todos los items
bool checkPlayerBalloonCollision(std::shared_ptr<Player> &player); // Comprueba la colisión entre el jugador y los globos activos
void checkPlayerItemCollision(std::shared_ptr<Player> &player); // Comprueba la colisión entre el jugador y los items
void checkBulletCollision(); // Comprueba y procesa la colisión de las balas
void updateBullets(); // Mueve las balas activas
void renderBullets(); // Pinta las balas activas
void createBullet(int x, int y, BulletType kind, bool powered_up, int owner); // Crea un objeto bala
void freeBullets(); // Vacia el vector de balas
void updateItems(); // Actualiza los items
void renderItems(); // Pinta los items activos
ItemType dropItem(); // Devuelve un item en función del azar
void createItem(ItemType type, float x, float y); // Crea un objeto item
void freeItems(); // Vacia el vector de items
void createItemText(int x, std::shared_ptr<Texture> texture); // Crea un objeto PathSprite
void createMessage(const std::vector<Path> &paths, std::shared_ptr<Texture> texture); // Crea un objeto PathSprite
void freeSmartSprites(); // Vacia el vector de smartsprites
void freePathSprites(); // Vacia el vector de pathsprites
void throwCoffee(int x, int y); // Crea un SpriteSmart para arrojar el item café al recibir un impacto
void updateSmartSprites(); // Actualiza los SpriteSmarts
void renderSmartSprites(); // Pinta los SpriteSmarts activos
void updatePathSprites(); // Actualiza los PathSprites
void renderPathSprites(); // Pinta los PathSprites activos
void killPlayer(std::shared_ptr<Player> &player); // Acciones a realizar cuando el jugador muere
void updateTimeStopped(); // Actualiza y comprueba el valor de la variable
void updateBackground(); // Actualiza el fondo
void initPaths(); // Inicializa las variables que contienen puntos de ruta para mover objetos
void enableTimeStopItem(); // Habilita el efecto del item de detener el tiempo
void disableTimeStopItem(); // Deshabilita el efecto del item de detener el tiempo
void updateHelper(); // Actualiza las variables de ayuda
bool allPlayersAreWaitingOrGameOver(); // Comprueba si todos los jugadores han terminado de jugar
bool allPlayersAreGameOver(); // Comprueba si todos los jugadores han terminado de jugar
bool allPlayersAreNotPlaying(); // Comprueba si todos los jugadores han terminado de jugar
void updateScoreboard(); // Actualiza el marcador
void fillCanvas(); // Dibuja los elementos de la zona de juego en su textura
void pause(bool value); // Pausa el juego
void addScoreToScoreBoard(const std::shared_ptr<Player> &player); // Añade una puntuación a la tabla de records
void checkAndUpdatePlayerStatus(int active_player_index, int inactive_player_index); // Saca del estado de GAME OVER al jugador si el otro está activo
void checkPlayersStatusPlaying(); // Comprueba el estado de juego de los jugadores
std::shared_ptr<Player> getPlayer(int id); // Obtiene un jugador a partir de su "id"
int getController(int playerId); // Obtiene un controlador a partir del "id" del jugador
void checkInput(); // Gestiona la entrada durante el juego
void checkPauseInput(); // Verifica si alguno de los controladores ha solicitado una pausa y actualiza el estado de pausa del juego.
void DEMO_handleInput(); // Gestiona las entradas de los jugadores en el modo demo, incluyendo movimientos y disparos automáticos.
void DEMO_handlePassInput(); // Gestiona las entradas de los jugadores en el modo demo para saltarse la demo.
void DEMO_handlePlayerInput(const std::shared_ptr<Player> &player, int index); // Procesa las entradas para un jugador específico durante el modo demo.
void handleFireInput(const std::shared_ptr<Player> &player, BulletType bulletType); // Maneja el disparo de un jugador, incluyendo la creación de balas y la gestión del tiempo de espera entre disparos.
void handlePlayersInput(); // Gestiona las entradas de todos los jugadores en el modo normal (fuera del modo demo).
void handleNormalPlayerInput(const std::shared_ptr<Player> &player); // Maneja las entradas de movimiento y disparo para un jugador en modo normal.
void handleFireInputs(const std::shared_ptr<Player> &player, bool autofire, int controllerIndex); // Procesa las entradas de disparo del jugador, permitiendo disparos automáticos si está habilitado.
void handlePlayerContinue(const std::shared_ptr<Player> &player); // Maneja la continuación del jugador cuando no está jugando, permitiendo que continúe si se pulsa el botón de inicio.
void handleNameInput(const std::shared_ptr<Player> &player); // Procesa las entradas para la introducción del nombre del jugador.
void initDemo(int player_id); // Inicializa las variables para el modo DEMO
void setTotalPower(); // Calcula el poder total necesario para completar el juego
void initScoreboard(); // Inicializa el marcador
void initDifficultyVars(); // Inicializa las opciones relacionadas con la dificultad
void initPlayers(int player_id); // Inicializa los jugadores
void playMusic(); // Hace sonar la música
void stopMusic(); // Detiene la música
void updateDemo(); // Actualiza las variables durante el modo demo
#ifdef RECORDING
void updateRecording(); // Actualiza las variables durante el modo de grabación
#endif
void updateGameStateFadeIn(); // Actualiza las variables durante dicho estado
void updateGameStateEnteringPlayer(); // Actualiza las variables durante dicho estado
void updateGameStateShowingGetReadyMessage(); // Actualiza las variables durante dicho estado
void updateGameStatePlaying(); // Actualiza las variables durante el transcurso normal del juego
void updateGameStateCompleted(); // Gestiona eventos para el estado del final del juego
void checkState(); // Comprueba el estado del juego
void cleanVectors(); // Vacía los vectores de elementos deshabilitados
void updateMenace(); // Gestiona el nivel de amenaza
void evaluateAndSetMenace(); // Calcula y establece el valor de amenaza en funcion de los globos activos
void checkAndUpdateBalloonSpeed(); // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
void setState(GameState state); // Cambia el estado del juego
};

View File

@@ -0,0 +1,434 @@
#include "hiscore_table.h"
#include <SDL3/SDL_blendmode.h> // Para SDL_BLENDMODE_BLEND
#include <SDL3/SDL_events.h> // Para SDL_PollEvent, SDL_Event
#include <SDL3/SDL_pixels.h> // Para SDL_PixelFormat
#include <SDL3/SDL_render.h> // Para SDL_SetRenderTarget, SDL_CreateTex...
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks
#include <stdlib.h> // Para rand, size_t
#include <algorithm> // Para max
#include <functional> // Para function
#include <vector> // Para vector
#include "background.h" // Para Background
#include "fade.h" // Para Fade, FadeMode, FadeType
#include "global_events.h" // Para check
#include "global_inputs.h" // Para check, update
#include "audio.h" // Para JA_GetMusicState, JA_Music_state
#include "lang.h" // Para getText
#include "manage_hiscore_table.h" // Para HiScoreEntry
#include "options.h" // Para GameOptions, Options, options
#include "param.h" // Para Param, param, ParamGame, ParamFade
#include "path_sprite.h" // Para PathSprite, Path, PathType
#include "resource.h" // Para Resource
#include "screen.h" // Para Screen
#include "section.h" // Para Name, name, Options, options
#include "sprite.h" // Para Sprite
#include "text.h" // Para Text, TEXT_SHADOW, TEXT_COLOR
#include "texture.h" // Para Texture
#include "utils.h" // Para Color, easeOutQuint, fade_color, Zone
#include "input.h"
// Constructor
HiScoreTable::HiScoreTable()
: renderer_(Screen::get()->getRenderer()),
backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
fade_(std::make_unique<Fade>()),
background_(std::make_unique<Background>()),
counter_(0),
ticks_(0),
view_area_(SDL_FRect{0, 0, static_cast<float>(param.game.width), static_cast<float>(param.game.height)}),
fade_mode_(FadeMode::IN),
background_fade_color_(Color(0, 0, 0))
{
// Inicializa el resto
Section::name = Section::Name::HI_SCORE_TABLE;
SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);
initFade();
initBackground();
iniEntryColors();
createSprites();
}
// Destructor
HiScoreTable::~HiScoreTable()
{
SDL_DestroyTexture(backbuffer_);
Options::settings.clearLastHiScoreEntries();
}
// Actualiza las variables
void HiScoreTable::update()
{
if (SDL_GetTicks() - ticks_ > param.game.speed)
{
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
// Actualiza las posiciones de los sprites de texto
updateSprites();
// Actualiza el fondo
background_->update();
// Gestiona el fade
updateFade();
// Gestiona el contador y sus eventos
updateCounter();
// Dibuja los sprites en la textura
fillTexture();
// Actualiza el objeto screen
Screen::get()->update();
// Actualiza las variables de globalInputs
}
}
// Dibuja los sprites en la textura
void HiScoreTable::fillTexture()
{
// Pinta en el backbuffer el texto y los sprites
auto temp = SDL_GetRenderTarget(renderer_);
SDL_SetRenderTarget(renderer_, backbuffer_);
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
SDL_RenderClear(renderer_);
// Escribe el texto: Mejores puntuaciones
header_->render();
// Escribe los nombres de la tabla de puntuaciones
for (auto const &entry : entry_names_)
{
entry->render();
}
// Cambia el destino de renderizado
SDL_SetRenderTarget(renderer_, temp);
}
// Pinta en pantalla
void HiScoreTable::render()
{
// Prepara para empezar a dibujar en la textura de juego
Screen::get()->start();
// Limpia la pantalla
Screen::get()->clean();
// Pinta el fondo
background_->render();
// Establece la ventana del backbuffer
view_area_.y = std::max(0.0f, param.game.height - counter_ + 100);
// Copia el backbuffer al renderizador
SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_area_);
// Renderiza el fade
fade_->render();
// Vuelca el contenido del renderizador en pantalla
Screen::get()->render();
}
// Comprueba los eventos
void HiScoreTable::checkEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
GlobalEvents::check(event);
}
}
// Comprueba las entradas
void HiScoreTable::checkInput()
{
Input::get()->update();
GlobalInputs::check();
}
// Bucle para la pantalla de instrucciones
void HiScoreTable::run()
{
Audio::get()->playMusic("title.ogg");
while (Section::name == Section::Name::HI_SCORE_TABLE)
{
checkInput();
update();
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Gestiona el fade
void HiScoreTable::updateFade()
{
fade_->update();
if (fade_->hasEnded() && fade_mode_ == FadeMode::IN)
{
fade_->reset();
fade_mode_ = FadeMode::OUT;
fade_->setMode(fade_mode_);
}
if (fade_->hasEnded() && fade_mode_ == FadeMode::OUT)
{
Section::name = (Section::options == Section::Options::HI_SCORE_AFTER_PLAYING)
? Section::Name::TITLE
: Section::Name::INSTRUCTIONS;
Section::options = Section::Options::NONE;
}
}
// Convierte un entero a un string con separadores de miles
std::string HiScoreTable::format(int number)
{
const std::string separator = ".";
const std::string score = std::to_string(number);
auto index = (int)score.size() - 1;
std::string result;
auto i = 0;
while (index >= 0)
{
result = score.at(index) + result;
index--;
i++;
if (i == 3)
{
i = 0;
result = separator + result;
}
}
return result;
}
// Crea los sprites con los textos
void HiScoreTable::createSprites()
{
auto header_text = Resource::get()->getText("04b_25_grey");
auto entry_text = Resource::get()->getText("smb2");
// Obtiene el tamaño de la textura
float backbuffer_width;
float backbuffer_height;
SDL_GetTextureSize(backbuffer_, &backbuffer_width, &backbuffer_height);
constexpr int ENTRY_LENGHT = 22;
constexpr int MAX_NAMES = 10;
const int space_between_header = entry_text->getCharacterSize() * 4;
const int space_between_lines = entry_text->getCharacterSize() * 2;
const int size = space_between_header + space_between_lines * (MAX_NAMES - 1) + entry_text->getCharacterSize();
const int first_line = (param.game.height - size) / 2;
// Crea el sprite para el texto de cabecera
header_ = std::make_unique<Sprite>(header_text->writeDXToTexture(TEXT_COLOR, Lang::getText("[HIGHSCORE_TABLE] CAPTION"), -2, background_fade_color_.inverse().lighten(25)));
header_->setPosition(param.game.game_area.center_x - (header_->getWidth() / 2), first_line);
// Crea los sprites para las entradas en la tabla de puntuaciones
const int animation = rand() % 4;
const std::string sample_line(ENTRY_LENGHT + 3, ' ');
auto sample_entry = std::make_unique<Sprite>(entry_text->writeDXToTexture(TEXT_SHADOW, sample_line, 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR));
const auto entry_width = sample_entry->getWidth();
for (int i = 0; i < MAX_NAMES; ++i)
{
const auto table_position = format(i + 1) + ". ";
const auto score = format(Options::settings.hi_score_table.at(i).score);
const auto num_dots = ENTRY_LENGHT - Options::settings.hi_score_table.at(i).name.size() - score.size();
const auto one_cc = Options::settings.hi_score_table.at(i).one_credit_complete ? " }" : "";
std::string dots;
for (int j = 0; j < (int)num_dots; ++j)
{
dots = dots + ".";
}
const auto line = table_position + Options::settings.hi_score_table.at(i).name + dots + score + one_cc;
entry_names_.emplace_back(std::make_shared<PathSprite>(entry_text->writeDXToTexture(TEXT_SHADOW, line, 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR)));
const int default_pos_x = (backbuffer_width - entry_width) / 2;
const int pos_x = (i < 9) ? default_pos_x : default_pos_x - entry_text->getCharacterSize();
const int pos_y = (i * space_between_lines) + first_line + space_between_header;
constexpr int steps = 80;
switch (animation)
{
case 0: // Ambos lados alternativamente
{
if (i % 2 == 0)
{
entry_names_.back()->addPath(-entry_names_.back()->getWidth(), pos_x, PathType::HORIZONTAL, pos_y, steps, easeOutQuint);
entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0);
}
else
{
entry_names_.back()->addPath(backbuffer_width, pos_x, PathType::HORIZONTAL, pos_y, steps, easeOutQuint);
entry_names_.back()->setPosition(backbuffer_width, 0);
}
break;
}
case 1: // Entran por la izquierda
{
entry_names_.back()->addPath(-entry_names_.back()->getWidth(), pos_x, PathType::HORIZONTAL, pos_y, steps, easeOutQuint);
entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0);
break;
}
case 2: // Entran por la derecha
{
entry_names_.back()->addPath(backbuffer_width, pos_x, PathType::HORIZONTAL, pos_y, steps, easeOutQuint);
entry_names_.back()->setPosition(backbuffer_width, 0);
break;
}
case 3: // Entran desde la parte inferior
{
entry_names_.back()->addPath(backbuffer_height, pos_y, PathType::VERTICAL, pos_x, steps, easeOutQuint);
entry_names_.back()->setPosition(0, backbuffer_height);
}
default:
break;
}
}
}
// Actualiza las posiciones de los sprites de texto
void HiScoreTable::updateSprites()
{
constexpr int init_counter = 190;
const int counter_between_entries = 16;
if (counter_ >= init_counter)
{
const int counter2 = counter_ - init_counter;
if (counter2 % counter_between_entries == 0)
{
int index = counter2 / counter_between_entries;
if (index < static_cast<int>(entry_names_.size()))
{
entry_names_.at(index)->enable();
}
}
}
for (auto const &entry : entry_names_)
{
entry->update();
}
glowEntryNames();
}
// Inicializa el fade
void HiScoreTable::initFade()
{
fade_->setColor(param.fade.color);
fade_->setType(FadeType::RANDOM_SQUARE);
fade_->setPostDuration(param.fade.post_duration);
fade_->setMode(fade_mode_);
fade_->activate();
}
// Inicializa el fondo
void HiScoreTable::initBackground()
{
background_->setPos(param.game.game_area.rect);
background_->setCloudsSpeed(-0.1f);
const int lucky = rand() % 3;
switch (lucky)
{
case 0: // Fondo verde
{
background_->setGradientNumber(2);
background_->setTransition(0.0f);
background_->setSunProgression(1.0f);
background_->setMoonProgression(0.0f);
background_fade_color_ = GREEN_SKY_COLOR;
break;
}
case 1: // Fondo naranja
{
background_->setGradientNumber(1);
background_->setTransition(0.0f);
background_->setSunProgression(0.65f);
background_->setMoonProgression(0.0f);
background_fade_color_ = PINK_SKY_COLOR;
break;
}
case 2: // Fondo azul
{
background_->setGradientNumber(0);
background_->setTransition(0.0f);
background_->setSunProgression(0.0f);
background_->setMoonProgression(0.0f);
background_fade_color_ = BLUE_SKY_COLOR;
break;
}
default:
break;
}
}
// Obtiene un color del vector de colores de entradas
Color HiScoreTable::getEntryColor(int counter_)
{
int cycle_length = entry_colors_.size() * 2 - 2;
size_t n = counter_ % cycle_length;
size_t index;
if (n < entry_colors_.size())
{
index = n; // Avanza: 0,1,2,3
}
else
{
index = 2 * (entry_colors_.size() - 1) - n; // Retrocede: 2,1
}
return entry_colors_[index];
}
// Inicializa los colores de las entradas
void HiScoreTable::iniEntryColors()
{
entry_colors_.clear();
entry_colors_.emplace_back(background_fade_color_.inverse().lighten(75));
entry_colors_.emplace_back(background_fade_color_.inverse().lighten(50));
entry_colors_.emplace_back(background_fade_color_.inverse().lighten(25));
entry_colors_.emplace_back(background_fade_color_.inverse());
}
// Hace brillar los nombres de la tabla de records
void HiScoreTable::glowEntryNames()
{
const Color entry_color = getEntryColor(counter_ / 5);
for (const auto &entry_index : Options::settings.last_hi_score_entry)
{
if (entry_index != -1)
{
entry_names_.at(entry_index)->getTexture()->setColor(entry_color);
}
}
}
// Gestiona el contador
void HiScoreTable::updateCounter()
{
++counter_;
if (counter_ == 150)
{
background_->setColor(background_fade_color_.darken());
background_->setAlpha(96);
}
if (counter_ == COUNTER_END_)
{
fade_->activate();
}
}

View File

@@ -0,0 +1,79 @@
#pragma once
#include <SDL3/SDL_rect.h> // Para SDL_FRect
#include <SDL3/SDL_render.h> // Para SDL_Renderer, SDL_Texture
#include <SDL3/SDL_stdinc.h> // Para Uint16, Uint32, Uint8
#include <memory> // Para unique_ptr, shared_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "utils.h"
class Background;
class Fade;
class PathSprite;
class Sprite;
enum class FadeMode : Uint8;
struct Path;
/*
Esta clase gestiona un estado del programa. Se encarga de mostrar la tabla con las puntuaciones
más altas. Para ello utiliza un objeto que se encarga de pintar el fondo y una textura
sobre la que escribe las puntuaciones. Esta textura se recorre modificando la ventana de vista
para dar el efecto de que la textura se mueve sobre la pantalla.
Para mejorar la legibilidad de los textos, el objeto que dibuja el fondo es capaz de modificar
su atenuación.
*/
// Clase HiScoreTable
class HiScoreTable
{
public:
// Constructor
HiScoreTable();
// Destructor
~HiScoreTable();
// Bucle principal
void run();
private:
// --- Constantes ---
static constexpr Uint16 COUNTER_END_ = 800; // Valor final para el contador
// --- Objetos y punteros ---
SDL_Renderer *renderer_; // El renderizador de la ventana
SDL_Texture *backbuffer_; // Textura para usar como backbuffer
std::unique_ptr<Fade> fade_; // Objeto para renderizar fades
std::unique_ptr<Background> background_; // Objeto para dibujar el fondo del juego
std::unique_ptr<Sprite> header_; // Sprite con la cabecera del texto
std::vector<std::shared_ptr<PathSprite>> entry_names_; // Lista con los sprites de cada uno de los nombres de la tabla de records
std::vector<Path> paths_; // Vector con los recorridos precalculados
// --- Variables ---
Uint16 counter_ = 0; // Contador
Uint64 ticks_; // Contador de ticks para ajustar la velocidad del programa
SDL_FRect view_area_; // Parte de la textura que se muestra en pantalla
FadeMode fade_mode_; // Modo de fade a utilizar
Color background_fade_color_; // Color de atenuación del fondo
std::vector<Color> entry_colors_; // Colores para destacar las entradas en la tabla
// --- Métodos internos ---
void update(); // Actualiza las variables
void render(); // Pinta en pantalla
void checkEvents(); // Comprueba los eventos
void checkInput(); // Comprueba las entradas
std::string format(int number); // Convierte un entero a un string con separadores de miles
void fillTexture(); // Dibuja los sprites en la textura
void updateFade(); // Gestiona el fade
void createSprites(); // Crea los sprites con los textos
void updateSprites(); // Actualiza las posiciones de los sprites de texto
void initFade(); // Inicializa el fade
void initBackground(); // Inicializa el fondo
Color getEntryColor(int counter_); // Obtiene un color del vector de colores de entradas
void iniEntryColors(); // Inicializa los colores de las entradas
void glowEntryNames(); // Hace brillar los nombres de la tabla de records
void updateCounter(); // Gestiona el contador
};

View File

@@ -0,0 +1,381 @@
#include "instructions.h"
#include <SDL3/SDL_blendmode.h> // Para SDL_BLENDMODE_BLEND
#include <SDL3/SDL_events.h> // Para SDL_PollEvent, SDL_Event
#include <SDL3/SDL_pixels.h> // Para SDL_PIXELFORMAT_RGBA8888
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks
#include <algorithm> // Para max
#include <array> // Para array
#include <utility> // Para move
#include <vector> // Para vector
#include "fade.h" // Para Fade, FadeMode, FadeType
#include "global_events.h" // Para check
#include "global_inputs.h" // Para check, update
#include "audio.h" // Para JA_GetMusicState, JA_Music_state
#include "lang.h" // Para getText
#include "param.h" // Para Param, param, ParamGame, ParamFade
#include "resource.h" // Para Resource
#include "screen.h" // Para Screen
#include "section.h" // Para Name, name, Options, options
#include "sprite.h" // Para Sprite
#include "text.h" // Para Text, TEXT_CENTER, TEXT_COLOR, TEXT_...
#include "tiled_bg.h" // Para TiledBG, TiledBGMode
#include "utils.h" // Para Color, shdw_txt_color, Zone, no_color
#include "input.h"
// Constructor
Instructions::Instructions()
: renderer_(Screen::get()->getRenderer()),
texture_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
text_(Resource::get()->getText("smb2")),
tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::STATIC)),
fade_(std::make_unique<Fade>())
{
// Configura las texturas
SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);
SDL_SetTextureBlendMode(texture_, SDL_BLENDMODE_BLEND);
// Inicializa variables
Section::name = Section::Name::INSTRUCTIONS;
view_ = param.game.game_area.rect;
// Inicializa objetos
fade_->setColor(param.fade.color);
fade_->setType(FadeType::FULLSCREEN);
fade_->setPostDuration(param.fade.post_duration);
fade_->setMode(FadeMode::IN);
fade_->activate();
// Inicializa las líneas con un retraso progresivo de 50 ms
lines_ = initializeLines(256);
// Rellena la textura de texto
fillTexture();
// Inicializa los sprites de los items
iniSprites();
}
// Destructor
Instructions::~Instructions()
{
item_textures_.clear();
sprites_.clear();
SDL_DestroyTexture(backbuffer_);
SDL_DestroyTexture(texture_);
}
// Inicializa los sprites de los items
void Instructions::iniSprites()
{
// Inicializa las texturas
item_textures_.emplace_back(Resource::get()->getTexture("item_points1_disk.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_points2_gavina.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_points3_pacmar.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_clock.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_coffee.png"));
// Inicializa los sprites
for (int i = 0; i < (int)item_textures_.size(); ++i)
{
auto sprite = std::make_unique<Sprite>(item_textures_[i], 0, 0, param.game.item_size, param.game.item_size);
sprite->setPosition((SDL_FPoint){sprite_pos_.x, sprite_pos_.y + ((param.game.item_size + item_space_) * i)});
sprites_.push_back(std::move(sprite));
}
}
// Actualiza los sprites
void Instructions::updateSprites()
{
SDL_FRect src_rect = {0, 0, param.game.item_size, param.game.item_size};
// Disquito
src_rect.y = param.game.item_size * (((counter_ + 12) / 36) % 2);
sprites_[0]->setSpriteClip(src_rect);
// Gavina
src_rect.y = param.game.item_size * (((counter_ + 9) / 36) % 2);
sprites_[1]->setSpriteClip(src_rect);
// Pacmar
src_rect.y = param.game.item_size * (((counter_ + 6) / 36) % 2);
sprites_[2]->setSpriteClip(src_rect);
// Time Stopper
src_rect.y = param.game.item_size * (((counter_ + 3) / 36) % 2);
sprites_[3]->setSpriteClip(src_rect);
// Coffee
src_rect.y = param.game.item_size * (((counter_ + 0) / 36) % 2);
sprites_[4]->setSpriteClip(src_rect);
}
// Rellena la textura de texto
void Instructions::fillTexture()
{
const int desp_x = param.game.item_size + 8;
// Modifica el renderizador para pintar en la textura
auto temp = SDL_GetRenderTarget(renderer_);
SDL_SetRenderTarget(renderer_, texture_);
// Limpia la textura
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
SDL_RenderClear(renderer_);
// Constantes
constexpr int num_lines = 4;
constexpr int num_item_lines = 4;
constexpr int num_post_headers = 2;
constexpr int num_pre_headers = 1;
constexpr int space_post_header = 20;
constexpr int space_pre_header = 28;
const int space_between_lines = text_->getCharacterSize() * 1.5f;
const int space_between_item_lines = param.game.item_size + item_space_;
const int space_new_paragraph = space_between_lines * 0.5f;
const int size = (num_lines * space_between_lines) + (num_item_lines * space_between_item_lines) + (num_post_headers * space_post_header) + (num_pre_headers * space_pre_header) + (space_new_paragraph);
const int first_line = (param.game.height - size) / 2;
// Calcula cual es el texto más largo de las descripciones de los items
int lenght = 0;
const std::array<std::string, 5> ITEM_DESCRIPTIONS = {
Lang::getText("[INSTRUCTIONS] 07"),
Lang::getText("[INSTRUCTIONS] 08"),
Lang::getText("[INSTRUCTIONS] 09"),
Lang::getText("[INSTRUCTIONS] 10"),
Lang::getText("[INSTRUCTIONS] 11")};
for (const auto &desc : ITEM_DESCRIPTIONS)
{
const int l = text_->lenght(desc);
lenght = l > lenght ? l : lenght;
}
const int ANCHOR_ITEM = (param.game.width - (lenght + desp_x)) / 2;
constexpr Color ORANGE_COLOR = Color(0XFF, 0X7A, 0X00);
// Escribe el texto de las instrucciones
text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, first_line, Lang::getText("[INSTRUCTIONS] 01"), 1, ORANGE_COLOR, 1, SHADOW_TEXT_COLOR);
const int anchor1 = first_line + space_post_header;
text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, anchor1 + space_between_lines * 0, Lang::getText("[INSTRUCTIONS] 02"), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, anchor1 + space_between_lines * 1, Lang::getText("[INSTRUCTIONS] 03"), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, anchor1 + space_new_paragraph + space_between_lines * 2, Lang::getText("[INSTRUCTIONS] 04"), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, anchor1 + space_new_paragraph + space_between_lines * 3, Lang::getText("[INSTRUCTIONS] 05"), 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR);
// Escribe el texto de los objetos y sus puntos
const int anchor2 = anchor1 + space_pre_header + space_new_paragraph + space_between_lines * 3;
text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, anchor2, Lang::getText("[INSTRUCTIONS] 06"), 1, ORANGE_COLOR, 1, SHADOW_TEXT_COLOR);
const int anchor3 = anchor2 + space_post_header;
text_->writeShadowed(ANCHOR_ITEM + desp_x, anchor3 + space_between_item_lines * 0, Lang::getText("[INSTRUCTIONS] 07"), SHADOW_TEXT_COLOR);
text_->writeShadowed(ANCHOR_ITEM + desp_x, anchor3 + space_between_item_lines * 1, Lang::getText("[INSTRUCTIONS] 08"), SHADOW_TEXT_COLOR);
text_->writeShadowed(ANCHOR_ITEM + desp_x, anchor3 + space_between_item_lines * 2, Lang::getText("[INSTRUCTIONS] 09"), SHADOW_TEXT_COLOR);
text_->writeShadowed(ANCHOR_ITEM + desp_x, anchor3 + space_between_item_lines * 3, Lang::getText("[INSTRUCTIONS] 10"), SHADOW_TEXT_COLOR);
text_->writeShadowed(ANCHOR_ITEM + desp_x, anchor3 + space_between_item_lines * 4, Lang::getText("[INSTRUCTIONS] 11"), SHADOW_TEXT_COLOR);
// Deja el renderizador como estaba
SDL_SetRenderTarget(renderer_, temp);
// Da valor a la variable
sprite_pos_.x = ANCHOR_ITEM;
sprite_pos_.y = anchor3 - ((param.game.item_size - text_->getCharacterSize()) / 2);
}
// Rellena el backbuffer
void Instructions::fillBackbuffer()
{
// Modifica el renderizador para pintar en la textura
auto temp = SDL_GetRenderTarget(renderer_);
SDL_SetRenderTarget(renderer_, backbuffer_);
// Limpia la textura
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
SDL_RenderClear(renderer_);
// Coloca el texto de fondo
SDL_RenderTexture(renderer_, texture_, nullptr, nullptr);
// Dibuja los sprites
for (auto &sprite : sprites_)
{
sprite->render();
}
// Deja el renderizador como estaba
SDL_SetRenderTarget(renderer_, temp);
}
// Actualiza las variables
void Instructions::update()
{
if (SDL_GetTicks() - ticks_ > param.game.speed)
{
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
// Actualiza el objeto screen
Screen::get()->update();
// Incrementa el contador
counter_++;
// Actualiza los sprites
updateSprites();
// Gestiona la textura con los graficos
updateBackbuffer();
// Actualiza el mosaico de fondo
tiled_bg_->update();
// Actualiza el objeto "fade"
fade_->update();
// Rellena el backbuffer
fillBackbuffer();
}
}
// Pinta en pantalla
void Instructions::render()
{
// Prepara para empezar a dibujar en la textura de juego
Screen::get()->start();
// Limpia la pantalla
Screen::get()->clean();
// Dibuja el mosacico de fondo
tiled_bg_->render();
// Copia la textura y el backbuffer al renderizador
if (view_.y == 0)
renderLines(renderer_, backbuffer_, lines_);
else
SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_);
fade_->render();
// Vuelca el contenido del renderizador en pantalla
Screen::get()->render();
}
// Comprueba los eventos
void Instructions::checkEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
GlobalEvents::check(event);
}
}
// Comprueba las entradas
void Instructions::checkInput()
{
Input::get()->update();
GlobalInputs::check();
}
// Bucle para la pantalla de instrucciones
void Instructions::run()
{
Audio::get()->playMusic("title.ogg");
while (Section::name == Section::Name::INSTRUCTIONS)
{
checkInput();
update();
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Método para inicializar las líneas
std::vector<Line> Instructions::initializeLines(int height)
{
std::vector<Line> lines;
for (int y = 0; y < height; y++)
{
int direction = (y % 2 == 0) ? -1 : 1; // Pares a la izquierda, impares a la derecha
lines.emplace_back(y, 0.0f, direction);
}
return lines;
}
// Método para mover las líneas con suavizado
bool Instructions::moveLines(std::vector<Line> &lines, int width, float duration, Uint32 startDelay)
{
Uint32 current_time = SDL_GetTicks();
bool all_lines_off_screen = true;
for (auto &line : lines)
{
// Establecer startTime en el primer cuadro de animación
if (line.startTime == 0)
{
line.startTime = current_time + line.y * startDelay;
}
float elapsed_time = (current_time - line.startTime) / 1000.0f; // Convertir a segundos
if (elapsed_time < 0)
{
all_lines_off_screen = false; // Si aún no se debe mover esta línea, no están todas fuera de pantalla
continue;
}
if (elapsed_time >= duration)
{
continue; // Si la línea ha salido de los límites, no la muevas más
}
float t = elapsed_time / duration;
float smooth_factor = easeInOutQuint(t);
line.x = line.direction * smooth_factor * width;
all_lines_off_screen = false; // Si alguna línea aún se está moviendo, no están todas fuera de pantalla
}
return all_lines_off_screen;
}
// Método para renderizar las líneas
void Instructions::renderLines(SDL_Renderer *renderer, SDL_Texture *texture, const std::vector<Line> &lines)
{
for (const auto &LINE : lines)
{
SDL_FRect srcRect = {0, static_cast<float>(LINE.y), 320, 1};
SDL_FRect dstRect = {static_cast<float>(LINE.x), static_cast<float>(LINE.y), 320, 1};
SDL_RenderTexture(renderer, texture, &srcRect, &dstRect);
}
}
// Gestiona la textura con los graficos
void Instructions::updateBackbuffer()
{
// Establece la ventana del backbuffer
view_.y = std::max(0.0f, param.game.height - counter_ + 100);
// Verifica si view_.y == 0 y gestiona el temporizador
if (view_.y == 0)
{
if (!start_delay_triggered_)
{
// Activa el temporizador si no ha sido activado
start_delay_triggered_ = true;
start_delay_time_ = SDL_GetTicks();
}
else if (SDL_GetTicks() - start_delay_time_ >= 4000)
{
// Han pasado tres segundos, mover líneas
all_lines_off_screen_ = moveLines(lines_, 320, 1.0f, 5);
}
}
// Comprueba si el contador ha llegado al final
if (all_lines_off_screen_)
{
Section::name = Section::Name::TITLE;
Section::options = Section::Options::TITLE_1;
}
}

View File

@@ -0,0 +1,90 @@
#pragma once
#include <SDL3/SDL_rect.h> // Para SDL_FPoint, SDL_FRect
#include <SDL3/SDL_render.h> // Para SDL_Texture, SDL_Renderer
#include <SDL3/SDL_stdinc.h> // Para Uint32
#include <memory> // Para unique_ptr, shared_ptr
#include <vector> // Para vector
class Fade;
class Sprite;
class Text;
class Texture;
class TiledBG;
/*
Esta clase gestiona un estado del programa. Se encarga de poner en pantalla
un texto explicativo para entender cómo se juega.
Además muestra algunos items y explica para qué sirven.
Utiliza dos texturas de apoyo, una con el texto ya escrito y otra donde se combina
tanto el texto de la primera textura como los sprites de los items.
Finalmente, una ventana recorre la textura para dar el efecto de que todo se desplaza
por la pantalla sobre el mosaico de fondo (gestionado por el correspondiente objeto).
*/
// Estructura para almacenar información de línea animada
struct Line
{
int y; // Coordenada Y de la línea
float x; // Coordenada X inicial (usamos float para mayor precisión en el suavizado)
int direction; // Dirección de movimiento: -1 para izquierda, 1 para derecha
Uint32 startTime; // Tiempo de inicio del movimiento
// Constructor de Line
Line(int y, float x, int direction)
: y(y), x(x), direction(direction), startTime(0) {}
};
// Clase Instructions
class Instructions
{
public:
// Constructor
Instructions();
// Destructor
~Instructions();
// Bucle principal
void run();
private:
// --- Objetos y punteros ---
SDL_Renderer *renderer_; // El renderizador de la ventana
SDL_Texture *texture_; // Textura fija con el texto
SDL_Texture *backbuffer_; // Textura para usar como backbuffer
std::vector<std::shared_ptr<Texture>> item_textures_; // Vector con las texturas de los items
std::vector<std::unique_ptr<Sprite>> sprites_; // Vector con los sprites de los items
std::shared_ptr<Text> text_; // Objeto para escribir texto
std::unique_ptr<TiledBG> tiled_bg_; // Objeto para dibujar el mosaico animado de fondo
std::unique_ptr<Fade> fade_; // Objeto para renderizar fades
// --- Variables ---
int counter_ = 0; // Contador para manejar el progreso en la pantalla de instrucciones
Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla
SDL_FPoint sprite_pos_ = {0, 0}; // Posición del primer sprite en la lista
float item_space_ = 2.0; // Espacio entre los items en pantalla
std::vector<Line> lines_; // Vector que contiene las líneas animadas en la pantalla
bool all_lines_off_screen_ = false; // Indica si todas las líneas han salido de la pantalla
Uint32 start_delay_time_ = 0; // Tiempo de inicio del retraso para mover las líneas
bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado
// --- Métodos internos ---
void update(); // Actualiza las variables
void render(); // Pinta en pantalla
void checkEvents(); // Comprueba los eventos
void checkInput(); // Comprueba las entradas
void fillTexture(); // Rellena la textura de texto
void fillBackbuffer(); // Rellena el backbuffer
void iniSprites(); // Inicializa los sprites de los items
void updateSprites(); // Actualiza los sprites
std::vector<Line> initializeLines(int height); // Inicializa las líneas animadas
bool moveLines(std::vector<Line> &lines, int width, float duration, Uint32 startDelay); // Mueve las líneas
void renderLines(SDL_Renderer *renderer, SDL_Texture *texture, const std::vector<Line> &lines); // Renderiza las líneas
void updateBackbuffer(); // Gestiona la textura con los gráficos
};

536
source/sections/intro.cpp Normal file
View File

@@ -0,0 +1,536 @@
#include "intro.h"
#include <SDL3/SDL_blendmode.h> // Para SDL_BLENDMODE_BLEND, SDL_BLENDMODE_MOD
#include <SDL3/SDL_events.h> // Para SDL_PollEvent, SDL_Event
#include <SDL3/SDL_pixels.h> // Para SDL_PIXELFORMAT_RGBA8888
#include <SDL3/SDL_rect.h> // Para SDL_FRect
#include <SDL3/SDL_render.h> // Para SDL_SetTextureBlendMode, SDL_GetRend...
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks
#include <array> // Para array
#include <functional> // Para function
#include <string> // Para basic_string, string
#include <utility> // Para move
#include "global_events.h" // Para check
#include "global_inputs.h" // Para check, update
#include "audio.h" // Para JA_PlayMusic, JA_StopMusic
#include "lang.h" // Para getText
#include "param.h" // Para Param, ParamGame, param
#include "path_sprite.h" // Para PathSprite, PathType
#include "resource.h" // Para Resource
#include "screen.h" // Para Screen
#include "section.h" // Para Name, name, Options, options
#include "texture.h" // Para Texture
#include "tiled_bg.h" // Para TiledBG, TiledBGMode
#include "utils.h" // Para Color, Zone, easeOutQuint, BLOCK
#include "writer.h" // Para Writer
#include "input.h"
// Constructor
Intro::Intro()
: tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::DIAGONAL))
{
// Inicializa variables
Section::name = Section::Name::INTRO;
Section::options = Section::Options::NONE;
// Inicializa las imagens
initSprites();
// Inicializa los textos
initTexts();
// Configura el fondo
tiled_bg_->setSpeed(0.3f);
tiled_bg_->setColor(bg_color_);
}
// Comprueba los eventos
void Intro::checkEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
GlobalEvents::check(event);
}
}
// Comprueba las entradas
void Intro::checkInput()
{
Input::get()->update();
GlobalInputs::check();
}
// Actualiza las escenas de la intro
void Intro::updateScenes()
{
switch (scene_)
{
case 0:
{
// Primera imagen - UPV
sprites_.at(0)->enable();
shadow_sprites_.at(0)->enable();
// Primer texto de la primera imagen
if (sprites_.at(0)->hasFinished() && !texts_.at(0)->hasFinished())
{
texts_.at(0)->setEnabled(true);
}
// Segundo texto de la primera imagen
if (texts_.at(0)->hasFinished() && !texts_.at(1)->hasFinished())
{
texts_.at(0)->setEnabled(false);
texts_.at(1)->setEnabled(true);
}
// Tercer texto de la primera imagen
if (texts_.at(1)->hasFinished() && !texts_.at(2)->hasFinished())
{
texts_.at(1)->setEnabled(false);
texts_.at(2)->setEnabled(true);
}
// Fin de la primera escena
if (texts_.at(2)->hasFinished())
{
texts_.at(2)->setEnabled(false);
scene_++;
}
break;
}
case 1:
{
// Segunda imagen - Máquina
sprites_.at(1)->enable();
shadow_sprites_.at(1)->enable();
// Primer texto de la segunda imagen
if (sprites_.at(1)->hasFinished() && !texts_.at(3)->hasFinished())
{
texts_.at(3)->setEnabled(true);
}
// Fin de la segunda escena
if (texts_.at(3)->hasFinished())
{
texts_.at(3)->setEnabled(false);
scene_++;
}
break;
}
case 2:
{
// Tercera imagen junto con primer texto - GRITO
if (!texts_.at(4)->hasFinished())
{
sprites_.at(2)->enable();
shadow_sprites_.at(2)->enable();
texts_.at(4)->setEnabled(true);
}
// Fin de la tercera escena
if (sprites_.at(2)->hasFinished() && texts_.at(4)->hasFinished())
{
texts_.at(4)->setEnabled(false);
scene_++;
}
break;
}
case 3:
{
// Cuarta imagen junto con primer texto - Reflexión
sprites_.at(3)->enable();
shadow_sprites_.at(3)->enable();
if (!texts_.at(5)->hasFinished())
{
texts_.at(5)->setEnabled(true);
}
// Segundo texto de la cuarta imagen
if (texts_.at(5)->hasFinished() && !texts_.at(6)->hasFinished())
{
texts_.at(5)->setEnabled(false);
texts_.at(6)->setEnabled(true);
}
// Fin de la cuarta escena
if (sprites_.at(3)->hasFinished() && texts_.at(6)->hasFinished())
{
texts_.at(6)->setEnabled(false);
scene_++;
}
break;
}
case 4:
{
// Quinta imagen - Patada
sprites_.at(4)->enable();
shadow_sprites_.at(4)->enable();
// Primer texto de la quinta imagen
if (!texts_.at(7)->hasFinished())
{
texts_.at(7)->setEnabled(true);
}
// Fin de la quinta escena
if (sprites_.at(4)->hasFinished() && texts_.at(7)->hasFinished())
{
texts_.at(7)->setEnabled(false);
scene_++;
}
break;
}
case 5:
{
// Sexta imagen junto con texto - Globos de café
sprites_.at(5)->enable();
shadow_sprites_.at(5)->enable();
if (!texts_.at(8)->hasFinished())
{
texts_.at(8)->setEnabled(true);
}
// Acaba el último texto
if (texts_.at(8)->hasFinished())
{
texts_.at(8)->setEnabled(false);
}
// Acaba la ultima imagen
if (sprites_.at(5)->hasFinished() && texts_.at(8)->hasFinished())
{
state_ = IntroState::POST;
state_start_time_ = SDL_GetTicks();
}
break;
}
default:
break;
}
}
// Actualiza las variables del objeto
void Intro::update()
{
if (SDL_GetTicks() - ticks_ > param.game.speed)
{
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
// Actualiza el fondo
tiled_bg_->update();
switch (state_)
{
case IntroState::SCENES:
updateSprites();
updateTexts();
updateScenes();
break;
case IntroState::POST:
updatePostState();
break;
}
// Actualiza el objeto screen
Screen::get()->update();
}
}
// Dibuja el objeto en pantalla
void Intro::render()
{
// Prepara para empezar a dibujar en la textura de juego
Screen::get()->start();
// Limpia la pantalla
Screen::get()->clean();
// Dibuja el fondo
tiled_bg_->render();
switch (state_)
{
case IntroState::SCENES:
{
renderSprites();
renderTexts();
break;
}
case IntroState::POST:
break;
}
// Vuelca el contenido del renderizador en pantalla
Screen::get()->render();
}
// Bucle principal
void Intro::run()
{
Audio::get()->playMusic("intro.ogg", 0);
while (Section::name == Section::Name::INTRO)
{
checkInput();
update();
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Inicializa las imagens
void Intro::initSprites()
{
// Listado de imagenes a usar
const std::array<std::string, 6> TEXTURE_LIST = {
"intro1.png",
"intro2.png",
"intro3.png",
"intro4.png",
"intro5.png",
"intro6.png"};
// Constantes
auto texture = Resource::get()->getTexture(TEXTURE_LIST.front());
const float SPRITE_WIDTH = texture->getWidth();
const float SPRITE_HEIGHT = texture->getHeight();
const float X_DEST = param.game.game_area.center_x - SPRITE_WIDTH / 2;
const float Y_DEST = param.game.game_area.first_quarter_y - (SPRITE_HEIGHT / 4);
// Inicializa los sprites con las imagenes
constexpr int TOTAL_SPRITES = 6;
for (int i = 0; i < TOTAL_SPRITES; ++i)
{
auto sprite = std::make_unique<PathSprite>(Resource::get()->getTexture(TEXTURE_LIST.at(i)));
sprite->setWidth(SPRITE_WIDTH);
sprite->setHeight(SPRITE_HEIGHT);
sprite->setSpriteClip(0, 0, SPRITE_WIDTH, SPRITE_HEIGHT);
sprites_.push_back(std::move(sprite));
}
sprites_.at(0)->addPath(-SPRITE_WIDTH - 10, X_DEST, PathType::HORIZONTAL, Y_DEST, 100, easeInOutExpo, 0);
sprites_.at(1)->addPath(param.game.width, X_DEST, PathType::HORIZONTAL, Y_DEST, 100, easeOutBounce, 0);
sprites_.at(2)->addPath(-SPRITE_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, 40, easeOutQuint, 0);
sprites_.at(3)->addPath(param.game.height, Y_DEST, PathType::VERTICAL, X_DEST, 300, easeInOutExpo, 0);
sprites_.at(4)->addPath(-SPRITE_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, 70, easeOutElastic, 0);
sprites_.at(5)->addPath(param.game.width, X_DEST, PathType::HORIZONTAL, Y_DEST, 250, easeOutQuad, 450);
sprites_.at(5)->addPath(X_DEST, -SPRITE_WIDTH, PathType::HORIZONTAL, Y_DEST, 80, easeInElastic, 0);
// Constantes
const float BORDER = 4;
const float SHADOW_SPRITE_WIDTH = SPRITE_WIDTH + BORDER;
const float SHADOW_SPRITE_HEIGHT = SPRITE_HEIGHT + BORDER;
const float S_X_DEST = X_DEST - BORDER / 2;
const float S_Y_DEST = Y_DEST - BORDER / 2;
// Crea las texturas para las imágenes traseras
std::vector<std::shared_ptr<Texture>> shadow_textures;
for (int i = 0; i < TOTAL_SPRITES; ++i)
{
// Crea la textura
auto shadow_texture = std::make_shared<Texture>(Screen::get()->getRenderer());
shadow_texture->createBlank(SHADOW_SPRITE_WIDTH, SHADOW_SPRITE_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
shadow_texture->setBlendMode(SDL_BLENDMODE_BLEND);
// Apuntamos el renderizador a la textura
auto temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
shadow_texture->setAsRenderTarget(Screen::get()->getRenderer());
// Limpia la textura
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
SDL_RenderClear(Screen::get()->getRenderer());
// Pone color en el marco de la textura
auto color = param.intro.card_color;
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), color.r, color.g, color.b, color.a);
SDL_FRect rect1 = {1, 0, SHADOW_SPRITE_WIDTH - 2, SHADOW_SPRITE_HEIGHT};
SDL_FRect rect2 = {0, 1, SHADOW_SPRITE_WIDTH, SHADOW_SPRITE_HEIGHT - 2};
SDL_RenderRect(Screen::get()->getRenderer(), &rect1);
SDL_RenderRect(Screen::get()->getRenderer(), &rect2);
// Deja el renderizador como estaba y añade la textura a la lista
SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
shadow_textures.push_back(shadow_texture);
}
// Inicializa los sprites para la sombra
for (int i = 0; i < TOTAL_SPRITES; ++i)
{
auto sprite = std::make_unique<PathSprite>(shadow_textures.at(i));
sprite->setWidth(SHADOW_SPRITE_WIDTH);
sprite->setHeight(SHADOW_SPRITE_HEIGHT);
sprite->setSpriteClip(0, 0, SHADOW_SPRITE_WIDTH, SHADOW_SPRITE_HEIGHT);
shadow_sprites_.push_back(std::move(sprite));
}
shadow_sprites_.at(0)->addPath(param.game.height + 10, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 100, easeInOutExpo, 0);
shadow_sprites_.at(1)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 100, easeOutBounce, 0);
shadow_sprites_.at(2)->addPath(-SHADOW_SPRITE_WIDTH, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, 40, easeOutQuint, 0);
shadow_sprites_.at(3)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 400, easeInOutExpo, 0);
shadow_sprites_.at(4)->addPath(param.game.height, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 70, easeOutElastic, 0);
shadow_sprites_.at(5)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 250, easeOutQuad, 450);
shadow_sprites_.at(5)->addPath(S_X_DEST, param.game.width, PathType::HORIZONTAL, S_Y_DEST, 80, easeInElastic, 0);
}
// Inicializa los textos
void Intro::initTexts()
{
constexpr int TOTAL_TEXTS = 9;
for (int i = 0; i < TOTAL_TEXTS; ++i)
{
auto w = std::make_unique<Writer>(Resource::get()->getText("04b_25_metal"));
w->setPosX(BLOCK * 0);
w->setPosY(param.game.height - (BLOCK * 6));
w->setKerning(-2);
w->setEnabled(false);
w->setFinishedCounter(180);
texts_.push_back(std::move(w));
}
// Un dia qualsevol de l'any 2000
texts_.at(0)->setCaption(Lang::getText("[INTRO] 1"));
texts_.at(0)->setSpeed(8);
// Tot esta tranquil a la UPV
texts_.at(1)->setCaption(Lang::getText("[INTRO] 2"));
texts_.at(1)->setSpeed(8);
// Fins que un desaprensiu...
texts_.at(2)->setCaption(Lang::getText("[INTRO] 3"));
texts_.at(2)->setSpeed(12);
// HEY! ME ANE A FERME UN CORTAET...
texts_.at(3)->setCaption(Lang::getText("[INTRO] 4"));
texts_.at(3)->setSpeed(8);
// UAAAAAAAAAAAAA!!!
texts_.at(4)->setCaption(Lang::getText("[INTRO] 5"));
texts_.at(4)->setSpeed(1);
// Espera un moment...
texts_.at(5)->setCaption(Lang::getText("[INTRO] 6"));
texts_.at(5)->setSpeed(16);
// Si resulta que no tinc solt!
texts_.at(6)->setCaption(Lang::getText("[INTRO] 7"));
texts_.at(6)->setSpeed(2);
// MERDA DE MAQUINA!
texts_.at(7)->setCaption(Lang::getText("[INTRO] 8"));
texts_.at(7)->setSpeed(3);
// Blop... blop... blop...
texts_.at(8)->setCaption(Lang::getText("[INTRO] 9"));
texts_.at(8)->setSpeed(20);
for (auto &text : texts_)
{
text->center(param.game.game_area.center_x);
}
}
// Actualiza los sprites
void Intro::updateSprites()
{
for (auto &sprite : sprites_)
{
sprite->update();
}
for (auto &sprite : shadow_sprites_)
{
sprite->update();
}
}
// Actualiza los textos
void Intro::updateTexts()
{
for (auto &text : texts_)
{
text->update();
}
}
// Dibuja los sprites
void Intro::renderSprites()
{
shadow_sprites_.at(scene_)->render();
sprites_.at(scene_)->render();
}
// Dibuja los textos
void Intro::renderTexts()
{
for (const auto &text : texts_)
{
text->render();
}
}
// Actualiza el estado POST
void Intro::updatePostState()
{
const Uint32 ELAPSED_TIME = SDL_GetTicks() - state_start_time_;
switch (post_state_)
{
case IntroPostState::STOP_BG:
// EVENTO: Detiene el fondo después de 2 segundos
if (ELAPSED_TIME >= 1000)
{
tiled_bg_->stopGracefully();
// Modifica el color del fondo hasta llegar a blanco
if (bg_color_.r <= 253 || bg_color_.g <= 253 || bg_color_.b <= 253) // Garantiza que no se exceda de 255 al incrementar de 2 en 2
{
bg_color_ = bg_color_.lighten(2);
}
else
{
bg_color_ = Color(255, 255, 255); // Asegura que bg_color_ no exceda el límite máximo
}
tiled_bg_->setColor(bg_color_);
}
// Cambia de estado si el fondo se ha detenido y recuperado el color
if (tiled_bg_->isStopped() && bg_color_.r == 255 && bg_color_.g == 255 && bg_color_.b == 255)
{
post_state_ = IntroPostState::END;
state_start_time_ = SDL_GetTicks();
}
break;
case IntroPostState::END:
// Finaliza la intro después de 1 segundo
if (ELAPSED_TIME >= 1000)
{
Audio::get()->stopMusic();
Section::name = Section::Name::TITLE;
Section::options = Section::Options::TITLE_1;
}
break;
default:
break;
}
}

70
source/sections/intro.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include <SDL3/SDL_stdinc.h> // Para Uint32, Uint8
#include <memory> // Para unique_ptr
#include <vector> // Para vector
#include "path_sprite.h" // Para PathSprite
#include "tiled_bg.h" // Para TiledBG
#include "writer.h" // Para Writer
#include "param.h"
/*
Esta clase gestiona un estado del programa. Se encarga de mostrar la secuencia
de introducción.
*/
// Clase Intro
class Intro
{
public:
// Constructor
Intro();
// Destructor
~Intro() = default;
// Bucle principal
void run();
private:
// --- Estados internos ---
enum class IntroState
{
SCENES,
POST,
};
enum class IntroPostState
{
STOP_BG,
END,
};
// --- Objetos ---
std::vector<std::unique_ptr<PathSprite>> sprites_; // Vector con los sprites inteligentes para los dibujos de la intro
std::vector<std::unique_ptr<PathSprite>> shadow_sprites_; // Vector con los sprites inteligentes para las sombras
std::vector<std::unique_ptr<Writer>> texts_; // Textos de la intro
std::unique_ptr<TiledBG> tiled_bg_; // Fondo en mosaico
// --- Variables ---
Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
int scene_ = 0; // Indica qué escena está activa
IntroState state_ = IntroState::SCENES; // Estado principal de la intro
IntroPostState post_state_ = IntroPostState::STOP_BG; // Estado POST
Uint32 state_start_time_; // Tiempo de inicio del estado actual
Color bg_color_ = param.intro.bg_color; // Color de fondo
// --- Métodos internos ---
void update(); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla
void checkEvents(); // Comprueba los eventos
void checkInput(); // Comprueba las entradas
void updateScenes(); // Actualiza las escenas de la intro
void initSprites(); // Inicializa las imágenes
void initTexts(); // Inicializa los textos
void updateSprites(); // Actualiza los sprites
void updateTexts(); // Actualiza los textos
void renderSprites(); // Dibuja los sprites
void renderTexts(); // Dibuja los textos
void updatePostState(); // Actualiza el estado POST
};

209
source/sections/logo.cpp Normal file
View File

@@ -0,0 +1,209 @@
#include "logo.h"
#include <SDL3/SDL_events.h> // Para SDL_PollEvent, SDL_Event
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks
#include <utility> // Para move
#include "global_events.h" // Para check
#include "global_inputs.h" // Para check, update
#include "audio.h" // Para JA_FadeOutMusic, JA_PlaySound, JA_StopC...
#include "param.h" // Para Param, ParamGame, param
#include "resource.h" // Para Resource
#include "screen.h" // Para Screen
#include "section.h" // Para Name, name
#include "sprite.h" // Para Sprite
#include "texture.h" // Para Texture
#include "utils.h" // Para Color, Zone
#include "input.h"
// Constructor
Logo::Logo()
: since_texture_(Resource::get()->getTexture("logo_since_1998.png")),
since_sprite_(std::make_unique<Sprite>(since_texture_)),
jail_texture_(Resource::get()->getTexture("logo_jailgames.png"))
{
// Inicializa variables
Section::name = Section::Name::LOGO;
dest_.x = param.game.game_area.center_x - jail_texture_->getWidth() / 2;
dest_.y = param.game.game_area.center_y - jail_texture_->getHeight() / 2;
since_sprite_->setPosition(SDL_FRect{
static_cast<float>((param.game.width - since_texture_->getWidth()) / 2),
static_cast<float>(83 + jail_texture_->getHeight() + 5),
static_cast<float>(since_texture_->getWidth()),
static_cast<float>(since_texture_->getHeight())});
since_sprite_->setY(dest_.y + jail_texture_->getHeight() + 5);
since_sprite_->setSpriteClip(0, 0, since_texture_->getWidth(), since_texture_->getHeight());
since_texture_->setColor(0x00, 0x00, 0x00);
// Crea los sprites de cada linea
for (int i = 0; i < jail_texture_->getHeight(); ++i)
{
auto temp = std::make_unique<Sprite>(jail_texture_, 0, i, jail_texture_->getWidth(), 1);
temp->setSpriteClip(0, i, jail_texture_->getWidth(), 1);
const int POS_X = (i % 2 == 0) ? param.game.width + (i * 3) : -jail_texture_->getWidth() - (i * 3);
temp->setX(POS_X);
temp->setY(dest_.y + i);
jail_sprite_.push_back(std::move(temp));
}
// Inicializa el vector de colores
color_.push_back(Color(0x00, 0x00, 0x00)); // Black
color_.push_back(Color(0x00, 0x00, 0xd8)); // Blue
color_.push_back(Color(0xd8, 0x00, 0x00)); // Red
color_.push_back(Color(0xd8, 0x00, 0xd8)); // Magenta
color_.push_back(Color(0x00, 0xd8, 0x00)); // Green
color_.push_back(Color(0x00, 0xd8, 0xd8)); // Cyan
color_.push_back(Color(0xd8, 0xd8, 0x00)); // Yellow
color_.push_back(Color(0xFF, 0xFF, 0xFF)); // Bright white
}
// Destructor
Logo::~Logo()
{
jail_texture_->setColor(255, 255, 255);
since_texture_->setColor(255, 255, 255);
Audio::get()->stopAllSounds();
}
// Comprueba el manejador de eventos
void Logo::checkEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
GlobalEvents::check(event);
}
}
// Comprueba las entradas
void Logo::checkInput()
{
Input::get()->update();
GlobalInputs::check();
}
// Gestiona el logo de JAILGAMES
void Logo::updateJAILGAMES()
{
if (counter_ == 30)
{
Audio::get()->playSound("logo.wav");
}
if (counter_ > 30)
{
for (int i = 0; i < (int)jail_sprite_.size(); ++i)
{
if (jail_sprite_[i]->getX() != dest_.x)
{
if (i % 2 == 0)
{
jail_sprite_[i]->incX(-SPEED);
if (jail_sprite_[i]->getX() < dest_.x)
{
jail_sprite_[i]->setX(dest_.x);
}
}
else
{
jail_sprite_[i]->incX(SPEED);
if (jail_sprite_[i]->getX() > dest_.x)
{
jail_sprite_[i]->setX(dest_.x);
}
}
}
}
}
// Comprueba si ha terminado el logo
if (counter_ == END_LOGO_COUNTER_MARK + POST_LOGO_DURATION)
{
Section::name = Section::Name::INTRO;
}
}
// Gestiona el color de las texturas
void Logo::updateTextureColors()
{
constexpr int inc = 4;
// Manejo de 'sinceTexture'
for (int i = 0; i <= 7; ++i)
{
if (counter_ == SHOW_SINCE_SPRITE_COUNTER_MARK + inc * i)
{
since_texture_->setColor(color_[i].r, color_[i].g, color_[i].b);
}
}
// Manejo de 'jailTexture' y 'sinceTexture' en el fade
for (int i = 0; i <= 6; ++i)
{
if (counter_ == INIT_FADE_COUNTER_MARK + inc * i)
{
jail_texture_->setColor(color_[6 - i].r, color_[6 - i].g, color_[6 - i].b);
since_texture_->setColor(color_[6 - i].r, color_[6 - i].g, color_[6 - i].b);
}
}
}
// Actualiza las variables
void Logo::update()
{
if (SDL_GetTicks() - ticks_ > param.game.speed)
{
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
// Actualiza el objeto screen
Screen::get()->update();
// Comprueba las entradas
checkInput();
updateJAILGAMES();
updateTextureColors();
// Gestiona el contador
++counter_;
}
}
// Dibuja en pantalla
void Logo::render()
{
Screen::get()->start();
Screen::get()->clean();
renderJAILGAMES();
Screen::get()->render();
}
// Bucle para el logo del juego
void Logo::run()
{
Audio::get()->fadeOutMusic(300);
while (Section::name == Section::Name::LOGO)
{
checkInput();
update();
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Renderiza el logo de JAILGAMES
void Logo::renderJAILGAMES()
{
// Dibuja los sprites
for (auto &sprite : jail_sprite_)
{
sprite->render();
}
if (counter_ >= SHOW_SINCE_SPRITE_COUNTER_MARK)
{
since_sprite_->render();
}
}

61
source/sections/logo.h Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include <SDL3/SDL_rect.h> // Para SDL_FPoint
#include <SDL3/SDL_stdinc.h> // Para Uint64
#include <memory> // Para shared_ptr, unique_ptr
#include <vector> // Para vector
class Sprite;
class Texture;
struct Color;
/*
Esta clase gestiona un estado del programa. Se encarga de dibujar por pantalla el
logo de "JAILGAMES" utilizando un sencillo efecto consistente en generar un sprite por
cada línea del bitmap que forma la palabra "JAILGAMES". Posteriormente realiza una
modulación de color sobre la textura para simular un fade to black al estilo
ZX Spectrum.
*/
// --- Clase Logo ---
class Logo
{
public:
// Constructor
Logo();
// Destructor
~Logo();
// Bucle principal
void run();
private:
// --- Constantes ---
static constexpr int SHOW_SINCE_SPRITE_COUNTER_MARK = 70; // Tiempo del contador en el que empieza a verse el sprite de "SINCE 1998"
static constexpr int INIT_FADE_COUNTER_MARK = 300; // Tiempo del contador cuando inicia el fade a negro
static constexpr int END_LOGO_COUNTER_MARK = 400; // Tiempo del contador para terminar el logo
static constexpr int POST_LOGO_DURATION = 20; // Tiempo que dura el logo con el fade al máximo
static constexpr int SPEED = 8; // Velocidad de desplazamiento de cada línea
// --- Objetos y punteros ---
std::shared_ptr<Texture> since_texture_; // Textura con los gráficos "Since 1998"
std::unique_ptr<Sprite> since_sprite_; // Sprite para manejar la since_texture
std::shared_ptr<Texture> jail_texture_; // Textura con los gráficos "JAILGAMES"
std::vector<std::unique_ptr<Sprite>> jail_sprite_; // Vector con los sprites de cada línea que forman el bitmap JAILGAMES
// --- Variables ---
std::vector<Color> color_; // Vector con los colores para el fade
int counter_ = 0; // Contador
Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
SDL_FPoint dest_; // Posición donde dibujar el logo
// --- Métodos internos ---
void update(); // Actualiza las variables
void render(); // Dibuja en pantalla
void checkEvents(); // Comprueba el manejador de eventos
void checkInput(); // Comprueba las entradas
void updateJAILGAMES(); // Gestiona el logo de JAILGAMES
void renderJAILGAMES(); // Renderiza el logo de JAILGAMES
void updateTextureColors(); // Gestiona el color de las texturas
};

369
source/sections/title.cpp Normal file
View File

@@ -0,0 +1,369 @@
#include "title.h"
#include <SDL3/SDL_events.h> // Para SDL_PollEvent, SDL_Event, SDL_KEYDOWN
#include <SDL3/SDL_keycode.h> // Para SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks
#include <stddef.h> // Para size_t
#include <string> // Para char_traits, operator+, basic_string
#include <vector> // Para vector
#include "define_buttons.h" // Para DefineButtons
#include "fade.h" // Para Fade, FadeType
#include "game_logo.h" // Para GameLogo
#include "global_inputs.h" // Para check, update
#include "input.h" // Para Input, InputAction, INPUT_DO_NOT_ALLOW_R...
#include "audio.h" // Para JA_GetMusicState, JA_FadeOutMusic, JA_...
#include "lang.h" // Para getText
#include "global_events.h" // Para handleEvent
#include "notifier.h" // Para Notifier
#include "options.h" // Para OptionsController, Options, options
#include "param.h" // Para Param, param, ParamGame, ParamTitle
#include "resource.h" // Para Resource
#include "screen.h" // Para Screen
#include "section.h" // Para Options, Name, name, AttractMode, options
#include "sprite.h" // Para Sprite
#include "text.h" // Para TEXT_CENTER, TEXT_SHADOW, Text
#include "texture.h" // Para Texture
#include "tiled_bg.h" // Para TiledBG, TiledBGMode
#include "utils.h" // Para Color, Zone, fade_color, no_color, BLOCK
#include "ui/service_menu.h"
// Constructor
Title::Title()
: text_(Resource::get()->getText("smb2")),
fade_(std::make_unique<Fade>()),
tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::RANDOM)),
game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)),
mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))),
define_buttons_(std::make_unique<DefineButtons>()),
num_controllers_(Input::get()->getNumControllers()),
state_(TitleState::LOGO_ANIMATING)
{
// Configura objetos
game_logo_->enable();
mini_logo_sprite_->setX(param.game.game_area.center_x - mini_logo_sprite_->getWidth() / 2);
fade_->setColor(param.fade.color);
fade_->setType(FadeType::RANDOM_SQUARE);
fade_->setPostDuration(param.fade.post_duration);
Resource::get()->getTexture("smb2.gif")->setPalette(1);
// Asigna valores a otras variables
Section::options = Section::Options::TITLE_1;
const bool IS_TITLE_TO_DEMO = (Section::attract_mode == Section::AttractMode::TITLE_TO_DEMO);
next_section_ = IS_TITLE_TO_DEMO ? Section::Name::GAME_DEMO : Section::Name::LOGO;
Section::attract_mode = IS_TITLE_TO_DEMO ? Section::AttractMode::TITLE_TO_LOGO : Section::AttractMode::TITLE_TO_DEMO;
}
// Destructor
Title::~Title()
{
Resource::get()->getTexture("smb2.gif")->setPalette(0);
Audio::get()->stopAllSounds();
}
// Actualiza las variables del objeto
void Title::update()
{
if (SDL_GetTicks() - ticks_ > param.game.speed)
{
// Actualiza el contador de ticks_
ticks_ = SDL_GetTicks();
// Actualiza el fade
updateFade();
// Actualiza el estado
updateState();
// Actualiza el objeto screen
Screen::get()->update();
}
}
// Dibuja el objeto en pantalla
void Title::render()
{
// Prepara para empezar a dibujar en la textura de juego
Screen::get()->start();
// Limpia la pantalla
Screen::get()->clean();
// Dibuja el mosacico de fondo
tiled_bg_->render();
// Dibuja el logo con el título del juego
game_logo_->render();
constexpr Color shadow = Color(0x14, 0x87, 0xc4);
if (state_ != TitleState::LOGO_ANIMATING)
{
// Mini logo
const int pos1 = (param.game.height / 5 * 4) + BLOCK;
const int pos2 = pos1 + mini_logo_sprite_->getHeight() + 3;
mini_logo_sprite_->setY(pos1);
mini_logo_sprite_->render();
// Texto con el copyright
text_->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, pos2, TEXT_COPYRIGHT, 1, NO_TEXT_COLOR, 1, shadow);
}
if (state_ == TitleState::LOGO_FINISHED)
{
// 'PRESS TO PLAY'
if (counter_ % 50 > 14 && !define_buttons_->isEnabled())
{
text_->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, param.title.press_start_position, Lang::getText("[TITLE] PRESS_BUTTON_TO_PLAY"), 1, NO_TEXT_COLOR, 1, shadow);
}
}
if (state_ == TitleState::START_HAS_BEEN_PRESSED)
{
// 'PRESS TO PLAY'
if (counter_ % 10 > 4 && !define_buttons_->isEnabled())
{
text_->writeDX(TEXT_CENTER | TEXT_SHADOW, param.game.game_area.center_x, param.title.press_start_position, Lang::getText("[TITLE] PRESS_BUTTON_TO_PLAY"), 1, NO_TEXT_COLOR, 1, shadow);
}
}
// Define Buttons
define_buttons_->render();
// Fade
fade_->render();
// Vuelca el contenido del renderizador en pantalla
Screen::get()->render();
}
// Comprueba los eventos
void Title::checkEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat == 0)
{
switch (event.key.key)
{
case SDLK_1: // Redefine los botones del mando #0
define_buttons_->enable(0);
break;
case SDLK_2: // Redefine los botones del mando #1
define_buttons_->enable(1);
break;
case SDLK_3: // Intercambia los mandos entre los dos jugadores
swapControllers();
break;
case SDLK_4: // Intercambia la asignación del teclado
swapKeyboard();
break;
case SDLK_5: // Muestra la asignación de mandos y teclado
showControllers();
break;
default:
break;
}
resetCounter();
}
GlobalEvents::check(event);
define_buttons_->checkEvents(event);
}
}
// Comprueba las entradas
void Title::checkInput()
{
Input::get()->update();
if (!ServiceMenu::get()->isEnabled())
{
// Comprueba las entradas solo si no se estan definiendo los botones
if (!define_buttons_->isEnabled())
{
// Comprueba todos los métodos de control
for (const auto &CONTROLLER : Options::controllers)
{
// START
if (Input::get()->checkInput(InputAction::START, INPUT_DO_NOT_ALLOW_REPEAT, CONTROLLER.type, CONTROLLER.index))
{
if ((state_ == TitleState::LOGO_FINISHED || ALLOW_TITLE_ANIMATION_SKIP) && !fade_->isEnabled())
{
Audio::get()->playSound("game_start.wav");
Audio::get()->fadeOutMusic(1500);
switch (CONTROLLER.player_id)
{
case 1:
selection_ = Section::Options::GAME_PLAY_1P;
break;
case 2:
selection_ = Section::Options::GAME_PLAY_2P;
break;
default:
selection_ = Section::Options::TITLE_TIME_OUT;
break;
}
state_ = TitleState::START_HAS_BEEN_PRESSED;
counter_ = 0;
return;
}
}
}
}
}
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
if (!define_buttons_->isEnabled())
{
GlobalInputs::check();
}
}
// Bucle para el titulo del juego
void Title::run()
{
while (Section::name == Section::Name::TITLE)
{
checkInput();
update();
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Reinicia el contador interno
void Title::resetCounter() { counter_ = 0; }
// Intercambia la asignación de mandos a los jugadores
void Title::swapControllers()
{
if (Input::get()->getNumControllers() == 0)
{
return;
}
Options::swapControllers();
showControllers();
}
// Intercambia el teclado de jugador
void Title::swapKeyboard()
{
Options::swapKeyboard();
std::string text = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(Options::getPlayerWhoUsesKeyboard()) + ": " + Lang::getText("[DEFINE_BUTTONS] KEYBOARD");
Notifier::get()->show({text});
}
// Muestra información sobre los controles y los jugadores
void Title::showControllers()
{
// Crea vectores de texto vacíos para un número máximo de mandos
constexpr size_t NUM_CONTROLLERS = 2;
std::vector<std::string> text(NUM_CONTROLLERS);
std::vector<int> player_controller_index(NUM_CONTROLLERS, -1);
// Obtiene de cada jugador el índice del mando que tiene asignado
for (size_t i = 0; i < NUM_CONTROLLERS; ++i)
{
// Ejemplo: el jugador 1 tiene el mando 2
player_controller_index.at(Options::controllers.at(i).player_id - 1) = i;
}
// Genera el texto correspondiente
for (size_t i = 0; i < NUM_CONTROLLERS; ++i)
{
const size_t index = player_controller_index.at(i);
if (Options::controllers.at(index).plugged)
{
text.at(i) = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(i + 1) + ": " + Options::controllers.at(index).name;
}
}
// Muestra la notificación
Notifier::get()->show({text.at(0), text.at(1)});
}
// Actualiza el fade
void Title::updateFade()
{
fade_->update();
if (fade_->hasEnded())
{
if (selection_ == Section::Options::TITLE_TIME_OUT)
{
// El menu ha hecho time out
Section::name = next_section_;
}
else
{
// Se ha pulsado para jugar
Section::name = Section::Name::GAME;
Section::options = selection_;
Audio::get()->stopMusic();
}
}
}
// Actualiza el estado
void Title::updateState()
{
// Establece la lógica según el estado
switch (state_)
{
case TitleState::LOGO_ANIMATING:
{
game_logo_->update();
if (game_logo_->hasFinished())
{
state_ = TitleState::LOGO_FINISHED;
Audio::get()->playMusic("title.ogg");
}
break;
}
case TitleState::LOGO_FINISHED:
{
// El contador solo sube si no estamos definiendo botones
counter_ = define_buttons_->isEnabled() ? 0 : counter_ + 1;
// Actualiza el logo con el título del juego
game_logo_->update();
// Actualiza el mosaico de fondo
tiled_bg_->update();
if (counter_ == param.title.title_duration)
{
// El menu ha hecho time out
fade_->setPostDuration(0);
fade_->activate();
selection_ = Section::Options::TITLE_TIME_OUT;
}
break;
}
case TitleState::START_HAS_BEEN_PRESSED:
{
// Actualiza el logo con el título del juego
game_logo_->update();
// Actualiza el mosaico de fondo
tiled_bg_->update();
if (counter_ == 100)
{
fade_->activate();
}
++counter_;
break;
}
default:
break;
}
}

73
source/sections/title.h Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include <SDL3/SDL_stdinc.h> // Para Uint32
#include <memory> // Para unique_ptr, shared_ptr
#include "section.h" // Para Options
class DefineButtons;
class Fade;
class GameLogo;
class Sprite;
class Text;
class TiledBG;
// Textos
constexpr const char TEXT_COPYRIGHT[] = "@2020,2025 JailDesigner";
// Parámetros
constexpr bool ALLOW_TITLE_ANIMATION_SKIP = false;
/*
Clase que gestiona el estado de título/menú principal del juego.
Responsable de mostrar el logo, el fondo animado y gestionar la entrada para comenzar la partida.
No permite saltar la animación del título salvo que se cambie el define.
*/
// Clase Title
class Title
{
public:
// --- Constructores y destructor ---
Title();
~Title();
// --- Método principal ---
void run(); // Bucle para el título del juego
private:
// --- Enumeraciones ---
enum class TitleState
{
LOGO_ANIMATING, // El logo está animándose
LOGO_FINISHED, // El logo ha terminado de animarse
START_HAS_BEEN_PRESSED, // Se ha pulsado el botón de start
};
// --- Objetos y punteros ---
std::shared_ptr<Text> text_; // Objeto de texto para escribir en pantalla
std::unique_ptr<Fade> fade_; // Fundido en pantalla
std::unique_ptr<TiledBG> tiled_bg_; // Fondo animado de tiles
std::unique_ptr<GameLogo> game_logo_; // Logo del juego
std::unique_ptr<Sprite> mini_logo_sprite_; // Logo JailGames mini
std::unique_ptr<DefineButtons> define_buttons_; // Definición de botones del joystick
// --- Variables de estado ---
int counter_ = 0; // Temporizador para la pantalla de título
Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad
Section::Name next_section_; // Siguiente sección a cargar
Section::Options selection_ = Section::Options::TITLE_TIME_OUT; // Opción elegida en el título
int num_controllers_; // Número de mandos conectados
TitleState state_; // Estado actual de la sección
// --- Métodos internos ---
void update(); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla
void checkEvents(); // Comprueba los eventos
void checkInput(); // Comprueba las entradas
void resetCounter(); // Reinicia el contador interno
void swapControllers(); // Intercambia la asignación de mandos a los jugadores
void swapKeyboard(); // Intercambia el teclado de jugador
void showControllers(); // Muestra información sobre los controles y los jugadores
void updateFade(); // Actualiza el fade
void updateState(); // Actualiza el estado
};