redistribuida la carpeta source

This commit is contained in:
2025-10-26 13:02:45 +01:00
parent 9676e5bc2f
commit 8f49e442de
164 changed files with 303 additions and 30479 deletions

View File

@@ -0,0 +1,103 @@
#include "core/input/global_inputs.hpp"
#include <SDL3/SDL.h>
#include <string> // Para allocator, operator+, char_traits, string
#include <vector> // Para vector
#include "core/input/input.hpp" // Para Input, InputAction, INPUT_DO_NOT_ALLOW_REPEAT
#include "game/gameplay/options.hpp" // Para Options, options, OptionsVideo, Section
#include "core/rendering/screen.hpp" // Para Screen
#include "game/ui/notifier.hpp" // Para Notifier, NotificationText
#include "utils/utils.hpp" // Para stringInVector
namespace globalInputs {
void quit() {
const std::string code = options.section.section == Section::GAME ? "PRESS AGAIN TO RETURN TO MENU" : "PRESS AGAIN TO EXIT";
auto code_found = stringInVector(Notifier::get()->getCodes(), code);
if (code_found) {
// Si la notificación de salir está activa, cambia de sección
options.section.section = options.section.section == Section::GAME ? Section::TITLE : Section::QUIT;
} else {
// Si la notificación de salir no está activa, muestra la notificación
Notifier::get()->show({code}, NotificationText::CENTER, 2000, -1, true, code);
}
}
// Cambia de seccion
void skip_section() {
switch (options.section.section) {
case Section::LOGO:
case Section::LOADING_SCREEN:
case Section::CREDITS:
case Section::DEMO:
case Section::GAME_OVER:
case Section::ENDING:
case Section::ENDING2:
options.section.section = Section::TITLE;
options.section.subsection = Subsection::NONE;
break;
default:
break;
}
}
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
void check() {
if (Input::get()->checkInput(InputAction::EXIT, INPUT_DO_NOT_ALLOW_REPEAT)) {
quit();
}
else if (Input::get()->checkInput(InputAction::ACCEPT, INPUT_DO_NOT_ALLOW_REPEAT)) {
skip_section();
}
else if (Input::get()->checkInput(InputAction::TOGGLE_BORDER, INPUT_DO_NOT_ALLOW_REPEAT)) {
Screen::get()->toggleBorder();
Notifier::get()->show({"BORDER " + std::string(options.video.border.enabled ? "ENABLED" : "DISABLED")}, NotificationText::CENTER);
}
else if (Input::get()->checkInput(InputAction::TOGGLE_VIDEOMODE, INPUT_DO_NOT_ALLOW_REPEAT)) {
Screen::get()->toggleVideoMode();
Notifier::get()->show({"FULLSCREEN " + std::string(options.video.fullscreen == 0 ? "DISABLED" : "ENABLED")}, NotificationText::CENTER);
}
else if (Input::get()->checkInput(InputAction::WINDOW_DEC_ZOOM, INPUT_DO_NOT_ALLOW_REPEAT)) {
if (Screen::get()->decWindowZoom()) {
Notifier::get()->show({"WINDOW ZOOM x" + std::to_string(options.window.zoom)}, NotificationText::CENTER);
}
}
else if (Input::get()->checkInput(InputAction::WINDOW_INC_ZOOM, INPUT_DO_NOT_ALLOW_REPEAT)) {
if (Screen::get()->incWindowZoom()) {
Notifier::get()->show({"WINDOW ZOOM x" + std::to_string(options.window.zoom)}, NotificationText::CENTER);
}
}
else if (Input::get()->checkInput(InputAction::TOGGLE_SHADERS, INPUT_DO_NOT_ALLOW_REPEAT)) {
Screen::get()->toggleShaders();
Notifier::get()->show({"SHADERS " + std::string(options.video.shaders ? "ENABLED" : "DISABLED")}, NotificationText::CENTER);
}
else if (Input::get()->checkInput(InputAction::NEXT_PALETTE, INPUT_DO_NOT_ALLOW_REPEAT)) {
Screen::get()->nextPalette();
Notifier::get()->show({"PALETTE " + options.video.palette}, NotificationText::CENTER);
}
else if (Input::get()->checkInput(InputAction::PREVIOUS_PALETTE, INPUT_DO_NOT_ALLOW_REPEAT)) {
Screen::get()->previousPalette();
Notifier::get()->show({"PALETTE " + options.video.palette}, NotificationText::CENTER);
}
else if (Input::get()->checkInput(InputAction::TOGGLE_INTEGER_SCALE, INPUT_DO_NOT_ALLOW_REPEAT)) {
Screen::get()->toggleIntegerScale();
Screen::get()->setVideoMode(options.video.fullscreen);
Notifier::get()->show({"INTEGER SCALE " + std::string(options.video.integer_scale ? "ENABLED" : "DISABLED")}, NotificationText::CENTER);
}
else if (Input::get()->checkInput(InputAction::SHOW_DEBUG_INFO, INPUT_DO_NOT_ALLOW_REPEAT)) {
Screen::get()->toggleDebugInfo();
}
}
} // namespace globalInputs

View File

@@ -0,0 +1,7 @@
#pragma once
namespace globalInputs
{
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
void check();
}

289
source/core/input/input.cpp Normal file
View File

@@ -0,0 +1,289 @@
#include "core/input/input.hpp"
#include <SDL3/SDL.h>
#include <algorithm> // Para find
#include <iostream> // Para basic_ostream, operator<<, cout, endl
#include <iterator> // Para distance
#include <unordered_map> // Para unordered_map, operator==, _Node_cons...
#include <utility> // Para pair
// [SINGLETON]
Input* Input::input_ = nullptr;
// [SINGLETON] Crearemos el objeto con esta función estática
void Input::init(const std::string& game_controller_db_path) {
Input::input_ = new Input(game_controller_db_path);
}
// [SINGLETON] Destruiremos el objeto con esta función estática
void Input::destroy() {
delete Input::input_;
}
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
Input* Input::get() {
return Input::input_;
}
// Constructor
Input::Input(const std::string& game_controller_db_path)
: game_controller_db_path_(game_controller_db_path) {
// Busca si hay mandos conectados
discoverGameControllers();
// Inicializa los vectores
key_bindings_.resize(static_cast<int>(InputAction::SIZE), KeyBindings());
controller_bindings_.resize(num_gamepads_, std::vector<ControllerBindings>(static_cast<int>(InputAction::SIZE), ControllerBindings()));
// Listado de los inputs para jugar que utilizan botones, ni palancas ni crucetas
button_inputs_ = {InputAction::JUMP};
}
// Asigna inputs a teclas
void Input::bindKey(InputAction input, SDL_Scancode code) {
key_bindings_.at(static_cast<int>(input)).scancode = code;
}
// Asigna inputs a botones del mando
void Input::bindGameControllerButton(int controller_index, InputAction input, SDL_GamepadButton button) {
if (controller_index < num_gamepads_) {
controller_bindings_.at(controller_index).at(static_cast<int>(input)).button = button;
}
}
// Asigna inputs a botones del mando
void Input::bindGameControllerButton(int controller_index, InputAction input_target, InputAction input_source) {
if (controller_index < num_gamepads_) {
controller_bindings_.at(controller_index).at(static_cast<int>(input_target)).button = controller_bindings_.at(controller_index).at(static_cast<int>(input_source)).button;
}
}
// Comprueba si un input esta activo
bool Input::checkInput(InputAction input, bool repeat, InputDeviceToUse device, int controller_index) {
bool success_keyboard = false;
bool success_controller = false;
const int input_index = static_cast<int>(input);
if (device == InputDeviceToUse::KEYBOARD || device == InputDeviceToUse::ANY) {
auto key_states = SDL_GetKeyboardState(nullptr);
if (repeat) {
success_keyboard = key_states[key_bindings_[input_index].scancode] != 0;
} else {
if (!key_bindings_[input_index].active) {
if (key_states[key_bindings_[input_index].scancode] != 0) {
key_bindings_[input_index].active = true;
success_keyboard = true;
} else {
success_keyboard = false;
}
} else {
if (key_states[key_bindings_[input_index].scancode] == 0) {
key_bindings_[input_index].active = false;
}
success_keyboard = false;
}
}
}
if (gameControllerFound() && controller_index >= 0 && controller_index < num_gamepads_) {
if ((device == InputDeviceToUse::CONTROLLER) || (device == InputDeviceToUse::ANY)) {
success_controller = checkAxisInput(input, controller_index, repeat);
if (!success_controller) {
if (repeat) {
success_controller = SDL_GetGamepadButton(connected_controllers_.at(controller_index), controller_bindings_.at(controller_index).at(input_index).button) != 0;
} else {
if (!controller_bindings_.at(controller_index).at(input_index).active) {
if (SDL_GetGamepadButton(connected_controllers_.at(controller_index), controller_bindings_.at(controller_index).at(input_index).button) != 0) {
controller_bindings_.at(controller_index).at(input_index).active = true;
success_controller = true;
} else {
success_controller = false;
}
} else {
if (SDL_GetGamepadButton(connected_controllers_.at(controller_index), controller_bindings_.at(controller_index).at(input_index).button) == 0) {
controller_bindings_.at(controller_index).at(input_index).active = false;
}
success_controller = false;
}
}
}
}
}
return (success_keyboard || success_controller);
}
// Comprueba si hay almenos un input activo
bool Input::checkAnyInput(InputDeviceToUse device, int controller_index) {
if (device == InputDeviceToUse::KEYBOARD || device == InputDeviceToUse::ANY) {
const bool *key_states = SDL_GetKeyboardState(nullptr);
for (int i = 0; i < (int)key_bindings_.size(); ++i) {
if (key_states[key_bindings_[i].scancode] != 0 && !key_bindings_[i].active) {
key_bindings_[i].active = true;
return true;
}
}
}
if (gameControllerFound()) {
if (device == InputDeviceToUse::CONTROLLER || device == InputDeviceToUse::ANY) {
for (int i = 0; i < (int)controller_bindings_.size(); ++i) {
if (SDL_GetGamepadButton(connected_controllers_[controller_index], controller_bindings_[controller_index][i].button) != 0 && !controller_bindings_[controller_index][i].active) {
controller_bindings_[controller_index][i].active = true;
return true;
}
}
}
}
return false;
}
// Busca si hay mandos conectados
bool Input::discoverGameControllers() {
bool found = false;
// Asegúrate de que el subsistema de gamepads está inicializado
if (SDL_WasInit(SDL_INIT_GAMEPAD) != 1) {
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
}
// Carga el mapping de mandos desde archivo
if (SDL_AddGamepadMappingsFromFile(game_controller_db_path_.c_str()) < 0) {
std::cout << "Error, could not load " << game_controller_db_path_.c_str()
<< " file: " << SDL_GetError() << std::endl;
}
// En SDL3 ya no existe SDL_NumJoysticks()
// Ahora se obtiene un array dinámico de IDs
int num_joysticks = 0;
SDL_JoystickID* joystick_ids = SDL_GetJoysticks(&num_joysticks);
num_joysticks_ = num_joysticks;
num_gamepads_ = 0;
joysticks_.clear();
// Recorremos todos los joysticks detectados
for (int i = 0; i < num_joysticks_; ++i) {
SDL_Joystick* joy = SDL_OpenJoystick(joystick_ids[i]);
joysticks_.push_back(joy);
if (SDL_IsGamepad(joystick_ids[i])) {
num_gamepads_++;
}
}
std::cout << "\n** LOOKING FOR GAME CONTROLLERS" << std::endl;
if (num_joysticks_ != num_gamepads_) {
std::cout << "Joysticks found: " << num_joysticks_ << std::endl;
std::cout << "Gamepads found : " << num_gamepads_ << std::endl;
} else {
std::cout << "Gamepads found: " << num_gamepads_ << std::endl;
}
if (num_gamepads_ > 0) {
found = true;
for (int i = 0; i < num_joysticks_; i++) {
if (SDL_IsGamepad(joystick_ids[i])) {
SDL_Gamepad* pad = SDL_OpenGamepad(joystick_ids[i]);
if (pad && SDL_GamepadConnected(pad)) {
connected_controllers_.push_back(pad);
const char* name = SDL_GetGamepadName(pad);
std::cout << "#" << i << ": " << (name ? name : "Unknown") << std::endl;
controller_names_.push_back(name ? name : "Unknown");
} else {
std::cout << "SDL_GetError() = " << SDL_GetError() << std::endl;
}
}
}
// En SDL3 ya no hace falta SDL_GameControllerEventState()
// Los eventos de gamepad están siempre habilitados
}
SDL_free(joystick_ids);
std::cout << "\n** FINISHED LOOKING FOR GAME CONTROLLERS" << std::endl;
return found;
}
// Comprueba si hay algun mando conectado
bool Input::gameControllerFound() { return num_gamepads_ > 0 ? true : false; }
// Obten el nombre de un mando de juego
std::string Input::getControllerName(int controller_index) const { return num_gamepads_ > 0 ? controller_names_.at(controller_index) : std::string(); }
// Obten el número de mandos conectados
int Input::getNumControllers() const { return num_gamepads_; }
// Obtiene el indice del controlador a partir de un event.id
int Input::getJoyIndex(SDL_JoystickID id) const {
for (int i = 0; i < num_joysticks_; ++i) {
if (SDL_GetJoystickID(joysticks_[i]) == id) {
return i;
}
}
return -1;
}
// Obtiene el SDL_GamepadButton asignado a un input
SDL_GamepadButton Input::getControllerBinding(int controller_index, InputAction input) const {
return controller_bindings_[controller_index][static_cast<int>(input)].button;
}
// Obtiene el indice a partir del nombre del mando
int Input::getIndexByName(const std::string& name) const {
auto it = std::find(controller_names_.begin(), controller_names_.end(), name);
return it != controller_names_.end() ? std::distance(controller_names_.begin(), it) : -1;
}
// Comprueba el eje del mando
bool Input::checkAxisInput(InputAction input, int controller_index, bool repeat) {
// Umbral para considerar el eje como activo
const Sint16 threshold = 30000;
bool axis_active_now = false;
switch (input) {
case InputAction::LEFT:
axis_active_now = SDL_GetGamepadAxis(connected_controllers_[controller_index], SDL_GAMEPAD_AXIS_LEFTX) < -threshold;
break;
case InputAction::RIGHT:
axis_active_now = SDL_GetGamepadAxis(connected_controllers_[controller_index], SDL_GAMEPAD_AXIS_LEFTX) > threshold;
break;
case InputAction::UP:
axis_active_now = SDL_GetGamepadAxis(connected_controllers_[controller_index], SDL_GAMEPAD_AXIS_LEFTY) < -threshold;
break;
case InputAction::DOWN:
axis_active_now = SDL_GetGamepadAxis(connected_controllers_[controller_index], SDL_GAMEPAD_AXIS_LEFTY) > threshold;
break;
default:
return false;
}
// Referencia al binding correspondiente
auto& binding = controller_bindings_.at(controller_index).at(static_cast<int>(input));
if (repeat) {
// Si se permite repetir, simplemente devolvemos el estado actual
return axis_active_now;
} else {
// Si no se permite repetir, aplicamos la lógica de transición
if (axis_active_now && !binding.axis_active) {
// Transición de inactivo a activo
binding.axis_active = true;
return true;
} else if (!axis_active_now && binding.axis_active) {
// Transición de activo a inactivo
binding.axis_active = false;
}
// Mantener el estado actual
return false;
}
}

138
source/core/input/input.hpp Normal file
View File

@@ -0,0 +1,138 @@
#pragma once
#include <SDL3/SDL.h>
#include <string> // Para string, basic_string
#include <vector> // Para vector
// Definiciones de repetición
constexpr bool INPUT_ALLOW_REPEAT = true;
constexpr bool INPUT_DO_NOT_ALLOW_REPEAT = false;
// Tipos de entrada
enum class InputDeviceToUse : int {
KEYBOARD = 0,
CONTROLLER = 1,
ANY = 2,
};
enum class InputAction {
// Inputs obligatorios
UP,
DOWN,
LEFT,
RIGHT,
PAUSE,
EXIT,
ACCEPT,
CANCEL,
// Inputs personalizados
JUMP,
WINDOW_INC_ZOOM,
WINDOW_DEC_ZOOM,
TOGGLE_VIDEOMODE,
TOGGLE_INTEGER_SCALE,
TOGGLE_BORDER,
TOGGLE_MUSIC,
NEXT_PALETTE,
PREVIOUS_PALETTE,
TOGGLE_SHADERS,
SHOW_DEBUG_INFO,
// Input obligatorio
NONE,
SIZE
};
class Input {
private:
// [SINGLETON] Objeto privado
static Input* input_;
struct KeyBindings {
Uint8 scancode; // Scancode asociado
bool active; // Indica si está activo
// Constructor
explicit KeyBindings(Uint8 sc = 0, bool act = false)
: scancode(sc),
active(act) {}
};
struct ControllerBindings {
SDL_GamepadButton button; // GameControllerButton asociado
bool active; // Indica si está activo
bool axis_active; // Estado del eje
// Constructor
explicit ControllerBindings(SDL_GamepadButton btn = SDL_GAMEPAD_BUTTON_INVALID, bool act = false, bool axis_act = false)
: button(btn),
active(act),
axis_active(axis_act) {}
};
// Variables
std::vector<SDL_Gamepad*> connected_controllers_; // Vector con todos los mandos conectados
std::vector<SDL_Joystick*> joysticks_; // Vector con todos los joysticks conectados
std::vector<KeyBindings> key_bindings_; // Vector con las teclas asociadas a los inputs predefinidos
std::vector<std::vector<ControllerBindings>> controller_bindings_; // Vector con los botones asociadas a los inputs predefinidos para cada mando
std::vector<std::string> controller_names_; // Vector con los nombres de los mandos
std::vector<InputAction> button_inputs_; // Inputs asignados al jugador y a botones, excluyendo direcciones
int num_joysticks_ = 0; // Número de joysticks conectados
int num_gamepads_ = 0; // Número de mandos conectados
std::string game_controller_db_path_; // Ruta al archivo gamecontrollerdb.txt
// Comprueba el eje del mando
bool checkAxisInput(InputAction input, int controller_index, bool repeat);
// Constructor
explicit Input(const std::string& game_controller_db_path);
// Destructor
~Input() = default;
public:
// [SINGLETON] Crearemos el objeto con esta función estática
static void init(const std::string& game_controller_db_path);
// [SINGLETON] Destruiremos el objeto con esta función estática
static void destroy();
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
static Input* get();
// Asigna inputs a teclas
void bindKey(InputAction input, SDL_Scancode code);
// Asigna inputs a botones del mando
void bindGameControllerButton(int controller_index, InputAction input, SDL_GamepadButton button);
void bindGameControllerButton(int controller_index, InputAction inputTarget, InputAction inputSource);
// Comprueba si un input esta activo
bool checkInput(InputAction input, bool repeat = true, InputDeviceToUse device = InputDeviceToUse::ANY, int controller_index = 0);
// Comprueba si hay almenos un input activo
bool checkAnyInput(InputDeviceToUse device = InputDeviceToUse::ANY, int controller_index = 0);
// Busca si hay mandos conectados
bool discoverGameControllers();
// Comprueba si hay algun mando conectado
bool gameControllerFound();
// Obten el número de mandos conectados
int getNumControllers() const;
// Obten el nombre de un mando de juego
std::string getControllerName(int controller_index) const;
// Obtiene el indice del controlador a partir de un event.id
int getJoyIndex(SDL_JoystickID id) const;
// Obtiene el SDL_GamepadButton asignado a un input
SDL_GamepadButton getControllerBinding(int controller_index, InputAction input) const;
// Obtiene el indice a partir del nombre del mando
int getIndexByName(const std::string& name) const;
};

View File

@@ -0,0 +1,26 @@
#include "core/input/mouse.hpp"
namespace Mouse {
Uint32 cursor_hide_time = 3000; // Tiempo en milisegundos para ocultar el cursor
Uint32 last_mouse_move_time = 0; // Última vez que el ratón se movió
bool cursor_visible = true; // Estado del cursor
void handleEvent(const SDL_Event& event) {
if (event.type == SDL_EVENT_MOUSE_MOTION) {
last_mouse_move_time = SDL_GetTicks();
if (!cursor_visible) {
SDL_ShowCursor();
cursor_visible = true;
}
}
}
void updateCursorVisibility() {
Uint32 current_time = SDL_GetTicks();
if (cursor_visible && (current_time - last_mouse_move_time > cursor_hide_time)) {
SDL_HideCursor();
cursor_visible = false;
}
}
} // namespace Mouse

View File

@@ -0,0 +1,12 @@
#pragma once
#include <SDL3/SDL.h>
namespace Mouse {
extern Uint32 cursor_hide_time; // Tiempo en milisegundos para ocultar el cursor
extern Uint32 last_mouse_move_time; // Última vez que el ratón se movió
extern bool cursor_visible; // Estado del cursor
void handleEvent(const SDL_Event& event);
void updateCursorVisibility();
} // namespace Mouse

View File

@@ -0,0 +1,316 @@
#include "core/rendering/gif.hpp"
#include <iostream> // Para std::cout
#include <cstring> // Para memcpy, size_t
#include <stdexcept> // Para runtime_error
#include <string> // Para allocator, char_traits, operator==, basic_string
namespace GIF
{
// Función inline para reemplazar el macro READ.
// Actualiza el puntero 'buffer' tras copiar 'size' bytes a 'dst'.
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size)
{
std::memcpy(dst, buffer, size);
buffer += size;
}
void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out)
{
// Verifica que el code_length tenga un rango razonable.
if (code_length < 2 || code_length > 12)
{
throw std::runtime_error("Invalid LZW code length");
}
int i, bit;
int prev = -1;
std::vector<DictionaryEntry> dictionary;
int dictionary_ind;
unsigned int mask = 0x01;
int reset_code_length = code_length;
int clear_code = 1 << code_length;
int stop_code = clear_code + 1;
int match_len = 0;
// Inicializamos el diccionario con el tamaño correspondiente.
dictionary.resize(1 << (code_length + 1));
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++)
{
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
dictionary[dictionary_ind].prev = -1;
dictionary[dictionary_ind].len = 1;
}
dictionary_ind += 2; // Reservamos espacio para clear y stop codes
// Bucle principal: procesar el stream comprimido.
while (input_length > 0)
{
int code = 0;
// Lee (code_length + 1) bits para formar el código.
for (i = 0; i < (code_length + 1); i++)
{
if (input_length <= 0)
{
throw std::runtime_error("Unexpected end of input in decompress");
}
bit = ((*input & mask) != 0) ? 1 : 0;
mask <<= 1;
if (mask == 0x100)
{
mask = 0x01;
input++;
input_length--;
}
code |= (bit << i);
}
if (code == clear_code)
{
// Reinicia el diccionario.
code_length = reset_code_length;
dictionary.resize(1 << (code_length + 1));
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++)
{
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
dictionary[dictionary_ind].prev = -1;
dictionary[dictionary_ind].len = 1;
}
dictionary_ind += 2;
prev = -1;
continue;
}
else if (code == stop_code)
{
break;
}
if (prev > -1 && code_length < 12)
{
if (code > dictionary_ind)
{
std::cerr << "code = " << std::hex << code
<< ", but dictionary_ind = " << dictionary_ind << std::endl;
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
}
int ptr;
if (code == dictionary_ind)
{
ptr = prev;
while (dictionary[ptr].prev != -1)
ptr = dictionary[ptr].prev;
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
}
else
{
ptr = code;
while (dictionary[ptr].prev != -1)
ptr = dictionary[ptr].prev;
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
}
dictionary[dictionary_ind].prev = prev;
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
dictionary_ind++;
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11))
{
code_length++;
dictionary.resize(1 << (code_length + 1));
}
}
prev = code;
// Verifica que 'code' sea un índice válido antes de usarlo.
if (code < 0 || static_cast<size_t>(code) >= dictionary.size())
{
std::cerr << "Invalid LZW code " << code
<< ", dictionary size " << dictionary.size() << std::endl;
throw std::runtime_error("LZW error: invalid code encountered");
}
int curCode = code; // Variable temporal para recorrer la cadena.
match_len = dictionary[curCode].len;
while (curCode != -1)
{
// Se asume que dictionary[curCode].len > 0.
out[dictionary[curCode].len - 1] = dictionary[curCode].byte;
if (dictionary[curCode].prev == curCode)
{
std::cerr << "Internal error; self-reference detected." << std::endl;
throw std::runtime_error("Internal error in decompress: self-reference");
}
curCode = dictionary[curCode].prev;
}
out += match_len;
}
}
std::vector<uint8_t> Gif::readSubBlocks(const uint8_t *&buffer)
{
std::vector<uint8_t> data;
uint8_t block_size = *buffer;
buffer++;
while (block_size != 0)
{
data.insert(data.end(), buffer, buffer + block_size);
buffer += block_size;
block_size = *buffer;
buffer++;
}
return data;
}
std::vector<uint8_t> Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits)
{
ImageDescriptor image_descriptor;
// Lee 9 bytes para el image descriptor.
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
uint8_t lzw_code_size;
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
return uncompressed_data;
}
std::vector<uint32_t> Gif::loadPalette(const uint8_t *buffer)
{
uint8_t header[6];
std::memcpy(header, buffer, 6);
buffer += 6;
ScreenDescriptor screen_descriptor;
std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor));
buffer += sizeof(ScreenDescriptor);
std::vector<uint32_t> global_color_table;
if (screen_descriptor.fields & 0x80)
{
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
global_color_table.resize(global_color_table_size);
for (int i = 0; i < global_color_table_size; ++i)
{
uint8_t r = buffer[0];
uint8_t g = buffer[1];
uint8_t b = buffer[2];
global_color_table[i] = (r << 16) | (g << 8) | b;
buffer += 3;
}
}
return global_color_table;
}
std::vector<uint8_t> Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h)
{
// Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a")
uint8_t header[6];
std::memcpy(header, buffer, 6);
buffer += 6;
// Opcional: Validar header
std::string headerStr(reinterpret_cast<char *>(header), 6);
if (headerStr != "GIF87a" && headerStr != "GIF89a")
{
throw std::runtime_error("Formato de archivo GIF inválido.");
}
// Leer el Screen Descriptor (7 bytes, empaquetado sin padding)
ScreenDescriptor screen_descriptor;
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
// Asigna ancho y alto
w = screen_descriptor.width;
h = screen_descriptor.height;
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
std::vector<RGB> global_color_table;
if (screen_descriptor.fields & 0x80)
{
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
global_color_table.resize(global_color_table_size);
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
buffer += 3 * global_color_table_size;
}
// Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B
uint8_t block_type = *buffer++;
while (block_type != TRAILER)
{
if (block_type == EXTENSION_INTRODUCER) // 0x21
{
// Se lee la etiqueta de extensión, la cual indica el tipo de extensión.
uint8_t extension_label = *buffer++;
switch (extension_label)
{
case GRAPHIC_CONTROL: // 0xF9
{
// Procesar Graphic Control Extension:
uint8_t blockSize = *buffer++; // Normalmente, blockSize == 4
buffer += blockSize; // Saltamos los 4 bytes del bloque fijo
// Saltar los sub-bloques
uint8_t subBlockSize = *buffer++;
while (subBlockSize != 0)
{
buffer += subBlockSize;
subBlockSize = *buffer++;
}
break;
}
case APPLICATION_EXTENSION: // 0xFF
case COMMENT_EXTENSION: // 0xFE
case PLAINTEXT_EXTENSION: // 0x01
{
// Para estas extensiones, saltamos el bloque fijo y los sub-bloques.
uint8_t blockSize = *buffer++;
buffer += blockSize;
uint8_t subBlockSize = *buffer++;
while (subBlockSize != 0)
{
buffer += subBlockSize;
subBlockSize = *buffer++;
}
break;
}
default:
{
// Si la etiqueta de extensión es desconocida, saltarla también:
uint8_t blockSize = *buffer++;
buffer += blockSize;
uint8_t subBlockSize = *buffer++;
while (subBlockSize != 0)
{
buffer += subBlockSize;
subBlockSize = *buffer++;
}
break;
}
}
}
else if (block_type == IMAGE_DESCRIPTOR)
{
// Procesar el Image Descriptor y retornar los datos de imagen
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
}
else
{
std::cerr << "Unrecognized block type " << std::hex << static_cast<int>(block_type) << std::endl;
return std::vector<uint8_t>{};
}
block_type = *buffer++;
}
return std::vector<uint8_t>{};
}
std::vector<uint8_t> Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h)
{
return processGifStream(buffer, w, h);
}
} // namespace GIF

View File

@@ -0,0 +1,102 @@
#pragma once
#include <cstdint> // Para uint8_t, uint16_t, uint32_t
#include <vector> // Para vector
namespace GIF
{
// Constantes definidas con constexpr, en lugar de macros
constexpr uint8_t EXTENSION_INTRODUCER = 0x21;
constexpr uint8_t IMAGE_DESCRIPTOR = 0x2C;
constexpr uint8_t TRAILER = 0x3B;
constexpr uint8_t GRAPHIC_CONTROL = 0xF9;
constexpr uint8_t APPLICATION_EXTENSION = 0xFF;
constexpr uint8_t COMMENT_EXTENSION = 0xFE;
constexpr uint8_t PLAINTEXT_EXTENSION = 0x01;
#pragma pack(push, 1)
struct ScreenDescriptor
{
uint16_t width;
uint16_t height;
uint8_t fields;
uint8_t background_color_index;
uint8_t pixel_aspect_ratio;
};
struct RGB
{
uint8_t r, g, b;
};
struct ImageDescriptor
{
uint16_t image_left_position;
uint16_t image_top_position;
uint16_t image_width;
uint16_t image_height;
uint8_t fields;
};
#pragma pack(pop)
struct DictionaryEntry
{
uint8_t byte;
int prev;
int len;
};
struct Extension
{
uint8_t extension_code;
uint8_t block_size;
};
struct GraphicControlExtension
{
uint8_t fields;
uint16_t delay_time;
uint8_t transparent_color_index;
};
struct ApplicationExtension
{
uint8_t application_id[8];
uint8_t version[3];
};
struct PlaintextExtension
{
uint16_t left, top, width, height;
uint8_t cell_width, cell_height;
uint8_t foreground_color, background_color;
};
class Gif
{
public:
// Descompone (uncompress) el bloque comprimido usando LZW.
// Este método puede lanzar std::runtime_error en caso de error.
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out);
// Carga la paleta (global color table) a partir de un buffer,
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
std::vector<uint32_t> loadPalette(const uint8_t *buffer);
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
// asigna el ancho y alto mediante referencias.
std::vector<uint8_t> loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h);
private:
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
std::vector<uint8_t> readSubBlocks(const uint8_t *&buffer);
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
std::vector<uint8_t> processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits);
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
std::vector<uint8_t> processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h);
};
} // namespace GIF

View File

@@ -0,0 +1,462 @@
#include "core/rendering/opengl/opengl_shader.hpp"
#include <SDL3/SDL.h>
#include <cstring>
#include <stdexcept>
#include <vector>
namespace Rendering {
OpenGLShader::~OpenGLShader() {
cleanup();
}
#ifndef __APPLE__
bool OpenGLShader::initGLExtensions() {
glCreateShader = (PFNGLCREATESHADERPROC)SDL_GL_GetProcAddress("glCreateShader");
glShaderSource = (PFNGLSHADERSOURCEPROC)SDL_GL_GetProcAddress("glShaderSource");
glCompileShader = (PFNGLCOMPILESHADERPROC)SDL_GL_GetProcAddress("glCompileShader");
glGetShaderiv = (PFNGLGETSHADERIVPROC)SDL_GL_GetProcAddress("glGetShaderiv");
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)SDL_GL_GetProcAddress("glGetShaderInfoLog");
glDeleteShader = (PFNGLDELETESHADERPROC)SDL_GL_GetProcAddress("glDeleteShader");
glAttachShader = (PFNGLATTACHSHADERPROC)SDL_GL_GetProcAddress("glAttachShader");
glCreateProgram = (PFNGLCREATEPROGRAMPROC)SDL_GL_GetProcAddress("glCreateProgram");
glLinkProgram = (PFNGLLINKPROGRAMPROC)SDL_GL_GetProcAddress("glLinkProgram");
glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)SDL_GL_GetProcAddress("glValidateProgram");
glGetProgramiv = (PFNGLGETPROGRAMIVPROC)SDL_GL_GetProcAddress("glGetProgramiv");
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)SDL_GL_GetProcAddress("glGetProgramInfoLog");
glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram");
glDeleteProgram = (PFNGLDELETEPROGRAMPROC)SDL_GL_GetProcAddress("glDeleteProgram");
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)SDL_GL_GetProcAddress("glGetUniformLocation");
glUniform2f = (PFNGLUNIFORM2FPROC)SDL_GL_GetProcAddress("glUniform2f");
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glGenVertexArrays");
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)SDL_GL_GetProcAddress("glBindVertexArray");
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glDeleteVertexArrays");
glGenBuffers = (PFNGLGENBUFFERSPROC)SDL_GL_GetProcAddress("glGenBuffers");
glBindBuffer = (PFNGLBINDBUFFERPROC)SDL_GL_GetProcAddress("glBindBuffer");
glBufferData = (PFNGLBUFFERDATAPROC)SDL_GL_GetProcAddress("glBufferData");
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)SDL_GL_GetProcAddress("glDeleteBuffers");
glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)SDL_GL_GetProcAddress("glVertexAttribPointer");
glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)SDL_GL_GetProcAddress("glEnableVertexAttribArray");
return glCreateShader && glShaderSource && glCompileShader && glGetShaderiv &&
glGetShaderInfoLog && glDeleteShader && glAttachShader && glCreateProgram &&
glLinkProgram && glValidateProgram && glGetProgramiv && glGetProgramInfoLog &&
glUseProgram && glDeleteProgram && glGetUniformLocation && glUniform2f &&
glGenVertexArrays && glBindVertexArray && glDeleteVertexArrays &&
glGenBuffers && glBindBuffer && glBufferData && glDeleteBuffers &&
glVertexAttribPointer && glEnableVertexAttribArray;
}
#endif
void OpenGLShader::checkGLError(const char* operation) {
GLenum error = glGetError();
if (error != GL_NO_ERROR) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error OpenGL en %s: 0x%x", operation, error);
}
}
GLuint OpenGLShader::compileShader(const std::string& source, GLenum shader_type) {
if (source.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"ERROR: El código fuente del shader está vacío");
return 0;
}
GLuint shader_id = glCreateShader(shader_type);
if (shader_id == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al crear shader");
checkGLError("glCreateShader");
return 0;
}
const char* sources[1] = {source.c_str()};
glShaderSource(shader_id, 1, sources, nullptr);
checkGLError("glShaderSource");
glCompileShader(shader_id);
checkGLError("glCompileShader");
// Verificar compilación
GLint compiled = GL_FALSE;
glGetShaderiv(shader_id, GL_COMPILE_STATUS, &compiled);
if (compiled != GL_TRUE) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error en compilación del shader");
GLint log_length;
glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_length);
if (log_length > 0) {
std::vector<char> log(log_length);
glGetShaderInfoLog(shader_id, log_length, &log_length, log.data());
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Log de compilación: %s", log.data());
}
glDeleteShader(shader_id);
return 0;
}
return shader_id;
}
GLuint OpenGLShader::linkProgram(GLuint vertex_shader, GLuint fragment_shader) {
GLuint program = glCreateProgram();
if (program == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error al crear programa de shaders");
return 0;
}
glAttachShader(program, vertex_shader);
checkGLError("glAttachShader(vertex)");
glAttachShader(program, fragment_shader);
checkGLError("glAttachShader(fragment)");
glLinkProgram(program);
checkGLError("glLinkProgram");
// Verificar enlace
GLint linked = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (linked != GL_TRUE) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error al enlazar programa");
GLint log_length;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
if (log_length > 0) {
std::vector<char> log(log_length);
glGetProgramInfoLog(program, log_length, &log_length, log.data());
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Log de enlace: %s", log.data());
}
glDeleteProgram(program);
return 0;
}
glValidateProgram(program);
checkGLError("glValidateProgram");
return program;
}
void OpenGLShader::createQuadGeometry() {
// Datos del quad: posición (x, y) + coordenadas de textura (u, v)
// Formato: x, y, u, v
float vertices[] = {
// Posición // TexCoords
-1.0f, -1.0f, 0.0f, 0.0f, // Inferior izquierda
1.0f, -1.0f, 1.0f, 0.0f, // Inferior derecha
1.0f, 1.0f, 1.0f, 1.0f, // Superior derecha
-1.0f, 1.0f, 0.0f, 1.0f // Superior izquierda
};
// Índices para dibujar el quad con dos triángulos
unsigned int indices[] = {
0, 1, 2, // Primer triángulo
2, 3, 0 // Segundo triángulo
};
// Generar y configurar VAO
glGenVertexArrays(1, &vao_);
glBindVertexArray(vao_);
checkGLError("glBindVertexArray");
// Generar y configurar VBO
glGenBuffers(1, &vbo_);
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
checkGLError("glBufferData(VBO)");
// Generar y configurar EBO
glGenBuffers(1, &ebo_);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
checkGLError("glBufferData(EBO)");
// Atributo 0: Posición (2 floats)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
checkGLError("glVertexAttribPointer(position)");
// Atributo 1: Coordenadas de textura (2 floats)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
checkGLError("glVertexAttribPointer(texcoord)");
// Desvincular
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
GLuint OpenGLShader::getTextureID(SDL_Texture* texture) {
if (!texture) return 1;
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
GLuint texture_id = 0;
// Intentar obtener ID de textura OpenGL
texture_id = (GLuint)(uintptr_t)SDL_GetPointerProperty(props, "SDL.texture.opengl.texture", nullptr);
if (texture_id == 0) {
texture_id = (GLuint)(uintptr_t)SDL_GetPointerProperty(props, "texture.opengl.texture", nullptr);
}
if (texture_id == 0) {
texture_id = (GLuint)SDL_GetNumberProperty(props, "SDL.texture.opengl.texture", 1);
}
if (texture_id == 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No se pudo obtener ID de textura OpenGL, usando 1 por defecto");
texture_id = 1;
}
return texture_id;
}
bool OpenGLShader::init(SDL_Window* window,
SDL_Texture* texture,
const std::string& vertex_source,
const std::string& fragment_source) {
window_ = window;
back_buffer_ = texture;
renderer_ = SDL_GetRenderer(window);
if (!renderer_) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error: No se pudo obtener el renderer");
return false;
}
// Obtener tamaños
SDL_GetWindowSize(window_, &window_width_, &window_height_);
SDL_GetTextureSize(back_buffer_, &texture_width_, &texture_height_);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Inicializando shaders: ventana=%dx%d, textura=%.0fx%.0f",
window_width_, window_height_, texture_width_, texture_height_);
// Verificar que es OpenGL
const char* renderer_name = SDL_GetRendererName(renderer_);
if (!renderer_name || strncmp(renderer_name, "opengl", 6) != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Renderer no es OpenGL: %s", renderer_name ? renderer_name : "unknown");
return false;
}
#ifndef __APPLE__
// Inicializar extensiones OpenGL en Windows/Linux
if (!initGLExtensions()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error al inicializar extensiones OpenGL");
return false;
}
#endif
// Limpiar shader anterior si existe
if (program_id_ != 0) {
glDeleteProgram(program_id_);
program_id_ = 0;
}
// Compilar shaders
GLuint vertex_shader = compileShader(vertex_source, GL_VERTEX_SHADER);
GLuint fragment_shader = compileShader(fragment_source, GL_FRAGMENT_SHADER);
if (vertex_shader == 0 || fragment_shader == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error al compilar shaders");
if (vertex_shader != 0) glDeleteShader(vertex_shader);
if (fragment_shader != 0) glDeleteShader(fragment_shader);
return false;
}
// Enlazar programa
program_id_ = linkProgram(vertex_shader, fragment_shader);
// Limpiar shaders (ya no necesarios tras el enlace)
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
if (program_id_ == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error al crear programa de shaders");
return false;
}
// Crear geometría del quad
createQuadGeometry();
// Obtener ubicación del uniform TextureSize
glUseProgram(program_id_);
texture_size_location_ = glGetUniformLocation(program_id_, "TextureSize");
if (texture_size_location_ != -1) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Configurando TextureSize uniform: %.0fx%.0f",
texture_width_, texture_height_);
glUniform2f(texture_size_location_, texture_width_, texture_height_);
checkGLError("glUniform2f(TextureSize)");
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Uniform 'TextureSize' no encontrado en shader");
}
glUseProgram(0);
is_initialized_ = true;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"** OpenGL 3.3 Shader Backend inicializado correctamente");
return true;
}
void OpenGLShader::render() {
if (!is_initialized_ || program_id_ == 0) {
// Fallback: renderizado SDL normal
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
SDL_SetRenderTarget(renderer_, nullptr);
SDL_RenderClear(renderer_);
SDL_RenderTexture(renderer_, back_buffer_, nullptr, nullptr);
SDL_RenderPresent(renderer_);
return;
}
// Obtener tamaño actual de ventana (puede haber cambiado)
int current_width, current_height;
SDL_GetWindowSize(window_, &current_width, &current_height);
// Guardar estados OpenGL
GLint old_program;
glGetIntegerv(GL_CURRENT_PROGRAM, &old_program);
GLint old_viewport[4];
glGetIntegerv(GL_VIEWPORT, old_viewport);
GLboolean was_texture_enabled = glIsEnabled(GL_TEXTURE_2D);
GLint old_texture;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture);
GLint old_vao;
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao);
// Preparar renderizado
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
SDL_SetRenderTarget(renderer_, nullptr);
SDL_RenderClear(renderer_);
// Obtener y bindear textura
GLuint texture_id = getTextureID(back_buffer_);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture_id);
checkGLError("glBindTexture");
// Usar nuestro programa
glUseProgram(program_id_);
checkGLError("glUseProgram");
// Configurar viewport (obtener tamaño lógico de SDL)
int logical_w, logical_h;
SDL_RendererLogicalPresentation mode;
SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &mode);
if (logical_w == 0 || logical_h == 0) {
logical_w = current_width;
logical_h = current_height;
}
// Calcular viewport considerando aspect ratio
int viewport_x = 0, viewport_y = 0;
int viewport_w = current_width, viewport_h = current_height;
if (mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE) {
int scale_x = current_width / logical_w;
int scale_y = current_height / logical_h;
int scale = (scale_x < scale_y) ? scale_x : scale_y;
if (scale < 1) scale = 1;
viewport_w = logical_w * scale;
viewport_h = logical_h * scale;
viewport_x = (current_width - viewport_w) / 2;
viewport_y = (current_height - viewport_h) / 2;
} else {
float window_aspect = static_cast<float>(current_width) / current_height;
float logical_aspect = static_cast<float>(logical_w) / logical_h;
if (window_aspect > logical_aspect) {
viewport_w = static_cast<int>(logical_aspect * current_height);
viewport_x = (current_width - viewport_w) / 2;
} else {
viewport_h = static_cast<int>(current_width / logical_aspect);
viewport_y = (current_height - viewport_h) / 2;
}
}
glViewport(viewport_x, viewport_y, viewport_w, viewport_h);
checkGLError("glViewport");
// Dibujar quad usando VAO
glBindVertexArray(vao_);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
checkGLError("glDrawElements");
// Presentar
SDL_GL_SwapWindow(window_);
// Restaurar estados OpenGL
glUseProgram(old_program);
glBindTexture(GL_TEXTURE_2D, old_texture);
if (!was_texture_enabled) {
glDisable(GL_TEXTURE_2D);
}
glBindVertexArray(old_vao);
glViewport(old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3]);
}
void OpenGLShader::setTextureSize(float width, float height) {
if (!is_initialized_ || program_id_ == 0) {
return;
}
texture_width_ = width;
texture_height_ = height;
GLint old_program;
glGetIntegerv(GL_CURRENT_PROGRAM, &old_program);
glUseProgram(program_id_);
if (texture_size_location_ != -1) {
glUniform2f(texture_size_location_, width, height);
checkGLError("glUniform2f(TextureSize)");
}
glUseProgram(old_program);
}
void OpenGLShader::cleanup() {
if (vao_ != 0) {
glDeleteVertexArrays(1, &vao_);
vao_ = 0;
}
if (vbo_ != 0) {
glDeleteBuffers(1, &vbo_);
vbo_ = 0;
}
if (ebo_ != 0) {
glDeleteBuffers(1, &ebo_);
ebo_ = 0;
}
if (program_id_ != 0) {
glDeleteProgram(program_id_);
program_id_ = 0;
}
is_initialized_ = false;
window_ = nullptr;
renderer_ = nullptr;
back_buffer_ = nullptr;
}
} // namespace Rendering

View File

@@ -0,0 +1,98 @@
#pragma once
#include "core/rendering/shader_backend.hpp"
#ifdef __APPLE__
#include <OpenGL/gl3.h>
#else
#include <SDL3/SDL_opengl.h>
#endif
namespace Rendering {
/**
* @brief Backend de shaders usando OpenGL 3.3 Core Profile
*
* Implementa el renderizado de shaders usando APIs modernas de OpenGL:
* - VAO (Vertex Array Objects)
* - VBO (Vertex Buffer Objects)
* - Shaders GLSL #version 330 core
*/
class OpenGLShader : public ShaderBackend {
public:
OpenGLShader() = default;
~OpenGLShader() override;
bool init(SDL_Window* window,
SDL_Texture* texture,
const std::string& vertex_source,
const std::string& fragment_source) override;
void render() override;
void setTextureSize(float width, float height) override;
void cleanup() override;
bool isHardwareAccelerated() const override { return is_initialized_; }
private:
// Funciones auxiliares
bool initGLExtensions();
GLuint compileShader(const std::string& source, GLenum shader_type);
GLuint linkProgram(GLuint vertex_shader, GLuint fragment_shader);
void createQuadGeometry();
GLuint getTextureID(SDL_Texture* texture);
void checkGLError(const char* operation);
// Estado SDL
SDL_Window* window_ = nullptr;
SDL_Renderer* renderer_ = nullptr;
SDL_Texture* back_buffer_ = nullptr;
// Estado OpenGL
GLuint program_id_ = 0;
GLuint vao_ = 0; // Vertex Array Object
GLuint vbo_ = 0; // Vertex Buffer Object
GLuint ebo_ = 0; // Element Buffer Object
// Ubicaciones de uniforms
GLint texture_size_location_ = -1;
// Tamaños
int window_width_ = 0;
int window_height_ = 0;
float texture_width_ = 0.0f;
float texture_height_ = 0.0f;
// Estado
bool is_initialized_ = false;
#ifndef __APPLE__
// Punteros a funciones OpenGL en Windows/Linux
PFNGLCREATESHADERPROC glCreateShader = nullptr;
PFNGLSHADERSOURCEPROC glShaderSource = nullptr;
PFNGLCOMPILESHADERPROC glCompileShader = nullptr;
PFNGLGETSHADERIVPROC glGetShaderiv = nullptr;
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr;
PFNGLDELETESHADERPROC glDeleteShader = nullptr;
PFNGLATTACHSHADERPROC glAttachShader = nullptr;
PFNGLCREATEPROGRAMPROC glCreateProgram = nullptr;
PFNGLLINKPROGRAMPROC glLinkProgram = nullptr;
PFNGLVALIDATEPROGRAMPROC glValidateProgram = nullptr;
PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr;
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr;
PFNGLUSEPROGRAMPROC glUseProgram = nullptr;
PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
PFNGLUNIFORM2FPROC glUniform2f = nullptr;
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = nullptr;
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = nullptr;
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = nullptr;
PFNGLGENBUFFERSPROC glGenBuffers = nullptr;
PFNGLBINDBUFFERPROC glBindBuffer = nullptr;
PFNGLBUFFERDATAPROC glBufferData = nullptr;
PFNGLDELETEBUFFERSPROC glDeleteBuffers = nullptr;
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr;
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = nullptr;
#endif
};
} // namespace Rendering

View File

@@ -0,0 +1,585 @@
#include "core/rendering/screen.hpp"
#include <SDL3/SDL.h>
#include <ctype.h> // Para toupper
#include <algorithm> // Para max, min, transform
#include <fstream> // Para basic_ostream, operator<<, endl, basic_...
#include <iostream> // Para cerr
#include <iterator> // Para istreambuf_iterator, operator==
#include <string> // Para char_traits, string, operator+, operator==
#include "core/resources/asset.hpp" // Para Asset, AssetType
#include "core/input/mouse.hpp" // Para updateCursorVisibility
#include "game/gameplay/options.hpp" // Para Options, options, OptionsVideo, Border
#include "core/rendering/opengl/opengl_shader.hpp" // Para OpenGLShader
#include "core/resources/resource.hpp" // Para Resource
#include "core/rendering/surface.hpp" // Para Surface, readPalFile
#include "core/rendering/text.hpp" // Para Text
#include "game/ui/notifier.hpp" // Para Notifier
// [SINGLETON]
Screen* Screen::screen_ = nullptr;
// [SINGLETON] Crearemos el objeto con esta función estática
void Screen::init() {
Screen::screen_ = new Screen();
}
// [SINGLETON] Destruiremos el objeto con esta función estática
void Screen::destroy() {
delete Screen::screen_;
}
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
Screen* Screen::get() {
return Screen::screen_;
}
// Constructor
Screen::Screen()
: palettes_(Asset::get()->getListByType(AssetType::PALETTE)) {
// Arranca SDL VIDEO, crea la ventana y el renderizador
initSDLVideo();
// Ajusta los tamaños
game_surface_dstrect_ = {options.video.border.width, options.video.border.height, options.game.width, options.game.height};
//adjustWindowSize();
current_palette_ = findPalette(options.video.palette);
// Define el color del borde para el modo de pantalla completa
border_color_ = static_cast<Uint8>(PaletteColor::BLACK);
// Crea la textura donde se dibujan los graficos del juego
game_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, options.game.width, options.game.height);
if (!game_texture_) {
// Registrar el error si está habilitado
if (options.console) {
std::cerr << "Error: game_texture_ could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
}
SDL_SetTextureScaleMode(game_texture_, SDL_SCALEMODE_NEAREST);
// Crea la textura donde se dibuja el borde que rodea el area de juego
border_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, options.game.width + options.video.border.width * 2, options.game.height + options.video.border.height * 2);
if (!border_texture_) {
// Registrar el error si está habilitado
if (options.console) {
std::cerr << "Error: border_texture_ could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
}
SDL_SetTextureScaleMode(border_texture_, SDL_SCALEMODE_NEAREST);
// Crea la surface donde se dibujan los graficos del juego
game_surface_ = std::make_shared<Surface>(options.game.width, options.game.height);
game_surface_->setPalette(readPalFile(palettes_.at(current_palette_)));
game_surface_->clear(static_cast<Uint8>(PaletteColor::BLACK));
// Crea la surface para el borde de colores
border_surface_ = std::make_shared<Surface>(options.game.width + options.video.border.width * 2, options.game.height + options.video.border.height * 2);
border_surface_->setPalette(readPalFile(palettes_.at(current_palette_)));
border_surface_->clear(border_color_);
// Establece la surface que actuará como renderer para recibir las llamadas a render()
renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_);
// Extrae el nombre de las paletas desde su ruta
processPaletteList();
// Renderizar una vez la textura vacía para que tenga contenido válido
// antes de inicializar los shaders (evita pantalla negra)
SDL_RenderTexture(renderer_, game_texture_, nullptr, nullptr);
SDL_RenderTexture(renderer_, border_texture_, nullptr, nullptr);
// Ahora sí inicializar los shaders
initShaders();
}
// Destructor
Screen::~Screen() {
SDL_DestroyTexture(game_texture_);
SDL_DestroyTexture(border_texture_);
}
// Limpia el renderer
void Screen::clearRenderer(Color color) {
SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 0xFF);
SDL_RenderClear(renderer_);
}
// Prepara para empezar a dibujar en la textura de juego
void Screen::start() { setRendererSurface(nullptr); }
// Vuelca el contenido del renderizador en pantalla
void Screen::render() {
fps_.increment();
// Renderiza todos los overlays
renderOverlays();
// Copia la surface a la textura
surfaceToTexture();
// Copia la textura al renderizador
textureToRenderer();
}
// Establece el modo de video
void Screen::setVideoMode(bool mode) {
// Actualiza las opciones
options.video.fullscreen = mode;
// Configura el modo de pantalla y ajusta la ventana
SDL_SetWindowFullscreen(window_, options.video.fullscreen);
adjustWindowSize();
adjustRenderLogicalSize();
}
// Camibia entre pantalla completa y ventana
void Screen::toggleVideoMode() {
options.video.fullscreen = !options.video.fullscreen;
setVideoMode(options.video.fullscreen);
}
// Reduce el tamaño de la ventana
bool Screen::decWindowZoom() {
if (options.video.fullscreen == 0) {
const int PREVIOUS_ZOOM = options.window.zoom;
--options.window.zoom;
options.window.zoom = std::max(options.window.zoom, 1);
if (options.window.zoom != PREVIOUS_ZOOM) {
setVideoMode(options.video.fullscreen);
return true;
}
}
return false;
}
// Aumenta el tamaño de la ventana
bool Screen::incWindowZoom() {
if (options.video.fullscreen == 0) {
const int PREVIOUS_ZOOM = options.window.zoom;
++options.window.zoom;
options.window.zoom = std::min(options.window.zoom, options.window.max_zoom);
if (options.window.zoom != PREVIOUS_ZOOM) {
setVideoMode(options.video.fullscreen);
return true;
}
}
return false;
}
// Cambia el color del borde
void Screen::setBorderColor(Uint8 color) {
border_color_ = color;
border_surface_->clear(border_color_);
}
// Cambia entre borde visible y no visible
void Screen::toggleBorder() {
options.video.border.enabled = !options.video.border.enabled;
setVideoMode(options.video.fullscreen);
initShaders();
}
// Dibuja las notificaciones
void Screen::renderNotifications() {
if (notifications_enabled_) {
Notifier::get()->render();
}
}
// Cambia el estado de los shaders
void Screen::toggleShaders() {
options.video.shaders = !options.video.shaders;
initShaders();
}
// Actualiza la lógica de la clase
void Screen::update() {
fps_.calculate(SDL_GetTicks());
Notifier::get()->update();
Mouse::updateCursorVisibility();
}
// Calcula el tamaño de la ventana
void Screen::adjustWindowSize() {
window_width_ = options.game.width + (options.video.border.enabled ? options.video.border.width * 2 : 0);
window_height_ = options.game.height + (options.video.border.enabled ? options.video.border.height * 2 : 0);
// Establece el nuevo tamaño
if (options.video.fullscreen == 0) {
int old_width, old_height;
SDL_GetWindowSize(window_, &old_width, &old_height);
int old_pos_x, old_pos_y;
SDL_GetWindowPosition(window_, &old_pos_x, &old_pos_y);
const int NEW_POS_X = old_pos_x + (old_width - (window_width_ * options.window.zoom)) / 2;
const int NEW_POS_Y = old_pos_y + (old_height - (window_height_ * options.window.zoom)) / 2;
SDL_SetWindowSize(window_, window_width_ * options.window.zoom, window_height_ * options.window.zoom);
SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS), std::max(NEW_POS_Y, 0));
}
}
// Ajusta el tamaño lógico del renderizador
void Screen::adjustRenderLogicalSize() {
SDL_SetRenderLogicalPresentation(renderer_, window_width_, window_height_, options.video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
}
// Establece el renderizador para las surfaces
void Screen::setRendererSurface(std::shared_ptr<Surface> surface) {
(surface) ? renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(surface) : renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_);
}
// Cambia la paleta
void Screen::nextPalette() {
++current_palette_;
if (current_palette_ == static_cast<int>(palettes_.size())) {
current_palette_ = 0;
}
setPalete();
}
// Cambia la paleta
void Screen::previousPalette() {
if (current_palette_ > 0) {
--current_palette_;
} else {
current_palette_ = static_cast<Uint8>(palettes_.size() - 1);
}
setPalete();
}
// Establece la paleta
void Screen::setPalete() {
game_surface_->loadPalette(Resource::get()->getPalette(palettes_.at(current_palette_)));
border_surface_->loadPalette(Resource::get()->getPalette(palettes_.at(current_palette_)));
options.video.palette = palettes_.at(current_palette_);
// Eliminar ".gif"
size_t pos = options.video.palette.find(".pal");
if (pos != std::string::npos) {
options.video.palette.erase(pos, 4);
}
// Convertir a mayúsculas
std::transform(options.video.palette.begin(), options.video.palette.end(), options.video.palette.begin(), ::toupper);
}
// Extrae los nombres de las paletas
void Screen::processPaletteList() {
for (auto& palette : palettes_) {
palette = getFileName(palette);
}
}
// Copia la surface a la textura
void Screen::surfaceToTexture() {
if (options.video.border.enabled) {
border_surface_->copyToTexture(renderer_, border_texture_);
game_surface_->copyToTexture(renderer_, border_texture_, nullptr, &game_surface_dstrect_);
} else {
game_surface_->copyToTexture(renderer_, game_texture_);
}
}
// Copia la textura al renderizador
void Screen::textureToRenderer() {
SDL_Texture* texture_to_render = options.video.border.enabled ? border_texture_ : game_texture_;
if (options.video.shaders && shader_backend_) {
shader_backend_->render();
} else {
SDL_SetRenderTarget(renderer_, nullptr);
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
SDL_RenderClear(renderer_);
SDL_RenderTexture(renderer_, texture_to_render, nullptr, nullptr);
SDL_RenderPresent(renderer_);
}
}
// Renderiza todos los overlays
void Screen::renderOverlays() {
renderNotifications();
renderInfo();
}
// Localiza la paleta dentro del vector de paletas
size_t Screen::findPalette(const std::string& name) {
std::string upper_name = toUpper(name + ".pal");
for (size_t i = 0; i < palettes_.size(); ++i) {
if (toUpper(getFileName(palettes_[i])) == upper_name) {
return i;
}
}
return static_cast<size_t>(0);
}
// Muestra información por pantalla
void Screen::renderInfo() {
if (show_debug_info_ && Resource::get()) {
auto text = Resource::get()->getText("smb2");
auto color = static_cast<Uint8>(PaletteColor::YELLOW);
// FPS
const std::string FPS_TEXT = std::to_string(fps_.lastValue) + " FPS";
text->writeColored(options.game.width - text->lenght(FPS_TEXT), 0, FPS_TEXT, color);
// Resolution
text->writeColored(0, 0, info_resolution_, color);
}
}
// Limpia la game_surface_
void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); }
// Establece el tamaño del borde
void Screen::setBorderWidth(int width) { options.video.border.width = width; }
// Establece el tamaño del borde
void Screen::setBorderHeight(int height) { options.video.border.height = height; }
// Establece si se ha de ver el borde en el modo ventana
void Screen::setBorderEnabled(bool value) { options.video.border.enabled = value; }
// Muestra la ventana
void Screen::show() { SDL_ShowWindow(window_); }
// Oculta la ventana
void Screen::hide() { SDL_HideWindow(window_); }
// Establece la visibilidad de las notificaciones
void Screen::setNotificationsEnabled(bool value) { notifications_enabled_ = value; }
// Activa / desactiva la información de debug
void Screen::toggleDebugInfo() { show_debug_info_ = !show_debug_info_; }
// Alterna entre activar y desactivar el escalado entero
void Screen::toggleIntegerScale() {
options.video.integer_scale = !options.video.integer_scale;
SDL_SetRenderLogicalPresentation(renderer_, options.game.width, options.game.height, options.video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
}
// Getters
SDL_Renderer* Screen::getRenderer() { return renderer_; }
std::shared_ptr<Surface> Screen::getRendererSurface() { return (*renderer_surface_); }
std::shared_ptr<Surface> Screen::getBorderSurface() { return border_surface_; }
std::vector<uint8_t> loadData(const std::string& filepath) {
// Fallback a filesystem
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
return {};
}
std::streamsize fileSize = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(fileSize);
if (!file.read(reinterpret_cast<char*>(data.data()), fileSize)) {
return {};
}
return data;
}
// Carga el contenido de los archivos GLSL
void Screen::loadShaders() {
if (vertex_shader_source_.empty()) {
// Detectar si necesitamos OpenGL ES (Raspberry Pi)
// Intentar cargar versión ES primero si existe
std::string VERTEX_FILE = "crtpi_vertex_es.glsl";
auto data = loadData(Asset::get()->get(VERTEX_FILE));
if (data.empty()) {
// Si no existe versión ES, usar versión Desktop
VERTEX_FILE = "crtpi_vertex.glsl";
data = loadData(Asset::get()->get(VERTEX_FILE));
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Usando shaders OpenGL Desktop 3.3");
} else {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Usando shaders OpenGL ES 3.0 (Raspberry Pi)");
}
if (!data.empty()) {
vertex_shader_source_ = std::string(data.begin(), data.end());
}
}
if (fragment_shader_source_.empty()) {
// Intentar cargar versión ES primero si existe
std::string FRAGMENT_FILE = "crtpi_fragment_es.glsl";
auto data = loadData(Asset::get()->get(FRAGMENT_FILE));
if (data.empty()) {
// Si no existe versión ES, usar versión Desktop
FRAGMENT_FILE = "crtpi_fragment.glsl";
data = loadData(Asset::get()->get(FRAGMENT_FILE));
}
if (!data.empty()) {
fragment_shader_source_ = std::string(data.begin(), data.end());
}
}
}
// Inicializa los shaders
void Screen::initShaders() {
#ifndef __APPLE__
if (options.video.shaders) {
loadShaders();
if (!shader_backend_) {
shader_backend_ = std::make_unique<Rendering::OpenGLShader>();
}
shader_backend_->init(window_, options.video.border.enabled ? border_texture_ : game_texture_, vertex_shader_source_, fragment_shader_source_);
//shader_backend_->init(window_, shaders_texture_, vertex_shader_source_, fragment_shader_source_);
}
#else
// En macOS, OpenGL está deprecated y rinde mal
// TODO: Implementar backend de Metal para shaders en macOS
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Shaders no disponibles en macOS (OpenGL deprecated). Usa Metal backend.");
#endif
}
// Obtiene información sobre la pantalla
void Screen::getDisplayInfo() {
int i;
int num_displays = 0;
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
if (displays != nullptr) {
for (i = 0; i < num_displays; ++i) {
SDL_DisplayID instance_id = displays[i];
const char* name = SDL_GetDisplayName(instance_id);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Display %" SDL_PRIu32 ": %s", instance_id, (name != nullptr) ? name : "Unknown");
}
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
// Guarda información del monitor en display_monitor_
const char* first_display_name = SDL_GetDisplayName(displays[0]);
display_monitor_.name = (first_display_name != nullptr) ? first_display_name : "Unknown";
display_monitor_.width = static_cast<int>(dm->w);
display_monitor_.height = static_cast<int>(dm->h);
display_monitor_.refresh_rate = static_cast<int>(dm->refresh_rate);
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
options.window.max_zoom = std::min(dm->w / options.game.width, dm->h / options.game.height);
options.window.zoom = std::min(options.window.zoom, options.window.max_zoom);
// Muestra información sobre el tamaño de la pantalla y de la ventana de juego
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Current display mode: %dx%d @ %dHz", static_cast<int>(dm->w), static_cast<int>(dm->h), static_cast<int>(dm->refresh_rate));
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Window resolution: %dx%d x%d", static_cast<int>(options.game.width), static_cast<int>(options.game.height), options.window.zoom);
options.video.info = std::to_string(static_cast<int>(dm->w)) + "x" +
std::to_string(static_cast<int>(dm->h)) + " @ " +
std::to_string(static_cast<int>(dm->refresh_rate)) + " Hz";
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
const int MAX_ZOOM = std::min(dm->w / options.game.width, (dm->h - WINDOWS_DECORATIONS) / options.game.height);
// Normaliza los valores de zoom
options.window.zoom = std::min(options.window.zoom, MAX_ZOOM);
SDL_free(displays);
}
}
// Arranca SDL VIDEO y crea la ventana
auto Screen::initSDLVideo() -> bool {
// Inicializar SDL
if (!SDL_Init(SDL_INIT_VIDEO)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to initialize SDL_VIDEO! SDL Error: %s",
SDL_GetError());
return false;
}
// Obtener información de la pantalla
getDisplayInfo();
// Configurar hint para renderizado
#ifdef __APPLE__
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set Metal hint!");
}
#else
// Configurar hint de render driver
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set OpenGL hint!");
}
#ifdef _WIN32
// Windows: Pedir explícitamente OpenGL 3.3 Core Profile
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Solicitando OpenGL 3.3 Core Profile");
#else
// Linux: Dejar que SDL elija (Desktop 3.3 en PC, ES 3.0 en RPi automáticamente)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Usando OpenGL por defecto del sistema");
#endif
#endif
// Crear ventana
const auto WINDOW_WIDTH = options.video.border.enabled ? options.game.width + options.video.border.width * 2 : options.game.width;
const auto WINDOW_HEIGHT = options.video.border.enabled ? options.game.height + options.video.border.height * 2 : options.game.height;
#ifdef __APPLE__
SDL_WindowFlags window_flags = SDL_WINDOW_METAL;
#else
SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL;
#endif
if (options.video.fullscreen) {
window_flags |= SDL_WINDOW_FULLSCREEN;
}
window_ = SDL_CreateWindow(options.window.caption.c_str(), WINDOW_WIDTH * options.window.zoom, WINDOW_HEIGHT * options.window.zoom, window_flags);
if (window_ == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to create window! SDL Error: %s",
SDL_GetError());
SDL_Quit();
return false;
}
// Crear renderer
renderer_ = SDL_CreateRenderer(window_, nullptr);
if (renderer_ == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to create renderer! SDL Error: %s",
SDL_GetError());
SDL_DestroyWindow(window_);
window_ = nullptr;
SDL_Quit();
return false;
}
// Configurar renderer
const int EXTRA_WIDTH = options.video.border.enabled ? options.video.border.width * 2 : 0;
const int EXTRA_HEIGHT = options.video.border.enabled ? options.video.border.height * 2 : 0;
SDL_SetRenderLogicalPresentation(
renderer_,
options.game.width + EXTRA_WIDTH,
options.game.height + EXTRA_HEIGHT,
options.video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
SDL_SetRenderVSync(renderer_, options.video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Video system initialized successfully");
return true;
}

View File

@@ -0,0 +1,215 @@
#pragma once
#include <SDL3/SDL.h>
#include <stddef.h> // Para size_t
#include <memory> // Para shared_ptr, __shared_ptr_access
#include <string> // Para string
#include <vector> // Para vector
#include "utils/utils.hpp" // Para Color
struct Surface;
namespace Rendering {
class ShaderBackend;
}
// Tipos de filtro
enum class ScreenFilter : Uint32 {
NEAREST = 0,
LINEAR = 1,
};
class Screen {
private:
// Constantes
static constexpr int WINDOWS_DECORATIONS = 35; // Decoraciones de la ventana
struct DisplayMonitor {
std::string name;
int width;
int height;
int refresh_rate;
};
struct FPS {
Uint32 ticks; // Tiempo en milisegundos desde que se comenzó a contar.
int frameCount; // Número acumulado de frames en el intervalo.
int lastValue; // Número de frames calculado en el último segundo.
// Constructor para inicializar la estructura.
FPS()
: ticks(0),
frameCount(0),
lastValue(0) {}
// Incrementador que se llama en cada frame.
void increment() {
frameCount++;
}
// Método para calcular y devolver el valor de FPS.
int calculate(Uint32 currentTicks) {
if (currentTicks - ticks >= 1000) // Si ha pasado un segundo o más.
{
lastValue = frameCount; // Actualizamos el valor del último FPS.
frameCount = 0; // Reiniciamos el contador de frames.
ticks = currentTicks; // Actualizamos el tiempo base.
}
return lastValue;
}
};
// [SINGLETON] Objeto privado
static Screen* screen_;
// Objetos y punteros
SDL_Window* window_; // Ventana de la aplicación
SDL_Renderer* renderer_; // El renderizador de la ventana
SDL_Texture* game_texture_; // Textura donde se dibuja el juego
SDL_Texture* border_texture_; // Textura donde se dibuja el borde del juego
std::shared_ptr<Surface> game_surface_; // Surface principal para manejar game_surface_data_
std::shared_ptr<Surface> border_surface_; // Surface para pintar el el borde de la pantalla
std::shared_ptr<std::shared_ptr<Surface>> renderer_surface_; // Puntero a la Surface que actua
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (OpenGL/Metal/Vulkan)
// Variables
int window_width_; // Ancho de la pantalla o ventana
int window_height_; // Alto de la pantalla o ventana
SDL_FRect game_surface_dstrect_; // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana
Uint8 border_color_; // Color del borde añadido a la textura de juego para rellenar la pantalla
std::vector<std::string> palettes_; // Listado de los ficheros de paletta disponibles
Uint8 current_palette_ = 0; // Indice para el vector de paletas
bool notifications_enabled_ = false; // indica si se muestran las notificaciones
FPS fps_; // Variable para gestionar los frames por segundo
std::string info_resolution_; // Texto con la informacion de la pantalla
std::string vertex_shader_source_; // Almacena el vertex shader
std::string fragment_shader_source_; // Almacena el fragment shader
DisplayMonitor display_monitor_; // Informacion de la pantalla
#ifdef DEBUG
bool show_debug_info_ = false; // Indica si ha de mostrar/ocultar la información de la pantalla
#else
bool show_debug_info_ = false; // Indica si ha de mostrar/ocultar la información de la pantalla
#endif
// Dibuja las notificaciones
void renderNotifications();
// Calcula el tamaño de la ventana
void adjustWindowSize();
// Ajusta el tamaño lógico del renderizador
void adjustRenderLogicalSize();
// Extrae los nombres de las paletas
void processPaletteList();
// Copia la surface a la textura
void surfaceToTexture();
// Copia la textura al renderizador
void textureToRenderer();
// Renderiza todos los overlays
void renderOverlays();
// Localiza la paleta dentro del vector de paletas
size_t findPalette(const std::string& name);
void initShaders(); // Inicializa los shaders
void loadShaders(); // Carga el contenido del archivo GLSL
void renderInfo(); // Muestra información por pantalla
void getDisplayInfo(); // Obtiene información sobre la pantalla
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
// Constructor
Screen();
// Destructor
~Screen();
public:
// [SINGLETON] Crearemos el objeto con esta función estática
static void init();
// [SINGLETON] Destruiremos el objeto con esta función estática
static void destroy();
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
static Screen* get();
// Limpia el renderer
void clearRenderer(Color color = {0x00, 0x00, 0x00});
// Limpia la game_surface_
void clearSurface(Uint8 index);
// Prepara para empezar a dibujar en la textura de juego
void start();
// Vuelca el contenido del renderizador en pantalla
void render();
// Actualiza la lógica de la clase
void update();
// Establece el modo de video
void setVideoMode(bool mode);
// Camibia entre pantalla completa y ventana
void toggleVideoMode();
// Alterna entre activar y desactivar el escalado entero
void toggleIntegerScale();
// Reduce el tamaño de la ventana
bool decWindowZoom();
// Aumenta el tamaño de la ventana
bool incWindowZoom();
// Cambia el color del borde
void setBorderColor(Uint8 color);
// Establece el tamaño del borde
void setBorderWidth(int width);
// Establece el tamaño del borde
void setBorderHeight(int height);
// Establece si se ha de ver el borde en el modo ventana
void setBorderEnabled(bool value);
// Cambia entre borde visible y no visible
void toggleBorder();
// Cambia el estado de los shaders
void toggleShaders();
// Muestra la ventana
void show();
// Oculta la ventana
void hide();
// Establece el renderizador para las surfaces
void setRendererSurface(std::shared_ptr<Surface> surface = nullptr);
// Cambia la paleta
void nextPalette();
void previousPalette();
// Establece la paleta
void setPalete();
// Establece la visibilidad de las notificaciones
void setNotificationsEnabled(bool value);
// Activa o desactiva la información de debug
void toggleDebugInfo();
// Getters
SDL_Renderer* getRenderer();
std::shared_ptr<Surface> getRendererSurface();
std::shared_ptr<Surface> getBorderSurface();
};

View File

@@ -0,0 +1,55 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
namespace Rendering {
/**
* @brief Interfaz abstracta para backends de renderizado con shaders
*
* Esta interfaz define el contrato que todos los backends de shaders
* deben cumplir (OpenGL, Metal, Vulkan, etc.)
*/
class ShaderBackend {
public:
virtual ~ShaderBackend() = default;
/**
* @brief Inicializa el backend de shaders
* @param window Ventana SDL
* @param texture Textura de backbuffer a la que aplicar shaders
* @param vertex_source Código fuente del vertex shader
* @param fragment_source Código fuente del fragment shader
* @return true si la inicialización fue exitosa
*/
virtual bool init(SDL_Window* window,
SDL_Texture* texture,
const std::string& vertex_source,
const std::string& fragment_source) = 0;
/**
* @brief Renderiza la textura con los shaders aplicados
*/
virtual void render() = 0;
/**
* @brief Establece el tamaño de la textura como parámetro del shader
* @param width Ancho de la textura
* @param height Alto de la textura
*/
virtual void setTextureSize(float width, float height) = 0;
/**
* @brief Limpia y libera recursos del backend
*/
virtual void cleanup() = 0;
/**
* @brief Verifica si el backend está usando aceleración por hardware
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
*/
virtual bool isHardwareAccelerated() const = 0;
};
} // namespace Rendering

View File

@@ -0,0 +1,564 @@
// IWYU pragma: no_include <bits/std_abs.h>
#include "core/rendering/surface.hpp"
#include <SDL3/SDL.h>
#include <algorithm> // Para min, max, copy_n, fill
#include <cmath> // Para abs
#include <cstdint> // Para uint32_t
#include <cstring> // Para memcpy, size_t
#include <fstream> // Para basic_ifstream, basic_ostream, basic_ist...
#include <iostream> // Para cerr
#include <memory> // Para shared_ptr, __shared_ptr_access, default...
#include <sstream> // Para basic_istringstream
#include <stdexcept> // Para runtime_error
#include <vector> // Para vector
#include "core/rendering/gif.hpp" // Para Gif
#include "core/rendering/screen.hpp" // Para Screen
// Carga una paleta desde un archivo .gif
Palette loadPalette(const std::string& file_path) {
// Abrir el archivo en modo binario
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
throw std::runtime_error("Error opening file: " + file_path);
}
// Obtener el tamaño del archivo y leerlo en un buffer
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<Uint8> buffer(size);
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
throw std::runtime_error("Error reading file: " + file_path);
}
// Cargar la paleta usando los datos del buffer
GIF::Gif gif;
std::vector<uint32_t> pal = gif.loadPalette(buffer.data());
if (pal.empty()) {
throw std::runtime_error("No palette found in GIF file: " + file_path);
}
// Crear la paleta y copiar los datos desde 'pal'
Palette palette = {}; // Inicializa la paleta con ceros
std::copy_n(pal.begin(), std::min(pal.size(), palette.size()), palette.begin());
// Mensaje de depuración
printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
return palette;
}
// Carga una paleta desde un archivo .pal
Palette readPalFile(const std::string& file_path) {
Palette palette{};
palette.fill(0); // Inicializar todo con 0 (transparente por defecto)
std::ifstream file(file_path);
if (!file.is_open()) {
throw std::runtime_error("No se pudo abrir el archivo .pal");
}
std::string line;
int line_number = 0;
int color_index = 0;
while (std::getline(file, line)) {
++line_number;
// Ignorar las tres primeras líneas del archivo
if (line_number <= 3) {
continue;
}
// Procesar las líneas restantes con valores RGB
std::istringstream ss(line);
int r, g, b;
if (ss >> r >> g >> b) {
// Construir el color ARGB (A = 255 por defecto)
Uint32 color = (255 << 24) | (r << 16) | (g << 8) | b;
palette[color_index++] = color;
// Limitar a un máximo de 256 colores (opcional)
if (color_index >= 256) {
break;
}
}
}
file.close();
return palette;
}
// Constructor
Surface::Surface(int w, int h)
: surface_data_(std::make_shared<SurfaceData>(w, h)),
transparent_color_(static_cast<Uint8>(PaletteColor::TRANSPARENT)) { initializeSubPalette(sub_palette_); }
Surface::Surface(const std::string& file_path)
: transparent_color_(static_cast<Uint8>(PaletteColor::TRANSPARENT)) {
SurfaceData loadedData = loadSurface(file_path);
surface_data_ = std::make_shared<SurfaceData>(std::move(loadedData));
initializeSubPalette(sub_palette_);
}
// Carga una superficie desde un archivo
SurfaceData Surface::loadSurface(const std::string& file_path) {
// Abrir el archivo usando std::ifstream para manejo automático del recurso
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
std::cerr << "Error opening file: " << file_path << std::endl;
throw std::runtime_error("Error opening file");
}
// Obtener el tamaño del archivo
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
// Leer el contenido del archivo en un buffer
std::vector<Uint8> buffer(size);
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
std::cerr << "Error reading file: " << file_path << std::endl;
throw std::runtime_error("Error reading file");
}
// Crear un objeto Gif y llamar a la función loadGif
GIF::Gif gif;
Uint16 w = 0, h = 0;
std::vector<Uint8> rawPixels = gif.loadGif(buffer.data(), w, h);
if (rawPixels.empty()) {
std::cerr << "Error loading GIF from file: " << file_path << std::endl;
throw std::runtime_error("Error loading GIF");
}
// Si el constructor de Surface espera un std::shared_ptr<Uint8[]>,
// reservamos un bloque dinámico y copiamos los datos del vector.
size_t pixelCount = rawPixels.size();
auto pixels = std::shared_ptr<Uint8[]>(new Uint8[pixelCount], std::default_delete<Uint8[]>());
std::memcpy(pixels.get(), rawPixels.data(), pixelCount);
// Crear y devolver directamente el objeto SurfaceData
printWithDots("Surface : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
return SurfaceData(w, h, pixels);
}
// Carga una paleta desde un archivo
void Surface::loadPalette(const std::string& file_path) {
palette_ = ::loadPalette(file_path);
}
// Carga una paleta desde otra paleta
void Surface::loadPalette(Palette palette) {
palette_ = palette;
}
// Establece un color en la paleta
void Surface::setColor(int index, Uint32 color) {
palette_.at(index) = color;
}
// Rellena la superficie con un color
void Surface::clear(Uint8 color) {
const size_t total_pixels = surface_data_->width * surface_data_->height;
Uint8* data_ptr = surface_data_->data.get();
std::fill(data_ptr, data_ptr + total_pixels, color);
}
// Pone un pixel en la SurfaceData
void Surface::putPixel(int x, int y, Uint8 color) {
if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height) {
return; // Coordenadas fuera de rango
}
const int index = x + y * surface_data_->width;
surface_data_->data.get()[index] = color;
}
// Obtiene el color de un pixel de la surface_data
Uint8 Surface::getPixel(int x, int y) { return surface_data_->data.get()[x + y * static_cast<int>(surface_data_->width)]; }
// Dibuja un rectangulo relleno
void Surface::fillRect(const SDL_FRect* rect, Uint8 color) {
// Limitar los valores del rectángulo al tamaño de la superficie
float x_start = std::max(0.0F, rect->x);
float y_start = std::max(0.0F, rect->y);
float x_end = std::min(rect->x + rect->w, surface_data_->width);
float y_end = std::min(rect->y + rect->h, surface_data_->height);
// Recorrer cada píxel dentro del rectángulo directamente
for (int y = y_start; y < y_end; ++y) {
for (int x = x_start; x < x_end; ++x) {
const int INDEX = x + y * surface_data_->width;
surface_data_->data.get()[INDEX] = color;
}
}
}
// Dibuja el borde de un rectangulo
void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) {
// Limitar los valores del rectángulo al tamaño de la superficie
float x_start = std::max(0.0F, rect->x);
float y_start = std::max(0.0F, rect->y);
float x_end = std::min(rect->x + rect->w, surface_data_->width);
float y_end = std::min(rect->y + rect->h, surface_data_->height);
// Dibujar bordes horizontales
for (int x = x_start; x < x_end; ++x) {
// Borde superior
const int top_index = x + y_start * surface_data_->width;
surface_data_->data.get()[top_index] = color;
// Borde inferior
const int bottom_index = x + (y_end - 1) * surface_data_->width;
surface_data_->data.get()[bottom_index] = color;
}
// Dibujar bordes verticales
for (int y = y_start; y < y_end; ++y) {
// Borde izquierdo
const int LEFT_INDEX = x_start + y * surface_data_->width;
surface_data_->data.get()[LEFT_INDEX] = color;
// Borde derecho
const int RIGHT_INDEX = (x_end - 1) + y * surface_data_->width;
surface_data_->data.get()[RIGHT_INDEX] = color;
}
}
// Dibuja una linea
void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) {
// Calcula las diferencias
float dx = std::abs(x2 - x1);
float dy = std::abs(y2 - y1);
// Determina la dirección del incremento
float sx = (x1 < x2) ? 1 : -1;
float sy = (y1 < y2) ? 1 : -1;
float err = dx - dy;
while (true) {
// Asegúrate de no dibujar fuera de los límites de la superficie
if (x1 >= 0 && x1 < surface_data_->width && y1 >= 0 && y1 < surface_data_->height) {
surface_data_->data.get()[static_cast<size_t>(x1 + y1 * surface_data_->width)] = color;
}
// Si alcanzamos el punto final, salimos
if (x1 == x2 && y1 == y2)
break;
int e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x1 += sx;
}
if (e2 < dx) {
err += dx;
y1 += sy;
}
}
}
void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
// Limitar la región para evitar accesos fuera de rango en destino
w = std::min(w, surface_data->width - dx);
h = std::min(h, surface_data->height - dy);
for (int iy = 0; iy < h; ++iy) {
for (int ix = 0; ix < w; ++ix) {
// Verificar que las coordenadas de destino están dentro de los límites
if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width) {
if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height) {
int src_x = sx + ix;
int src_y = sy + iy;
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
if (color != transparent_color_) {
surface_data->data.get()[static_cast<size_t>(dest_x + dest_y * surface_data->width)] = sub_palette_[color];
}
}
}
}
}
}
void Surface::render(int x, int y, SDL_FRect* srcRect, SDL_FlipMode flip) {
auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();
// Determina la región de origen (clip) a renderizar
float sx = (srcRect) ? srcRect->x : 0;
float sy = (srcRect) ? srcRect->y : 0;
float w = (srcRect) ? srcRect->w : surface_data_->width;
float h = (srcRect) ? srcRect->h : surface_data_->height;
// Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
w = std::min(w, surface_data_dest->width - x);
h = std::min(h, surface_data_dest->height - y);
// Limitar la región para evitar accesos fuera de rango en destino
w = std::min(w, surface_data_dest->width - x);
h = std::min(h, surface_data_dest->height - y);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < h; ++iy) {
for (int ix = 0; ix < w; ++ix) {
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
// Coordenadas de destino
int dest_x = x + ix;
int dest_y = y + iy;
// Verificar que las coordenadas de destino están dentro de los límites
if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height) {
// Copia el píxel si no es transparente
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
if (color != transparent_color_) {
surface_data_dest->data[dest_x + dest_y * surface_data_dest->width] = sub_palette_[color];
}
}
}
}
}
// Copia una región de la superficie de origen a la de destino
void Surface::render(SDL_FRect* srcRect, SDL_FRect* dstRect, SDL_FlipMode flip) {
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Si srcRect es nullptr, tomar toda la superficie fuente
float sx = (srcRect) ? srcRect->x : 0;
float sy = (srcRect) ? srcRect->y : 0;
float sw = (srcRect) ? srcRect->w : surface_data_->width;
float sh = (srcRect) ? srcRect->h : surface_data_->height;
// Si dstRect es nullptr, asignar las mismas dimensiones que srcRect
float dx = (dstRect) ? dstRect->x : 0;
float dy = (dstRect) ? dstRect->y : 0;
float dw = (dstRect) ? dstRect->w : sw;
float dh = (dstRect) ? dstRect->h : sh;
// Asegurarse de que srcRect y dstRect tienen las mismas dimensiones
if (sw != dw || sh != dh) {
dw = sw; // Respetar las dimensiones de srcRect
dh = sh;
}
// Limitar la región para evitar accesos fuera de rango en src y dst
sw = std::min(sw, surface_data_->width - sx);
sh = std::min(sh, surface_data_->height - sy);
dw = std::min(dw, surface_data->width - dx);
dh = std::min(dh, surface_data->height - dy);
int final_width = std::min(sw, dw);
int final_height = std::min(sh, dh);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < final_height; ++iy) {
for (int ix = 0; ix < final_width; ++ix) {
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + final_width - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + final_height - 1 - iy) : (sy + iy);
// Coordenadas de destino
if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width) {
if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height) {
// Copiar el píxel si no es transparente
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
if (color != transparent_color_) {
surface_data->data[dest_x + dest_y * surface_data->width] = sub_palette_[color];
}
}
}
}
}
}
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_FRect* srcRect, SDL_FlipMode flip) {
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Determina la región de origen (clip) a renderizar
float sx = (srcRect) ? srcRect->x : 0;
float sy = (srcRect) ? srcRect->y : 0;
float w = (srcRect) ? srcRect->w : surface_data_->width;
float h = (srcRect) ? srcRect->h : surface_data_->height;
// Limitar la región para evitar accesos fuera de rango
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < h; ++iy) {
for (int ix = 0; ix < w; ++ix) {
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
// Coordenadas de destino
int dest_x = x + ix;
int dest_y = y + iy;
// Verifica que las coordenadas de destino estén dentro de los límites
if (dest_x < 0 || dest_y < 0 || dest_x >= surface_data->width || dest_y >= surface_data->height) {
continue; // Saltar píxeles fuera del rango del destino
}
// Copia el píxel si no es transparente
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
if (color != transparent_color_) {
surface_data->data[dest_x + dest_y * surface_data->width] =
(color == source_color) ? target_color : color;
}
}
}
}
// Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) {
if (!renderer || !texture || !surface_data_) {
throw std::runtime_error("Renderer or texture is null.");
}
if (surface_data_->width <= 0 || surface_data_->height <= 0 || !surface_data_->data.get()) {
throw std::runtime_error("Invalid surface dimensions or data.");
}
Uint32* pixels = nullptr;
int pitch = 0;
// Bloquea la textura para modificar los píxeles directamente
if (!SDL_LockTexture(texture, nullptr, reinterpret_cast<void**>(&pixels), &pitch)) {
throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
}
// Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware)
int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y) {
for (int x = 0; x < surface_data_->width; ++x) {
// Calcular la posición correcta en la textura teniendo en cuenta el stride
int texture_index = y * row_stride + x;
int surface_index = y * surface_data_->width + x;
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
}
}
SDL_UnlockTexture(texture); // Desbloquea la textura
// Renderiza la textura en la pantalla completa
if (!SDL_RenderTexture(renderer, texture, nullptr, nullptr)) {
throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
}
}
// Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* srcRect, SDL_FRect* destRect) {
if (!renderer || !texture || !surface_data_) {
throw std::runtime_error("Renderer or texture is null.");
}
if (surface_data_->width <= 0 || surface_data_->height <= 0 || !surface_data_->data.get()) {
throw std::runtime_error("Invalid surface dimensions or data.");
}
Uint32* pixels = nullptr;
int pitch = 0;
SDL_Rect lockRect;
if (destRect) {
lockRect.x = static_cast<int>(destRect->x);
lockRect.y = static_cast<int>(destRect->y);
lockRect.w = static_cast<int>(destRect->w);
lockRect.h = static_cast<int>(destRect->h);
}
// Usa lockRect solo si destRect no es nulo
if (!SDL_LockTexture(texture, destRect ? &lockRect : nullptr, reinterpret_cast<void**>(&pixels), &pitch)) {
throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
}
int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y) {
for (int x = 0; x < surface_data_->width; ++x) {
int texture_index = y * row_stride + x;
int surface_index = y * surface_data_->width + x;
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
}
}
SDL_UnlockTexture(texture);
// Renderiza la textura con los rectángulos especificados
if (!SDL_RenderTexture(renderer, texture, srcRect, destRect)) {
throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
}
}
// Realiza un efecto de fundido en la paleta principal
bool Surface::fadePalette() {
// Verificar que el tamaño mínimo de palette_ sea adecuado
static constexpr int palette_size = 19;
if (sizeof(palette_) / sizeof(palette_[0]) < palette_size) {
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
}
// Desplazar colores (pares e impares)
for (int i = 18; i > 1; --i) {
palette_[i] = palette_[i - 2];
}
// Ajustar el primer color
palette_[1] = palette_[0];
// Devolver si el índice 15 coincide con el índice 0
return palette_[15] == palette_[0];
}
// Realiza un efecto de fundido en la paleta secundaria
bool Surface::fadeSubPalette(Uint32 delay) {
// Variable estática para almacenar el último tick
static Uint32 last_tick = 0;
// Obtener el tiempo actual
Uint32 current_tick = SDL_GetTicks();
// Verificar si ha pasado el tiempo de retardo
if (current_tick - last_tick < delay) {
return false; // No se realiza el fade
}
// Actualizar el último tick
last_tick = current_tick;
// Verificar que el tamaño mínimo de sub_palette_ sea adecuado
static constexpr int sub_palette_size = 19;
if (sizeof(sub_palette_) / sizeof(sub_palette_[0]) < sub_palette_size) {
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
}
// Desplazar colores (pares e impares)
for (int i = 18; i > 1; --i) {
sub_palette_[i] = sub_palette_[i - 2];
}
// Ajustar el primer color
sub_palette_[1] = sub_palette_[0];
// Devolver si el índice 15 coincide con el índice 0
return sub_palette_[15] == sub_palette_[0];
}

View File

@@ -0,0 +1,133 @@
#pragma once
#include <SDL3/SDL.h>
#include <array> // Para array
#include <memory> // Para default_delete, shared_ptr, __shared_pt...
#include <numeric> // Para iota
#include <string> // Para string
#include <utility> // Para move
#include "utils/utils.hpp" // Para PaletteColor
// Alias
using Palette = std::array<Uint32, 256>;
using SubPalette = std::array<Uint8, 256>;
// Carga una paleta desde un archivo .gif
Palette loadPalette(const std::string& file_path);
// Carga una paleta desde un archivo .pal
Palette readPalFile(const std::string& file_path);
struct SurfaceData {
std::shared_ptr<Uint8[]> data; // Usa std::shared_ptr para gestión automática
float width; // Ancho de la imagen
float height; // Alto de la imagen
// Constructor por defecto
SurfaceData()
: data(nullptr),
width(0),
height(0) {}
// Constructor que inicializa dimensiones y asigna memoria
SurfaceData(float w, float h)
: data(std::shared_ptr<Uint8[]>(new Uint8[static_cast<size_t>(w * h)](), std::default_delete<Uint8[]>())),
width(w),
height(h) {}
// Constructor para inicializar directamente con datos
SurfaceData(float w, float h, std::shared_ptr<Uint8[]> pixels)
: data(std::move(pixels)),
width(w),
height(h) {}
// Constructor de movimiento
SurfaceData(SurfaceData&& other) noexcept = default;
// Operador de movimiento
SurfaceData& operator=(SurfaceData&& other) noexcept = default;
// Evita copias accidentales
SurfaceData(const SurfaceData&) = delete;
SurfaceData& operator=(const SurfaceData&) = delete;
};
class Surface {
private:
std::shared_ptr<SurfaceData> surface_data_; // Datos a dibujar
Palette palette_; // Paleta para volcar la SurfaceData a una Textura
SubPalette sub_palette_; // Paleta para reindexar colores
int transparent_color_; // Indice de la paleta que se omite en la copia de datos
public:
// Constructor
Surface(int w, int h);
explicit Surface(const std::string& file_path);
// Destructor
~Surface() = default;
// Carga una SurfaceData desde un archivo
SurfaceData loadSurface(const std::string& file_path);
// Carga una paleta desde un archivo
void loadPalette(const std::string& file_path);
void loadPalette(Palette palette);
// Copia una región de la SurfaceData de origen a la SurfaceData de destino
void render(float dx, float dy, float sx, float sy, float w, float h);
void render(int x, int y, SDL_FRect* clip = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
void render(SDL_FRect* srcRect = nullptr, SDL_FRect* dstRect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* srcRect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
// Establece un color en la paleta
void setColor(int index, Uint32 color);
// Rellena la SurfaceData con un color
void clear(Uint8 color);
// Vuelca la SurfaceData a una textura
void copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture);
void copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* srcRect, SDL_FRect* destRect);
// Realiza un efecto de fundido en las paletas
bool fadePalette();
bool fadeSubPalette(Uint32 delay = 0);
// Pone un pixel en la SurfaceData
void putPixel(int x, int y, Uint8 color);
// Obtiene el color de un pixel de la surface_data
Uint8 getPixel(int x, int y);
// Dibuja un rectangulo relleno
void fillRect(const SDL_FRect* rect, Uint8 color);
// Dibuja el borde de un rectangulo
void drawRectBorder(const SDL_FRect* rect, Uint8 color);
// Dibuja una linea
void drawLine(float x1, float y1, float x2, float y2, Uint8 color);
// Metodos para gestionar surface_data_
std::shared_ptr<SurfaceData> getSurfaceData() const { return surface_data_; }
void setSurfaceData(std::shared_ptr<SurfaceData> new_data) { surface_data_ = new_data; }
// Obtien ancho y alto
float getWidth() const { return surface_data_->width; }
float getHeight() const { return surface_data_->height; }
// Color transparente
Uint8 getTransparentColor() const { return transparent_color_; }
void setTransparentColor(Uint8 color = 255) { transparent_color_ = color; }
// Paleta
void setPalette(const std::array<Uint32, 256>& palette) { palette_ = palette; }
// Inicializa la sub paleta
void initializeSubPalette(SubPalette& palette) { std::iota(palette.begin(), palette.end(), 0); }
};

View File

@@ -0,0 +1,236 @@
#include "core/rendering/surface_animated_sprite.hpp"
#include <stddef.h> // Para size_t
#include <fstream> // Para basic_ostream, basic_istream, operator<<, basic...
#include <iostream> // Para cout, cerr
#include <sstream> // Para basic_stringstream
#include <stdexcept> // Para runtime_error
#include "core/rendering/surface.hpp" // Para Surface
#include "utils/utils.hpp" // Para printWithDots
// Carga las animaciones en un vector(Animations) desde un fichero
Animations loadAnimationsFromFile(const std::string& file_path) {
std::ifstream file(file_path);
if (!file) {
std::cerr << "Error: Fichero no encontrado " << file_path << std::endl;
throw std::runtime_error("Fichero no encontrado: " + file_path);
}
printWithDots("Animation : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
std::vector<std::string> buffer;
std::string line;
while (std::getline(file, line)) {
if (!line.empty())
buffer.push_back(line);
}
return buffer;
}
// Constructor
SAnimatedSprite::SAnimatedSprite(std::shared_ptr<Surface> surface, const std::string& file_path)
: SMovingSprite(surface) {
// Carga las animaciones
if (!file_path.empty()) {
Animations v = loadAnimationsFromFile(file_path);
setAnimations(v);
}
}
// Constructor
SAnimatedSprite::SAnimatedSprite(std::shared_ptr<Surface> surface, const Animations& animations)
: SMovingSprite(surface) {
if (!animations.empty()) {
setAnimations(animations);
}
}
// Obtiene el indice de la animación a partir del nombre
int SAnimatedSprite::getIndex(const std::string& name) {
auto index = -1;
for (const auto& a : animations_) {
index++;
if (a.name == name) {
return index;
}
}
std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << std::endl;
return -1;
}
// Calcula el frame correspondiente a la animación
void SAnimatedSprite::animate() {
if (animations_[current_animation_].speed == 0) {
return;
}
// Calcula el frame actual a partir del contador
animations_[current_animation_].current_frame = animations_[current_animation_].counter / animations_[current_animation_].speed;
// Si alcanza el final de la animación, reinicia el contador de la animación
// en función de la variable loop y coloca el nuevo frame
if (animations_[current_animation_].current_frame >= static_cast<int>(animations_[current_animation_].frames.size())) {
if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
animations_[current_animation_].current_frame = animations_[current_animation_].frames.size();
animations_[current_animation_].completed = true;
} else { // Si hay loop, vuelve al frame indicado
animations_[current_animation_].counter = 0;
animations_[current_animation_].current_frame = animations_[current_animation_].loop;
}
}
// En caso contrario
else {
// Escoge el frame correspondiente de la animación
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
// Incrementa el contador de la animacion
animations_[current_animation_].counter++;
}
}
// Comprueba si ha terminado la animación
bool SAnimatedSprite::animationIsCompleted() {
return animations_[current_animation_].completed;
}
// Establece la animacion actual
void SAnimatedSprite::setCurrentAnimation(const std::string& name) {
const auto new_animation = getIndex(name);
if (current_animation_ != new_animation) {
current_animation_ = new_animation;
animations_[current_animation_].current_frame = 0;
animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false;
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}
}
// Establece la animacion actual
void SAnimatedSprite::setCurrentAnimation(int index) {
const auto new_animation = index;
if (current_animation_ != new_animation) {
current_animation_ = new_animation;
animations_[current_animation_].current_frame = 0;
animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false;
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}
}
// Actualiza las variables del objeto
void SAnimatedSprite::update() {
animate();
SMovingSprite::update();
}
// Reinicia la animación
void SAnimatedSprite::resetAnimation() {
animations_[current_animation_].current_frame = 0;
animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false;
}
// Carga la animación desde un vector de cadenas
void SAnimatedSprite::setAnimations(const Animations& animations) {
float frame_width = 1.0F;
float frame_height = 1.0F;
int frames_per_row = 1;
int max_tiles = 1;
size_t index = 0;
while (index < animations.size()) {
std::string line = animations.at(index);
// Parsea el fichero para buscar variables y valores
if (line != "[animation]") {
// Encuentra la posición del caracter '='
size_t pos = line.find("=");
// Procesa las dos subcadenas
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
int value = std::stoi(line.substr(pos + 1));
if (key == "frame_width")
frame_width = value;
else if (key == "frame_height")
frame_height = value;
else
std::cout << "Warning: unknown parameter " << key << std::endl;
frames_per_row = surface_->getWidth() / frame_width;
const int w = surface_->getWidth() / frame_width;
const int h = surface_->getHeight() / frame_height;
max_tiles = w * h;
}
}
// Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
if (line == "[animation]") {
AnimationData animation;
do {
index++;
line = animations.at(index);
size_t pos = line.find("=");
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
if (key == "name")
animation.name = value;
else if (key == "speed")
animation.speed = std::stoi(value);
else if (key == "loop")
animation.loop = std::stoi(value);
else if (key == "frames") {
// Se introducen los valores separados por comas en un vector
std::stringstream ss(value);
std::string tmp;
SDL_FRect rect = {0.0F, 0.0F, frame_width, frame_height};
while (getline(ss, tmp, ',')) {
// Comprueba que el tile no sea mayor que el maximo indice permitido
const int num_tile = std::stoi(tmp);
if (num_tile <= max_tiles) {
rect.x = (num_tile % frames_per_row) * frame_width;
rect.y = (num_tile / frames_per_row) * frame_height;
animation.frames.emplace_back(rect);
}
}
}
else
std::cout << "Warning: unknown parameter " << key << std::endl;
}
} while (line != "[/animation]");
// Añade la animación al vector de animaciones
animations_.emplace_back(animation);
}
// Una vez procesada la linea, aumenta el indice para pasar a la siguiente
index++;
}
// Pone un valor por defecto
setWidth(frame_width);
setHeight(frame_height);
}
// Establece el frame actual de la animación
void SAnimatedSprite::setCurrentAnimationFrame(int num) {
// Descarta valores fuera de rango
if (num < 0 || num >= static_cast<int>(animations_[current_animation_].frames.size())) {
num = 0;
}
// Cambia el valor de la variable
animations_[current_animation_].current_frame = num;
animations_[current_animation_].counter = 0;
// Escoge el frame correspondiente de la animación
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}

View File

@@ -0,0 +1,78 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "core/rendering/surface_moving_sprite.hpp" // Para SMovingSprite
class Surface; // lines 9-9
struct AnimationData {
std::string name; // Nombre de la animacion
std::vector<SDL_FRect> frames; // Cada uno de los frames que componen la animación
int speed; // Velocidad de la animación
int loop; // Indica a que frame vuelve la animación al terminar. -1 para que no vuelva
bool completed; // Indica si ha finalizado la animación
int current_frame; // Frame actual
int counter; // Contador para las animaciones
AnimationData()
: name(std::string()),
speed(5),
loop(0),
completed(false),
current_frame(0),
counter(0) {}
};
using Animations = std::vector<std::string>;
// Carga las animaciones en un vector(Animations) desde un fichero
Animations loadAnimationsFromFile(const std::string& file_path);
class SAnimatedSprite : public SMovingSprite {
protected:
// Variables
std::vector<AnimationData> animations_; // Vector con las diferentes animaciones
int current_animation_ = 0; // Animacion activa
// Calcula el frame correspondiente a la animación actual
void animate();
// Carga la animación desde un vector de cadenas
void setAnimations(const Animations& animations);
public:
// Constructor
SAnimatedSprite(std::shared_ptr<Surface> surface, const std::string& file_path);
SAnimatedSprite(std::shared_ptr<Surface> surface, const Animations& animations);
explicit SAnimatedSprite(std::shared_ptr<Surface> surface)
: SMovingSprite(surface) {}
// Destructor
virtual ~SAnimatedSprite() override = default;
// Actualiza las variables del objeto
void update() override;
// Comprueba si ha terminado la animación
bool animationIsCompleted();
// Obtiene el indice de la animación a partir del nombre
int getIndex(const std::string& name);
// Establece la animacion actual
void setCurrentAnimation(const std::string& name = "default");
void setCurrentAnimation(int index = 0);
// Reinicia la animación
void resetAnimation();
// Establece el frame actual de la animación
void setCurrentAnimationFrame(int num);
// Obtiene el numero de frames de la animación actual
int getCurrentAnimationSize() { return static_cast<int>(animations_[current_animation_].frames.size()); }
};

View File

@@ -0,0 +1,94 @@
#include "core/rendering/surface_moving_sprite.hpp"
#include "core/rendering/surface.hpp" // Para Surface
// Constructor
SMovingSprite::SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip)
: SSprite(surface, pos),
x_(pos.x),
y_(pos.y),
flip_(flip) { SSprite::pos_ = pos; }
SMovingSprite::SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
: SSprite(surface, pos),
x_(pos.x),
y_(pos.y),
flip_(SDL_FLIP_NONE) { SSprite::pos_ = pos; }
SMovingSprite::SMovingSprite(std::shared_ptr<Surface> surface)
: SSprite(surface),
x_(0.0f),
y_(0.0f),
flip_(SDL_FLIP_NONE) { SSprite::clear(); }
// Reinicia todas las variables
void SMovingSprite::clear() {
x_ = 0.0f; // Posición en el eje X
y_ = 0.0f; // Posición en el eje Y
vx_ = 0.0f; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
vy_ = 0.0f; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
ax_ = 0.0f; // Aceleración en el eje X. Variación de la velocidad
ay_ = 0.0f; // Aceleración en el eje Y. Variación de la velocidad
flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite
SSprite::clear();
}
// Mueve el sprite
void SMovingSprite::move() {
x_ += vx_;
y_ += vy_;
vx_ += ax_;
vy_ += ay_;
pos_.x = static_cast<int>(x_);
pos_.y = static_cast<int>(y_);
}
// Actualiza las variables internas del objeto
void SMovingSprite::update() {
move();
}
// Muestra el sprite por pantalla
void SMovingSprite::render() {
surface_->render(pos_.x, pos_.y, &clip_, flip_);
}
// Muestra el sprite por pantalla
void SMovingSprite::render(Uint8 source_color, Uint8 target_color) {
surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_, flip_);
}
// Establece la posición y_ el tamaño del objeto
void SMovingSprite::setPos(SDL_FRect rect) {
x_ = static_cast<float>(rect.x);
y_ = static_cast<float>(rect.y);
pos_ = rect;
}
// Establece el valor de las variables
void SMovingSprite::setPos(float x, float y) {
x_ = x;
y_ = y;
pos_.x = static_cast<int>(x_);
pos_.y = static_cast<int>(y_);
}
// Establece el valor de la variable
void SMovingSprite::setPosX(float value) {
x_ = value;
pos_.x = static_cast<int>(x_);
}
// Establece el valor de la variable
void SMovingSprite::setPosY(float value) {
y_ = value;
pos_.y = static_cast<int>(y_);
}

View File

@@ -0,0 +1,81 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
#include "core/rendering/surface_sprite.hpp" // Para SSprite
class Surface; // lines 8-8
// Clase SMovingSprite. Añade movimiento y flip al sprite
class SMovingSprite : public SSprite {
public:
protected:
float x_; // Posición en el eje X
float y_; // Posición en el eje Y
float vx_ = 0.0f; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
float vy_ = 0.0f; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
float ax_ = 0.0f; // Aceleración en el eje X. Variación de la velocidad
float ay_ = 0.0f; // Aceleración en el eje Y. Variación de la velocidad
SDL_FlipMode flip_; // Indica como se voltea el sprite
// Mueve el sprite
void move();
public:
// Constructor
SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip);
SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
explicit SMovingSprite(std::shared_ptr<Surface> surface);
// Destructor
virtual ~SMovingSprite() override = default;
// Actualiza las variables internas del objeto
virtual void update();
// Reinicia todas las variables a cero
void clear() override;
// Muestra el sprite por pantalla
void render() override;
void render(Uint8 source_color, Uint8 target_color) override;
// Obtiene la variable
float getPosX() const { return x_; }
float getPosY() const { return y_; }
float getVelX() const { return vx_; }
float getVelY() const { return vy_; }
float getAccelX() const { return ax_; }
float getAccelY() const { return ay_; }
// Establece la variable
void setVelX(float value) { vx_ = value; }
void setVelY(float value) { vy_ = value; }
void setAccelX(float value) { ax_ = value; }
void setAccelY(float value) { ay_ = value; }
// Establece el valor de la variable
void setFlip(SDL_FlipMode flip) { flip_ = flip; }
// Gira el sprite horizontalmente
void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; }
// Obtiene el valor de la variable
SDL_FlipMode getFlip() { return flip_; }
// Establece la posición y_ el tamaño del objeto
void setPos(SDL_FRect rect);
// Establece el valor de las variables
void setPos(float x, float y);
// Establece el valor de la variable
void setPosX(float value);
// Establece el valor de la variable
void setPosY(float value);
};

View File

@@ -0,0 +1,46 @@
#include "core/rendering/surface_sprite.hpp"
#include "core/rendering/surface.hpp" // Para Surface
// Constructor
SSprite::SSprite(std::shared_ptr<Surface> surface, float x, float y, float w, float h)
: surface_(surface),
pos_((SDL_FRect){x, y, w, h}),
clip_((SDL_FRect){0.0F, 0.0F, pos_.w, pos_.h}) {}
SSprite::SSprite(std::shared_ptr<Surface> surface, SDL_FRect rect)
: surface_(surface),
pos_(rect),
clip_((SDL_FRect){0, 0, pos_.w, pos_.h}) {}
SSprite::SSprite(std::shared_ptr<Surface> surface)
: surface_(surface),
pos_((SDL_FRect){0.0F, 0.0F, surface_->getWidth(), surface_->getHeight()}),
clip_(pos_) {}
// Muestra el sprite por pantalla
void SSprite::render() {
surface_->render(pos_.x, pos_.y, &clip_);
}
void SSprite::render(Uint8 source_color, Uint8 target_color) {
surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_);
}
// Establece la posición del objeto
void SSprite::setPosition(float x, float y) {
pos_.x = x;
pos_.y = y;
}
// Establece la posición del objeto
void SSprite::setPosition(SDL_FPoint p) {
pos_.x = p.x;
pos_.y = p.y;
}
// Reinicia las variables a cero
void SSprite::clear() {
pos_ = {0, 0, 0, 0};
clip_ = {0, 0, 0, 0};
}

View File

@@ -0,0 +1,69 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
class Surface; // lines 5-5
// Clase SSprite
class SSprite {
protected:
// Variables
std::shared_ptr<Surface> surface_; // Surface donde estan todos los dibujos del sprite
SDL_FRect pos_; // Posición y tamaño donde dibujar el sprite
SDL_FRect clip_; // Rectangulo de origen de la surface que se dibujará en pantalla
public:
// Constructor
SSprite(std::shared_ptr<Surface>, float x, float y, float w, float h);
SSprite(std::shared_ptr<Surface>, SDL_FRect rect);
explicit SSprite(std::shared_ptr<Surface>);
// Destructor
virtual ~SSprite() = default;
// Muestra el sprite por pantalla
virtual void render();
virtual void render(Uint8 source_color, Uint8 target_color);
// Reinicia las variables a cero
virtual void clear();
// Obtiene la posición y el tamaño
float getX() const { return pos_.x; }
float getY() const { return pos_.y; }
float getWidth() const { return pos_.w; }
float getHeight() const { return pos_.h; }
// Devuelve el rectangulo donde está el sprite
SDL_FRect getPosition() const { return pos_; }
SDL_FRect& getRect() { return pos_; }
// Establece la posición y el tamaño
void setX(float x) { pos_.x = x; }
void setY(float y) { pos_.y = y; }
void setWidth(float w) { pos_.w = w; }
void setHeight(float h) { pos_.h = h; }
// Establece la posición del objeto
void setPosition(float x, float y);
void setPosition(SDL_FPoint p);
void setPosition(SDL_FRect r) { pos_ = r; }
// Aumenta o disminuye la posición
void incX(float value) { pos_.x += value; }
void incY(float value) { pos_.y += value; }
// Obtiene el rectangulo que se dibuja de la surface
SDL_FRect getClip() const { return clip_; }
// Establece el rectangulo que se dibuja de la surface
void setClip(SDL_FRect rect) { clip_ = rect; }
void setClip(float x, float y, float w, float h) { clip_ = (SDL_FRect){x, y, w, h}; }
// Obtiene un puntero a la surface
std::shared_ptr<Surface> getSurface() const { return surface_; }
// Establece la surface a utilizar
void setSurface(std::shared_ptr<Surface> surface) { surface_ = surface; }
};

View File

@@ -0,0 +1,241 @@
#include "core/rendering/text.hpp"
#include <SDL3/SDL.h>
#include <stddef.h> // Para size_t
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream
#include <iostream> // Para cerr
#include <stdexcept> // Para runtime_error
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/surface_sprite.hpp" // Para SSprite
#include "core/rendering/surface.hpp" // Para Surface
#include "utils/utils.hpp" // Para getFileName, stringToColor, printWithDots
// Llena una estructuta TextFile desde un fichero
std::shared_ptr<TextFile> loadTextFile(const std::string& file_path) {
auto tf = std::make_shared<TextFile>();
// Inicializa a cero el vector con las coordenadas
for (int i = 0; i < 128; ++i) {
tf->offset[i].x = 0;
tf->offset[i].y = 0;
tf->offset[i].w = 0;
tf->box_width = 0;
tf->box_height = 0;
}
// Abre el fichero para leer los valores
std::ifstream file(file_path);
if (file.is_open() && file.good()) {
std::string buffer;
// Lee los dos primeros valores del fichero
std::getline(file, buffer);
std::getline(file, buffer);
tf->box_width = std::stoi(buffer);
std::getline(file, buffer);
std::getline(file, buffer);
tf->box_height = std::stoi(buffer);
// lee el resto de datos del fichero
auto index = 32;
auto line_read = 0;
while (std::getline(file, buffer)) {
// Almacena solo las lineas impares
if (line_read % 2 == 1)
tf->offset[index++].w = std::stoi(buffer);
// Limpia el buffer
buffer.clear();
line_read++;
};
// Cierra el fichero
printWithDots("Text File : ", getFileName(file_path), "[ LOADED ]");
file.close();
}
// El fichero no se puede abrir
else {
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << std::endl;
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
}
// Establece las coordenadas para cada caracter ascii de la cadena y su ancho
for (int i = 32; i < 128; ++i) {
tf->offset[i].x = ((i - 32) % 15) * tf->box_width;
tf->offset[i].y = ((i - 32) / 15) * tf->box_height;
}
return tf;
}
// Constructor
Text::Text(std::shared_ptr<Surface> surface, const std::string& text_file) {
// Carga los offsets desde el fichero
auto tf = loadTextFile(text_file);
// Inicializa variables desde la estructura
box_height_ = tf->box_height;
box_width_ = tf->box_width;
for (int i = 0; i < 128; ++i) {
offset_[i].x = tf->offset[i].x;
offset_[i].y = tf->offset[i].y;
offset_[i].w = tf->offset[i].w;
}
// Crea los objetos
sprite_ = std::make_unique<SSprite>(surface, (SDL_FRect){0.0F, 0.0F, static_cast<float>(box_width_), static_cast<float>(box_height_)});
// Inicializa variables
fixed_width_ = false;
}
// Constructor
Text::Text(std::shared_ptr<Surface> surface, std::shared_ptr<TextFile> text_file) {
// Inicializa variables desde la estructura
box_height_ = text_file->box_height;
box_width_ = text_file->box_width;
for (int i = 0; i < 128; ++i) {
offset_[i].x = text_file->offset[i].x;
offset_[i].y = text_file->offset[i].y;
offset_[i].w = text_file->offset[i].w;
}
// Crea los objetos
sprite_ = std::make_unique<SSprite>(surface, (SDL_FRect){0.0F, 0.0F, static_cast<float>(box_width_), static_cast<float>(box_height_)});
// Inicializa variables
fixed_width_ = false;
}
// Escribe texto en pantalla
void Text::write(int x, int y, const std::string& text, int kerning, int lenght) {
int shift = 0;
if (lenght == -1)
lenght = text.length();
sprite_->setY(y);
for (int i = 0; i < lenght; ++i) {
auto index = static_cast<int>(text[i]);
sprite_->setClip(offset_[index].x, offset_[index].y, box_width_, box_height_);
sprite_->setX(x + shift);
sprite_->render(1, 15);
shift += offset_[static_cast<int>(text[i])].w + kerning;
}
}
// Escribe el texto en una surface
std::shared_ptr<Surface> Text::writeToSurface(const std::string& text, int zoom, int kerning) {
auto width = lenght(text, kerning) * zoom;
auto height = box_height_ * zoom;
auto surface = std::make_shared<Surface>(width, height);
auto previuos_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(surface);
surface->clear(stringToColor("transparent"));
write(0, 0, text, kerning);
Screen::get()->setRendererSurface(previuos_renderer);
return surface;
}
// Escribe el texto con extras en una surface
std::shared_ptr<Surface> Text::writeDXToSurface(Uint8 flags, const std::string& text, int kerning, Uint8 textColor, Uint8 shadow_distance, Uint8 shadow_color, int lenght) {
auto width = Text::lenght(text, kerning) + shadow_distance;
auto height = box_height_ + shadow_distance;
auto surface = std::make_shared<Surface>(width, height);
auto previuos_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(surface);
surface->clear(stringToColor("transparent"));
writeDX(flags, 0, 0, text, kerning, textColor, shadow_distance, shadow_color, lenght);
Screen::get()->setRendererSurface(previuos_renderer);
return surface;
}
// Escribe el texto con colores
void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int lenght) {
int shift = 0;
if (lenght == -1) {
lenght = text.length();
}
sprite_->setY(y);
for (int i = 0; i < lenght; ++i) {
auto index = static_cast<int>(text[i]);
sprite_->setClip(offset_[index].x, offset_[index].y, box_width_, box_height_);
sprite_->setX(x + shift);
sprite_->render(1, color);
shift += offset_[static_cast<int>(text[i])].w + kerning;
}
}
// Escribe el texto con sombra
void Text::writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance, int kerning, int lenght) {
writeColored(x + shadow_distance, y + shadow_distance, text, color, kerning, lenght);
write(x, y, text, kerning, lenght);
}
// Escribe el texto centrado en un punto x
void Text::writeCentered(int x, int y, const std::string& text, int kerning, int lenght) {
x -= (Text::lenght(text, kerning) / 2);
write(x, y, text, kerning, lenght);
}
// Escribe texto con extras
void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Uint8 textColor, Uint8 shadow_distance, Uint8 shadow_color, int lenght) {
const auto centered = ((flags & TEXT_CENTER) == TEXT_CENTER);
const auto shadowed = ((flags & TEXT_SHADOW) == TEXT_SHADOW);
const auto colored = ((flags & TEXT_COLOR) == TEXT_COLOR);
const auto stroked = ((flags & TEXT_STROKE) == TEXT_STROKE);
if (centered) {
x -= (Text::lenght(text, kerning) / 2);
}
if (shadowed) {
writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, lenght);
}
if (stroked) {
for (int dist = 1; dist <= shadow_distance; ++dist) {
for (int dy = -dist; dy <= dist; ++dy) {
for (int dx = -dist; dx <= dist; ++dx) {
writeColored(x + dx, y + dy, text, shadow_color, kerning, lenght);
}
}
}
}
if (colored) {
writeColored(x, y, text, textColor, kerning, lenght);
} else {
writeColored(x, y, text, textColor, kerning, lenght);
// write(x, y, text, kerning, lenght);
}
}
// Obtiene la longitud en pixels de una cadena
int Text::lenght(const std::string& text, int kerning) const {
int shift = 0;
for (size_t i = 0; i < text.length(); ++i)
shift += (offset_[static_cast<int>(text[i])].w + kerning);
// Descuenta el kerning del último caracter
return shift - kerning;
}
// Devuelve el valor de la variable
int Text::getCharacterSize() const {
return box_width_;
}
// Establece si se usa un tamaño fijo de letra
void Text::setFixedWidth(bool value) {
fixed_width_ = value;
}

View File

@@ -0,0 +1,78 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para string
#include "core/rendering/surface_sprite.hpp" // Para SSprite
class Surface; // lines 8-8
constexpr int TEXT_COLOR = 1;
constexpr int TEXT_SHADOW = 2;
constexpr int TEXT_CENTER = 4;
constexpr int TEXT_STROKE = 8;
struct TextOffset {
int x, y, w;
};
struct TextFile {
int box_width; // Anchura de la caja de cada caracter en el png
int box_height; // Altura de la caja de cada caracter en el png
TextOffset offset[128]; // Vector con las posiciones y ancho de cada letra
};
// Llena una estructuta TextFile desde un fichero
std::shared_ptr<TextFile> loadTextFile(const std::string& file_path);
// Clase texto. Pinta texto en pantalla a partir de un bitmap
class Text {
private:
// Objetos y punteros
std::unique_ptr<SSprite> sprite_ = nullptr; // Objeto con los graficos para el texto
// Variables
int box_width_ = 0; // Anchura de la caja de cada caracter en el png
int box_height_ = 0; // Altura de la caja de cada caracter en el png
bool fixed_width_ = false; // Indica si el texto se ha de escribir con longitud fija en todas las letras
TextOffset offset_[128] = {}; // Vector con las posiciones y ancho de cada letra
public:
// Constructor
Text(std::shared_ptr<Surface> surface, const std::string& text_file);
Text(std::shared_ptr<Surface> surface, std::shared_ptr<TextFile> text_file);
// Destructor
~Text() = default;
// Escribe el texto en pantalla
void write(int x, int y, const std::string& text, int kerning = 1, int lenght = -1);
// Escribe el texto en una textura
std::shared_ptr<Surface> writeToSurface(const std::string& text, int zoom = 1, int kerning = 1);
// Escribe el texto con extras en una textura
std::shared_ptr<Surface> writeDXToSurface(Uint8 flags, const std::string& text, int kerning = 1, Uint8 textColor = Uint8(), Uint8 shadow_distance = 1, Uint8 shadow_color = Uint8(), int lenght = -1);
// Escribe el texto con colores
void writeColored(int x, int y, const std::string& text, Uint8 color, int kerning = 1, int lenght = -1);
// Escribe el texto con sombra
void writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance = 1, int kerning = 1, int lenght = -1);
// Escribe el texto centrado en un punto x
void writeCentered(int x, int y, const std::string& text, int kerning = 1, int lenght = -1);
// Escribe texto con extras
void writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning = 1, Uint8 textColor = Uint8(), Uint8 shadow_distance = 1, Uint8 shadow_color = Uint8(), int lenght = -1);
// Obtiene la longitud en pixels de una cadena
int lenght(const std::string& text, int kerning = 1) const;
// Devuelve el valor de la variable
int getCharacterSize() const;
// Establece si se usa un tamaño fijo de letra
void setFixedWidth(bool value);
};

View File

@@ -0,0 +1,167 @@
#include "core/rendering/texture.hpp"
#include <SDL3/SDL.h>
#include <iostream> // Para basic_ostream, operator<<, endl, cout
#include <stdexcept> // Para runtime_error
#include <string> // Para char_traits, operator<<, string, opera...
#include <vector> // Para vector
#include "utils/utils.hpp" // Para getFileName, Color, printWithDots
#define STB_IMAGE_IMPLEMENTATION
#include "external/stb_image.h" // para stbi_failure_reason, stbi_image_free
// Constructor
Texture::Texture(SDL_Renderer* renderer, const std::string& path)
: renderer_(renderer),
path_(path) {
// Carga el fichero en la textura
if (!path_.empty()) {
// Obtiene la extensión
const std::string extension = path_.substr(path_.find_last_of(".") + 1);
// .png
if (extension == "png") {
loadFromFile(path_);
}
}
}
// Destructor
Texture::~Texture() {
unloadTexture();
palettes_.clear();
}
// Carga una imagen desde un fichero
bool Texture::loadFromFile(const std::string& file_path) {
if (file_path.empty()) {
return false;
}
int req_format = STBI_rgb_alpha;
int width, height, orig_format;
unsigned char* data = stbi_load(file_path.c_str(), &width, &height, &orig_format, req_format);
if (!data) {
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << std::endl;
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
} else {
printWithDots("Image : ", getFileName(file_path), "[ LOADED ]");
}
int pitch;
SDL_PixelFormat pixel_format;
// STBI_rgb_alpha (RGBA)
pitch = 4 * width;
pixel_format = SDL_PIXELFORMAT_RGBA32;
// Limpia
unloadTexture();
// La textura final
SDL_Texture* newTexture = nullptr;
// Carga la imagen desde una ruta específica
auto *loaded_surface = SDL_CreateSurfaceFrom(width, height, pixel_format, static_cast<void *>(data), pitch);
if (loaded_surface == nullptr) {
std::cout << "Unable to load image " << file_path << std::endl;
} else {
// Crea la textura desde los pixels de la surface
newTexture = SDL_CreateTextureFromSurface(renderer_, loaded_surface);
if (newTexture == nullptr) {
std::cout << "Unable to create texture from " << file_path << "! SDL Error: " << SDL_GetError() << std::endl;
} else {
// Obtiene las dimensiones de la imagen
width_ = loaded_surface->w;
height_ = loaded_surface->h;
}
// Elimina la textura cargada
SDL_DestroySurface(loaded_surface);
}
// Return success
stbi_image_free(data);
texture_ = newTexture;
return texture_ != nullptr;
}
// Crea una textura en blanco
auto Texture::createBlank(int width, int height, SDL_PixelFormat format, SDL_TextureAccess access) -> bool {
// Crea una textura sin inicializar
texture_ = SDL_CreateTexture(renderer_, format, access, width, height);
if (texture_ == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create blank texture! SDL Error: %s", SDL_GetError());
} else {
width_ = width;
height_ = height;
}
return texture_ != nullptr;
}
// Libera la memoria de la textura
void Texture::unloadTexture() {
// Libera la textura
if (texture_) {
SDL_DestroyTexture(texture_);
texture_ = nullptr;
width_ = 0;
height_ = 0;
}
}
// Establece el color para la modulacion
void Texture::setColor(Uint8 red, Uint8 green, Uint8 blue) { SDL_SetTextureColorMod(texture_, red, green, blue); }
void Texture::setColor(Color color) { SDL_SetTextureColorMod(texture_, color.r, color.g, color.b); }
// Establece el blending
void Texture::setBlendMode(SDL_BlendMode blending) { SDL_SetTextureBlendMode(texture_, blending); }
// Establece el alpha para la modulación
void Texture::setAlpha(Uint8 alpha) { SDL_SetTextureAlphaMod(texture_, alpha); }
// Renderiza la textura en un punto específico
void Texture::render(float x, float y, SDL_FRect* clip, float zoomW, float zoomH, double angle, SDL_FPoint* center, SDL_FlipMode flip) {
// Establece el destino de renderizado en la pantalla
SDL_FRect render_quad = {x, y, width_, height_};
// Obtiene las dimesiones del clip de renderizado
if (clip != nullptr) {
render_quad.w = clip->w;
render_quad.h = clip->h;
}
// Calcula el zoom y las coordenadas
if (zoomH != 1.0f || zoomW != 1.0f) {
render_quad.x = render_quad.x + (render_quad.w / 2);
render_quad.y = render_quad.y + (render_quad.h / 2);
render_quad.w = render_quad.w * zoomW;
render_quad.h = render_quad.h * zoomH;
render_quad.x = render_quad.x - (render_quad.w / 2);
render_quad.y = render_quad.y - (render_quad.h / 2);
}
// Renderiza a pantalla
SDL_RenderTextureRotated(renderer_, texture_, clip, &render_quad, angle, center, flip);
}
// Establece la textura como objetivo de renderizado
void Texture::setAsRenderTarget(SDL_Renderer* renderer) { SDL_SetRenderTarget(renderer, texture_); }
// Obtiene el ancho de la imagen
int Texture::getWidth() { return width_; }
// Obtiene el alto de la imagen
int Texture::getHeight() { return height_; }
// Recarga la textura
bool Texture::reLoad() { return loadFromFile(path_); }
// Obtiene la textura
SDL_Texture* Texture::getSDLTexture() { return texture_; }
// Obtiene el renderizador
SDL_Renderer* Texture::getRenderer() { return renderer_; }

View File

@@ -0,0 +1,67 @@
#pragma once
#include <SDL3/SDL.h>
#include <string> // Para string
#include <vector> // Para vector
struct Color; // lines 11-11
class Texture {
private:
// Objetos y punteros
SDL_Renderer* renderer_; // Renderizador donde dibujar la textura
SDL_Texture* texture_ = nullptr; // La textura
// Variables
std::string path_; // Ruta de la imagen de la textura
float width_ = 0.0F; // Ancho de la imagen
float height_ = 0.0F; // Alto de la imagen
std::vector<std::vector<Uint32>> palettes_; // Vector con las diferentes paletas
// Libera la memoria de la textura
void unloadTexture();
public:
// Constructor
explicit Texture(SDL_Renderer* renderer, const std::string& path = std::string());
// Destructor
~Texture();
// Carga una imagen desde un fichero
bool loadFromFile(const std::string& path);
// Crea una textura en blanco
auto createBlank(int width, int height, SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA8888, SDL_TextureAccess access = SDL_TEXTUREACCESS_STREAMING) -> bool;
// Establece el color para la modulacion
void setColor(Uint8 red, Uint8 green, Uint8 blue);
void setColor(Color color);
// Establece el blending
void setBlendMode(SDL_BlendMode blending);
// Establece el alpha para la modulación
void setAlpha(Uint8 alpha);
// Renderiza la textura en un punto específico
void render(float x, float y, SDL_FRect* clip = nullptr, float zoomW = 1, float zoomH = 1, double angle = 0.0, SDL_FPoint* center = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
// Establece la textura como objetivo de renderizado
void setAsRenderTarget(SDL_Renderer* renderer);
// Obtiene el ancho de la imagen
int getWidth();
// Obtiene el alto de la imagen
int getHeight();
// Recarga la textura
bool reLoad();
// Obtiene la textura
SDL_Texture* getSDLTexture();
// Obtiene el renderizador
SDL_Renderer* getRenderer();
};

View File

@@ -0,0 +1,179 @@
#include "core/resources/asset.hpp"
#include <algorithm> // Para find_if, max
#include <fstream> // Para basic_ostream, operator<<, basic_ifstream, endl
#include <iostream> // Para cout
#include <string> // Para allocator, char_traits, string, operator+, oper...
#include "utils/utils.hpp" // Para getFileName, printWithDots
// [SINGLETON] Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
Asset *Asset::asset_ = nullptr;
// [SINGLETON] Crearemos el objeto asset con esta función estática
void Asset::init(const std::string &executable_path)
{
Asset::asset_ = new Asset(executable_path);
}
// [SINGLETON] Destruiremos el objeto asset con esta función estática
void Asset::destroy()
{
delete Asset::asset_;
}
// [SINGLETON] Con este método obtenemos el objeto asset y podemos trabajar con él
Asset *Asset::get()
{
return Asset::asset_;
}
// Añade un elemento a la lista
void Asset::add(const std::string &file, AssetType type, bool required, bool absolute)
{
file_list_.emplace_back(absolute ? file : executable_path_ + file, type, required);
longest_name_ = std::max(longest_name_, static_cast<int>(file_list_.back().file.size()));
}
// Devuelve la ruta completa a un fichero a partir de una cadena
std::string Asset::get(const std::string &text) const
{
auto it = std::find_if(file_list_.begin(), file_list_.end(),
[&text](const auto &f)
{
return getFileName(f.file) == text;
});
if (it != file_list_.end())
{
return it->file;
}
else
{
std::cout << "Warning: file " << text << " not found" << std::endl;
return "";
}
}
// Comprueba que existen todos los elementos
bool Asset::check() const
{
bool success = true;
std::cout << "\n** CHECKING FILES" << std::endl;
// std::cout << "Executable path is: " << executable_path_ << std::endl;
// std::cout << "Sample filepath: " << file_list_.back().file << std::endl;
// Comprueba la lista de ficheros clasificandolos por tipo
for (int type = 0; type < static_cast<int>(AssetType::MAX_ASSET_TYPE); ++type)
{
// Comprueba si hay ficheros de ese tipo
bool any = false;
for (const auto &f : file_list_)
{
if (f.required && f.type == static_cast<AssetType>(type))
{
any = true;
}
}
// Si hay ficheros de ese tipo, comprueba si existen
if (any)
{
std::cout << "\n>> " << getTypeName(static_cast<AssetType>(type)).c_str() << " FILES" << std::endl;
for (const auto &f : file_list_)
{
if (f.required && f.type == static_cast<AssetType>(type))
{
success &= checkFile(f.file);
}
}
if (success)
std::cout << " All files are OK." << std::endl;
}
}
// Resultado
std::cout << (success ? "\n** CHECKING FILES COMPLETED.\n" : "\n** CHECKING FILES FAILED.\n") << std::endl;
return success;
}
// Comprueba que existe un fichero
bool Asset::checkFile(const std::string &path) const
{
std::ifstream file(path);
bool success = file.good();
file.close();
if (!success)
{
printWithDots("Checking file : ", getFileName(path), "[ ERROR ]");
}
return success;
}
// Devuelve el nombre del tipo de recurso
std::string Asset::getTypeName(AssetType type) const
{
switch (type)
{
case AssetType::DATA:
return "DATA";
break;
case AssetType::BITMAP:
return "BITMAP";
break;
case AssetType::ANIMATION:
return "ANIMATION";
break;
case AssetType::MUSIC:
return "MUSIC";
break;
case AssetType::SOUND:
return "SOUND";
break;
case AssetType::FONT:
return "FONT";
break;
case AssetType::ROOM:
return "ROOM";
break;
case AssetType::TILEMAP:
return "TILEMAP";
break;
case AssetType::PALETTE:
return "PALETTE";
break;
default:
return "ERROR";
break;
}
}
// Devuelve la lista de recursos de un tipo
std::vector<std::string> Asset::getListByType(AssetType type) const
{
std::vector<std::string> list;
for (auto f : file_list_)
{
if (f.type == type)
{
list.push_back(f.file);
}
}
return list;
}

View File

@@ -0,0 +1,79 @@
#pragma once
#include <string> // para string, basic_string
#include <vector> // para vector
#include "utils/utils.hpp"
enum class AssetType : int
{
DATA,
BITMAP,
ANIMATION,
MUSIC,
SOUND,
FONT,
ROOM,
TILEMAP,
PALETTE,
MAX_ASSET_TYPE
};
// Clase Asset
class Asset
{
private:
// [SINGLETON] Objeto asset privado para Don Melitón
static Asset *asset_;
// Estructura para definir un item
struct AssetItem
{
std::string file; // Ruta del fichero desde la raíz del directorio
AssetType type; // Indica el tipo de recurso
bool required; // Indica si es un fichero que debe de existir
// Constructor
AssetItem(const std::string &filePath, AssetType assetType, bool isRequired)
: file(filePath), type(assetType), required(isRequired) {}
};
// Variables
int longest_name_ = 0; // Contiene la longitud del nombre de fichero mas largo
std::vector<AssetItem> file_list_; // Listado con todas las rutas a los ficheros
std::string executable_path_; // Ruta al ejecutable
// Comprueba que existe un fichero
bool checkFile(const std::string &path) const;
// Devuelve el nombre del tipo de recurso
std::string getTypeName(AssetType type) const;
// Constructor
explicit Asset(const std::string &executable_path)
: executable_path_(getPath(executable_path)) {}
// Destructor
~Asset() = default;
public:
// [SINGLETON] Crearemos el objeto con esta función estática
static void init(const std::string &executable_path);
// [SINGLETON] Destruiremos el objeto con esta función estática
static void destroy();
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
static Asset *get();
// Añade un elemento a la lista
void add(const std::string &file, AssetType type, bool required = true, bool absolute = false);
// Devuelve la ruta completa a un fichero a partir de una cadena
std::string get(const std::string &text) const;
// Comprueba que existen todos los elementos
bool check() const;
// Devuelve la lista de recursos de un tipo
std::vector<std::string> getListByType(AssetType type) const;
};

View File

@@ -0,0 +1,418 @@
#include "core/resources/resource.hpp"
#include <SDL3/SDL.h>
#include <stdlib.h> // Para exit, size_t
#include <algorithm> // Para find_if
#include <iostream> // Para basic_ostream, operator<<, endl, cout
#include <stdexcept> // Para runtime_error
#include "core/resources/asset.hpp" // Para AssetType, Asset
#include "external/jail_audio.h" // Para JA_DeleteMusic, JA_DeleteSound, JA_Loa...
#include "game/gameplay/options.hpp" // Para Options, OptionsGame, options
#include "game/gameplay/room.hpp" // Para RoomData, loadRoomFile, loadRoomTileFile
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/text.hpp" // Para Text, loadTextFile
#include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor
struct JA_Music_t; // lines 17-17
struct JA_Sound_t; // lines 18-18
// [SINGLETON] Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
Resource* Resource::resource_ = nullptr;
// [SINGLETON] Crearemos el objeto screen con esta función estática
void Resource::init() { Resource::resource_ = new Resource(); }
// [SINGLETON] Destruiremos el objeto screen con esta función estática
void Resource::destroy() { delete Resource::resource_; }
// [SINGLETON] Con este método obtenemos el objeto screen y podemos trabajar con él
Resource* Resource::get() { return Resource::resource_; }
// Constructor
Resource::Resource() { load(); }
// Vacia todos los vectores de recursos
void Resource::clear() {
clearSounds();
clearMusics();
surfaces_.clear();
palettes_.clear();
text_files_.clear();
texts_.clear();
animations_.clear();
}
// Carga todos los recursos
void Resource::load() {
calculateTotal();
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
std::cout << "** LOADING RESOURCES" << std::endl;
loadSounds();
loadMusics();
loadSurfaces();
loadPalettes();
loadTextFiles();
loadAnimations();
loadTileMaps();
loadRooms();
createText();
std::cout << "\n** RESOURCES LOADED" << std::endl;
}
// Recarga todos los recursos
void Resource::reload() {
clear();
load();
}
// Obtiene el sonido a partir de un nombre
JA_Sound_t* Resource::getSound(const std::string& name) {
auto it = std::find_if(sounds_.begin(), sounds_.end(), [&name](const auto& s) { return s.name == name; });
if (it != sounds_.end()) {
return it->sound;
}
std::cerr << "Error: Sonido no encontrado " << name << std::endl;
throw std::runtime_error("Sonido no encontrado: " + name);
}
// Obtiene la música a partir de un nombre
JA_Music_t* Resource::getMusic(const std::string& name) {
auto it = std::find_if(musics_.begin(), musics_.end(), [&name](const auto& m) { return m.name == name; });
if (it != musics_.end()) {
return it->music;
}
std::cerr << "Error: Música no encontrada " << name << std::endl;
throw std::runtime_error("Música no encontrada: " + name);
}
// Obtiene la surface a partir de un nombre
std::shared_ptr<Surface> Resource::getSurface(const std::string& name) {
auto it = std::find_if(surfaces_.begin(), surfaces_.end(), [&name](const auto& t) { return t.name == name; });
if (it != surfaces_.end()) {
return it->surface;
}
std::cerr << "Error: Imagen no encontrada " << name << std::endl;
throw std::runtime_error("Imagen no encontrada: " + name);
}
// Obtiene la paleta a partir de un nombre
Palette Resource::getPalette(const std::string& name) {
auto it = std::find_if(palettes_.begin(), palettes_.end(), [&name](const auto& t) { return t.name == name; });
if (it != palettes_.end()) {
return it->palette;
}
std::cerr << "Error: Paleta no encontrada " << name << std::endl;
throw std::runtime_error("Paleta no encontrada: " + name);
}
// Obtiene el fichero de texto a partir de un nombre
std::shared_ptr<TextFile> Resource::getTextFile(const std::string& name) {
auto it = std::find_if(text_files_.begin(), text_files_.end(), [&name](const auto& t) { return t.name == name; });
if (it != text_files_.end()) {
return it->text_file;
}
std::cerr << "Error: TextFile no encontrado " << name << std::endl;
throw std::runtime_error("TextFile no encontrado: " + name);
}
// Obtiene el objeto de texto a partir de un nombre
std::shared_ptr<Text> Resource::getText(const std::string& name) {
auto it = std::find_if(texts_.begin(), texts_.end(), [&name](const auto& t) { return t.name == name; });
if (it != texts_.end()) {
return it->text;
}
std::cerr << "Error: Text no encontrado " << name << std::endl;
throw std::runtime_error("Texto no encontrado: " + name);
}
// Obtiene la animación a partir de un nombre
Animations& Resource::getAnimations(const std::string& name) {
auto it = std::find_if(animations_.begin(), animations_.end(), [&name](const auto& a) { return a.name == name; });
if (it != animations_.end()) {
return it->animation;
}
std::cerr << "Error: Animación no encontrada " << name << std::endl;
throw std::runtime_error("Animación no encontrada: " + name);
}
// Obtiene el mapa de tiles a partir de un nombre
std::vector<int>& Resource::getTileMap(const std::string& name) {
auto it = std::find_if(tile_maps_.begin(), tile_maps_.end(), [&name](const auto& t) { return t.name == name; });
if (it != tile_maps_.end()) {
return it->tileMap;
}
std::cerr << "Error: Mapa de tiles no encontrado " << name << std::endl;
throw std::runtime_error("Mapa de tiles no encontrado: " + name);
}
// Obtiene la habitación a partir de un nombre
std::shared_ptr<RoomData> Resource::getRoom(const std::string& name) {
auto it = std::find_if(rooms_.begin(), rooms_.end(), [&name](const auto& r) { return r.name == name; });
if (it != rooms_.end()) {
return it->room;
}
std::cerr << "Error: Habitación no encontrada " << name << std::endl;
throw std::runtime_error("Habitación no encontrada: " + name);
}
// Obtiene todas las habitaciones
std::vector<ResourceRoom>& Resource::getRooms() {
return rooms_;
}
// Carga los sonidos
void Resource::loadSounds() {
std::cout << "\n>> SOUND FILES" << std::endl;
auto list = Asset::get()->getListByType(AssetType::SOUND);
sounds_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
sounds_.emplace_back(ResourceSound(name, JA_LoadSound(l.c_str())));
printWithDots("Sound : ", name, "[ LOADED ]");
updateLoadingProgress();
}
}
// Carga las musicas
void Resource::loadMusics() {
std::cout << "\n>> MUSIC FILES" << std::endl;
auto list = Asset::get()->getListByType(AssetType::MUSIC);
musics_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
musics_.emplace_back(ResourceMusic(name, JA_LoadMusic(l.c_str())));
printWithDots("Music : ", name, "[ LOADED ]");
updateLoadingProgress(1);
}
}
// Carga las texturas
void Resource::loadSurfaces() {
std::cout << "\n>> SURFACES" << std::endl;
auto list = Asset::get()->getListByType(AssetType::BITMAP);
surfaces_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
surfaces_.emplace_back(ResourceSurface(name, std::make_shared<Surface>(l)));
surfaces_.back().surface->setTransparentColor(0);
updateLoadingProgress();
}
// Reconfigura el color transparente de algunas surfaces
getSurface("loading_screen_color.gif")->setTransparentColor();
getSurface("ending1.gif")->setTransparentColor();
getSurface("ending2.gif")->setTransparentColor();
getSurface("ending3.gif")->setTransparentColor();
getSurface("ending4.gif")->setTransparentColor();
getSurface("ending5.gif")->setTransparentColor();
getSurface("standard.gif")->setTransparentColor(16);
}
// Carga las paletas
void Resource::loadPalettes() {
std::cout << "\n>> PALETTES" << std::endl;
auto list = Asset::get()->getListByType(AssetType::PALETTE);
palettes_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
palettes_.emplace_back(ResourcePalette(name, readPalFile(l)));
updateLoadingProgress();
}
}
// Carga los ficheros de texto
void Resource::loadTextFiles() {
std::cout << "\n>> TEXT FILES" << std::endl;
auto list = Asset::get()->getListByType(AssetType::FONT);
text_files_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
text_files_.emplace_back(ResourceTextFile(name, loadTextFile(l)));
updateLoadingProgress();
}
}
// Carga las animaciones
void Resource::loadAnimations() {
std::cout << "\n>> ANIMATIONS" << std::endl;
auto list = Asset::get()->getListByType(AssetType::ANIMATION);
animations_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
animations_.emplace_back(ResourceAnimation(name, loadAnimationsFromFile(l)));
updateLoadingProgress();
}
}
// Carga los mapas de tiles
void Resource::loadTileMaps() {
std::cout << "\n>> TILE MAPS" << std::endl;
auto list = Asset::get()->getListByType(AssetType::TILEMAP);
tile_maps_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
tile_maps_.emplace_back(ResourceTileMap(name, loadRoomTileFile(l)));
printWithDots("TileMap : ", name, "[ LOADED ]");
updateLoadingProgress();
}
}
// Carga las habitaciones
void Resource::loadRooms() {
std::cout << "\n>> ROOMS" << std::endl;
auto list = Asset::get()->getListByType(AssetType::ROOM);
rooms_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
rooms_.emplace_back(ResourceRoom(name, std::make_shared<RoomData>(loadRoomFile(l))));
printWithDots("Room : ", name, "[ LOADED ]");
updateLoadingProgress();
}
}
void Resource::createText() {
struct ResourceInfo {
std::string key; // Identificador del recurso
std::string textureFile; // Nombre del archivo de textura
std::string textFile; // Nombre del archivo de texto
// Constructor para facilitar la creación de objetos ResourceInfo
ResourceInfo(const std::string& k, const std::string& tFile, const std::string& txtFile)
: key(k),
textureFile(tFile),
textFile(txtFile) {}
};
std::cout << "\n>> CREATING TEXT_OBJECTS" << std::endl;
std::vector<ResourceInfo> resources = {
{"debug", "debug.gif", "debug.txt"},
{"gauntlet", "gauntlet.gif", "gauntlet.txt"},
{"smb2", "smb2.gif", "smb2.txt"},
{"subatomic", "subatomic.gif", "subatomic.txt"},
{"8bithud", "8bithud.gif", "8bithud.txt"}};
for (const auto& resource : resources) {
texts_.emplace_back(ResourceText(resource.key, std::make_shared<Text>(getSurface(resource.textureFile), getTextFile(resource.textFile))));
printWithDots("Text : ", resource.key, "[ DONE ]");
}
}
// Vacía el vector de sonidos
void Resource::clearSounds() {
// Itera sobre el vector y libera los recursos asociados a cada JA_Sound_t
for (auto& sound : sounds_) {
if (sound.sound) {
JA_DeleteSound(sound.sound);
sound.sound = nullptr;
}
}
sounds_.clear(); // Limpia el vector después de liberar todos los recursos
}
// Vacía el vector de musicas
void Resource::clearMusics() {
// Itera sobre el vector y libera los recursos asociados a cada JA_Music_t
for (auto& music : musics_) {
if (music.music) {
JA_DeleteMusic(music.music);
music.music = nullptr;
}
}
musics_.clear(); // Limpia el vector después de liberar todos los recursos
}
// Calcula el numero de recursos para cargar
void Resource::calculateTotal() {
std::vector<AssetType> assetTypes = {
AssetType::SOUND,
AssetType::MUSIC,
AssetType::BITMAP,
AssetType::PALETTE,
AssetType::FONT,
AssetType::ANIMATION,
AssetType::TILEMAP,
AssetType::ROOM};
size_t total = 0;
for (const auto& assetType : assetTypes) {
auto list = Asset::get()->getListByType(assetType);
total += list.size();
}
count_ = ResourceCount(total, 0);
}
// Muestra el progreso de carga
void Resource::renderProgress() {
constexpr float X_PADDING = 10;
constexpr float Y_PADDING = 10;
constexpr float BAR_HEIGHT = 10;
const float bar_position = options.game.height - BAR_HEIGHT - Y_PADDING;
Screen::get()->start();
Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));
auto surface = Screen::get()->getRendererSurface();
const float WIRED_BAR_WIDTH = options.game.width - (X_PADDING * 2);
SDL_FRect rect_wired = {X_PADDING, bar_position, WIRED_BAR_WIDTH, X_PADDING};
surface->drawRectBorder(&rect_wired, static_cast<Uint8>(PaletteColor::WHITE));
const float FULL_BAR_WIDTH = WIRED_BAR_WIDTH * count_.getPercentage();
SDL_FRect rect_full = {X_PADDING, bar_position, FULL_BAR_WIDTH, X_PADDING};
surface->fillRect(&rect_full, static_cast<Uint8>(PaletteColor::WHITE));
Screen::get()->render();
}
// Comprueba los eventos de la pantalla de carga
void Resource::checkEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_QUIT:
exit(0);
break;
case SDL_EVENT_KEY_DOWN:
if (event.key.key == SDLK_ESCAPE) {
exit(0);
}
break;
}
}
}
// Actualiza el progreso de carga
void Resource::updateLoadingProgress(int steps) {
count_.add(1);
if (count_.loaded % steps == 0 || count_.loaded == count_.total) {
renderProgress();
}
checkEvents();
}

View File

@@ -0,0 +1,257 @@
#pragma once
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "game/gameplay/room.hpp" // Para room_t
#include "core/rendering/surface_animated_sprite.hpp" // Para AnimationsFileBuffer
#include "core/rendering/surface.hpp" // Para Surface
#include "core/rendering/text.hpp" // Para Text, TextFile
struct JA_Music_t; // lines 11-11
struct JA_Sound_t; // lines 12-12
// Estructura para almacenar ficheros de sonido y su nombre
struct ResourceSound {
std::string name; // Nombre del sonido
JA_Sound_t* sound; // Objeto con el sonido
// Constructor
ResourceSound(const std::string& name, JA_Sound_t* sound)
: name(name),
sound(sound) {}
};
// Estructura para almacenar ficheros musicales y su nombre
struct ResourceMusic {
std::string name; // Nombre de la musica
JA_Music_t* music; // Objeto con la música
// Constructor
ResourceMusic(const std::string& name, JA_Music_t* music)
: name(name),
music(music) {}
};
// Estructura para almacenar objetos Surface y su nombre
struct ResourceSurface {
std::string name; // Nombre de la surface
std::shared_ptr<Surface> surface; // Objeto con la surface
// Constructor
ResourceSurface(const std::string& name, std::shared_ptr<Surface> surface)
: name(name),
surface(surface) {}
};
// Estructura para almacenar objetos Palette y su nombre
struct ResourcePalette {
std::string name; // Nombre de la surface
Palette palette; // Paleta
// Constructor
ResourcePalette(const std::string& name, Palette palette)
: name(name),
palette(palette) {}
};
// Estructura para almacenar ficheros TextFile y su nombre
struct ResourceTextFile {
std::string name; // Nombre del fichero
std::shared_ptr<TextFile> text_file; // Objeto con los descriptores de la fuente de texto
// Constructor
ResourceTextFile(const std::string& name, std::shared_ptr<TextFile> text_file)
: name(name),
text_file(text_file) {}
};
// Estructura para almacenar objetos Text y su nombre
struct ResourceText {
std::string name; // Nombre del objeto
std::shared_ptr<Text> text; // Objeto
// Constructor
ResourceText(const std::string& name, std::shared_ptr<Text> text)
: name(name),
text(text) {}
};
// Estructura para almacenar ficheros animaciones y su nombre
struct ResourceAnimation {
std::string name; // Nombre del fichero
Animations animation; // Objeto con las animaciones
// Constructor
ResourceAnimation(const std::string& name, const Animations& animation)
: name(name),
animation(animation) {}
};
// Estructura para almacenar ficheros con el mapa de tiles de una habitación y su nombre
struct ResourceTileMap {
std::string name; // Nombre del mapa de tiles
std::vector<int> tileMap; // Vector con los indices del mapa de tiles
// Constructor
ResourceTileMap(const std::string& name, const std::vector<int>& tileMap)
: name(name),
tileMap(tileMap) {}
};
// Estructura para almacenar habitaciones y su nombre
struct ResourceRoom {
std::string name; // Nombre de la habitación
std::shared_ptr<RoomData> room; // Habitación
// Constructor
ResourceRoom(const std::string& name, std::shared_ptr<RoomData> room)
: name(name),
room(room) {}
};
// Estructura para llevar la cuenta de los recursos cargados
struct ResourceCount {
int total; // Número total de recursos
int loaded; // Número de recursos cargados
// Constructor
ResourceCount()
: total(0),
loaded(0) {}
// Constructor
ResourceCount(int total, int loaded)
: total(total),
loaded(loaded) {}
// Añade una cantidad a los recursos cargados
void add(int amount) {
loaded += amount;
}
// Obtiene el porcentaje de recursos cargados
float getPercentage() {
return static_cast<float>(loaded) / static_cast<float>(total);
}
};
class Resource {
private:
// [SINGLETON] Objeto resource privado para Don Melitón
static Resource* resource_;
std::vector<ResourceSound> sounds_; // Vector con los sonidos
std::vector<ResourceMusic> musics_; // Vector con las musicas
std::vector<ResourceSurface> surfaces_; // Vector con las surfaces
std::vector<ResourcePalette> palettes_; // Vector con las paletas
std::vector<ResourceTextFile> text_files_; // Vector con los ficheros de texto
std::vector<ResourceText> texts_; // Vector con los objetos de texto
std::vector<ResourceAnimation> animations_; // Vector con las animaciones
std::vector<ResourceTileMap> tile_maps_; // Vector con los mapas de tiles
std::vector<ResourceRoom> rooms_; // Vector con las habitaciones
ResourceCount count_; // Contador de recursos
// Carga los sonidos
void loadSounds();
// Carga las musicas
void loadMusics();
// Carga las surfaces
void loadSurfaces();
// Carga las paletas
void loadPalettes();
// Carga los ficheros de texto
void loadTextFiles();
// Carga las animaciones
void loadAnimations();
// Carga los mapas de tiles
void loadTileMaps();
// Carga las habitaciones
void loadRooms();
// Crea los objetos de texto
void createText();
// Vacia todos los vectores de recursos
void clear();
// Carga todos los recursos
void load();
// Vacía el vector de sonidos
void clearSounds();
// Vacía el vector de musicas
void clearMusics();
// Calcula el numero de recursos para cargar
void calculateTotal();
// Muestra el progreso de carga
void renderProgress();
// Comprueba los eventos
void checkEvents();
// Actualiza el progreso de carga
void updateLoadingProgress(int steps = 5);
// [SINGLETON] Ahora el constructor y el destructor son privados, para no poder crear objetos resource desde fuera
// Constructor
Resource();
// Destructor
~Resource() = default;
public:
// [SINGLETON] Crearemos el objeto resource con esta función estática
static void init();
// [SINGLETON] Destruiremos el objeto resource con esta función estática
static void destroy();
// [SINGLETON] Con este método obtenemos el objeto resource y podemos trabajar con él
static Resource* get();
// Obtiene el sonido a partir de un nombre
JA_Sound_t* getSound(const std::string& name);
// Obtiene la música a partir de un nombre
JA_Music_t* getMusic(const std::string& name);
// Obtiene la surface a partir de un nombre
std::shared_ptr<Surface> getSurface(const std::string& name);
// Obtiene la paleta a partir de un nombre
Palette getPalette(const std::string& name);
// Obtiene el fichero de texto a partir de un nombre
std::shared_ptr<TextFile> getTextFile(const std::string& name);
// Obtiene el objeto de texto a partir de un nombre
std::shared_ptr<Text> getText(const std::string& name);
// Obtiene la animación a partir de un nombre
Animations& getAnimations(const std::string& name);
// Obtiene el mapa de tiles a partir de un nombre
std::vector<int>& getTileMap(const std::string& name);
// Obtiene la habitación a partir de un nombre
std::shared_ptr<RoomData> getRoom(const std::string& name);
// Obtiene todas las habitaciones
std::vector<ResourceRoom>& getRooms();
// Recarga todos los recursos
void reload();
};

View File

@@ -0,0 +1,55 @@
#include "core/system/debug.hpp"
#include <algorithm> // Para max
#include <memory> // Para __shared_ptr_access, shared_ptr
#include "core/resources/resource.hpp" // Para Resource
#include "core/rendering/text.hpp" // Para Text
#include "utils/utils.hpp" // Para Color
// [SINGLETON]
Debug* Debug::debug_ = nullptr;
// [SINGLETON] Crearemos el objeto con esta función estática
void Debug::init() {
Debug::debug_ = new Debug();
}
// [SINGLETON] Destruiremos el objeto con esta función estática
void Debug::destroy() {
delete Debug::debug_;
}
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
Debug* Debug::get() {
return Debug::debug_;
}
// Dibuja en pantalla
void Debug::render() {
auto text = Resource::get()->getText("debug");
int y = y_;
int w = 0;
for (const auto& s : slot_) {
text->write(x_, y, s);
w = (std::max(w, (int)s.length()));
y += text->getCharacterSize() + 1;
if (y > 192 - text->getCharacterSize()) {
y = y_;
x_ += w * text->getCharacterSize() + 2;
}
}
y = 0;
for (const auto& l : log_) {
text->writeColored(x_ + 10, y, l, static_cast<Uint8>(PaletteColor::WHITE));
y += text->getCharacterSize() + 1;
}
}
// Establece la posición donde se colocará la información de debug
void Debug::setPos(SDL_FPoint p) {
x_ = p.x;
y_ = p.y;
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include <SDL3/SDL.h>
#include <string> // Para string
#include <vector> // Para vector
// Clase Debug
class Debug {
private:
// [SINGLETON] Objeto privado
static Debug* debug_;
// Variables
std::vector<std::string> slot_; // Vector con los textos a escribir
std::vector<std::string> log_; // Vector con los textos a escribir
int x_ = 0; // Posicion donde escribir el texto de debug
int y_ = 0; // Posición donde escribir el texto de debug
bool enabled_ = false; // Indica si esta activo el modo debug
// Constructor
Debug() = default;
// Destructor
~Debug() = default;
public:
// [SINGLETON] Crearemos el objeto con esta función estática
static void init();
// [SINGLETON] Destruiremos el objeto con esta función estática
static void destroy();
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
static Debug* get();
// Dibuja en pantalla
void render();
// Establece la posición donde se colocará la información de debug
void setPos(SDL_FPoint p);
// Getters
bool getEnabled() { return enabled_; }
// Setters
void add(std::string text) { slot_.push_back(text); }
void clear() { slot_.clear(); }
void addToLog(std::string text) { log_.push_back(text); }
void clearLog() { log_.clear(); }
void setEnabled(bool value) { enabled_ = value; }
void toggleEnabled() { enabled_ = !enabled_; }
};

View File

@@ -0,0 +1,589 @@
#include "core/system/director.hpp"
#include <SDL3/SDL.h>
#include <errno.h> // Para errno, EEXIST, EACCES, ENAMETOO...
#include <stdio.h> // Para printf, perror
#include <sys/stat.h> // Para mkdir, stat, S_IRWXU
#include <unistd.h> // Para getuid
#include <cstdlib> // Para exit, EXIT_FAILURE, srand
#include <iostream> // Para basic_ostream, operator<<, cout
#include <memory> // Para make_unique, unique_ptr
#include <string> // Para operator+, allocator, char_traits
#include "core/resources/asset.hpp" // Para Asset, AssetType
#include "game/gameplay/cheevos.hpp" // Para Cheevos
#include "core/system/debug.hpp" // Para Debug
#include "utils/defines.hpp" // Para WINDOW_CAPTION
#include "external/jail_audio.h" // Para JA_SetMusicVolume, JA_SetSoundV...
#include "core/input/input.hpp" // Para Input, InputAction
#include "game/gameplay/options.hpp" // Para Options, options, OptionsVideo
#include "core/resources/resource.hpp" // Para Resource
#include "core/rendering/screen.hpp" // Para Screen
#include "game/scenes/credits.hpp" // Para Credits
#include "game/scenes/ending.hpp" // Para Ending
#include "game/scenes/ending2.hpp" // Para Ending2
#include "game/scenes/game.hpp" // Para Game, GameMode
#include "game/scenes/game_over.hpp" // Para GameOver
#include "game/scenes/loading_screen.hpp" // Para LoadingScreen
#include "game/scenes/logo.hpp" // Para Logo
#include "game/scenes/title.hpp" // Para Title
#include "game/ui/notifier.hpp" // Para Notifier
#ifndef _WIN32
#include <pwd.h>
#endif
// Constructor
Director::Director(int argc, const char* argv[]) {
std::cout << "Game start" << std::endl;
// Crea e inicializa las opciones del programa
initOptions();
// Comprueba los parametros del programa
executable_path_ = checkProgramArguments(argc, argv);
// Crea el objeto que controla los ficheros de recursos
Asset::init(executable_path_);
// Crea la carpeta del sistema donde guardar datos
createSystemFolder("jailgames");
createSystemFolder("jailgames/jaildoctors_dilemma");
// Si falta algún fichero no inicia el programa
if (!setFileList()) {
exit(EXIT_FAILURE);
}
// Carga las opciones desde un fichero
loadOptionsFromFile(Asset::get()->get("config.txt"));
// Inicializa JailAudio
initJailAudio();
// Crea los objetos
Screen::init();
SDL_HideCursor();
Resource::init();
Notifier::init("", "8bithud");
Screen::get()->setNotificationsEnabled(true);
Input::init(Asset::get()->get("gamecontrollerdb.txt"));
initInput();
Debug::init();
Cheevos::init(Asset::get()->get("cheevos.bin"));
}
Director::~Director() {
// Guarda las opciones a un fichero
saveOptionsToFile(Asset::get()->get("config.txt"));
// Destruye los singletones
Cheevos::destroy();
Debug::destroy();
Input::destroy();
Notifier::destroy();
Resource::destroy();
Screen::destroy();
Asset::destroy();
SDL_Quit();
std::cout << "\nBye!" << std::endl;
}
// Comprueba los parametros del programa
std::string Director::checkProgramArguments(int argc, const char* argv[]) {
// Iterar sobre los argumentos del programa
for (int i = 1; i < argc; ++i) {
std::string argument(argv[i]);
if (argument == "--console") {
options.console = true;
} else if (argument == "--infiniteLives") {
options.cheats.infinite_lives = Cheat::CheatState::ENABLED;
} else if (argument == "--invincible") {
options.cheats.invincible = Cheat::CheatState::ENABLED;
} else if (argument == "--jailEnabled") {
options.cheats.jail_is_open = Cheat::CheatState::ENABLED;
} else if (argument == "--altSkin") {
options.cheats.alternate_skin = Cheat::CheatState::ENABLED;
}
}
return argv[0];
}
// Crea la carpeta del sistema donde guardar datos
void Director::createSystemFolder(const std::string& folder) {
#ifdef _WIN32
system_folder_ = std::string(getenv("APPDATA")) + "/" + folder;
#elif __APPLE__
struct passwd* pw = getpwuid(getuid());
const char* homedir = pw->pw_dir;
system_folder_ = std::string(homedir) + "/Library/Application Support" + "/" + folder;
#elif __linux__
struct passwd* pw = getpwuid(getuid());
const char* homedir = pw->pw_dir;
system_folder_ = std::string(homedir) + "/.config/" + folder;
{
// Intenta crear ".config", per si no existeix
std::string config_base_folder = std::string(homedir) + "/.config";
int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
if (ret == -1 && errno != EEXIST) {
printf("ERROR CREATING CONFIG BASE FOLDER.");
exit(EXIT_FAILURE);
}
}
#endif
struct stat st = {0};
if (stat(system_folder_.c_str(), &st) == -1) {
errno = 0;
#ifdef _WIN32
int ret = mkdir(system_folder_.c_str());
#else
int ret = mkdir(system_folder_.c_str(), S_IRWXU);
#endif
if (ret == -1) {
switch (errno) {
case EACCES:
printf("the parent directory does not allow write");
exit(EXIT_FAILURE);
case EEXIST:
printf("pathname already exists");
exit(EXIT_FAILURE);
case ENAMETOOLONG:
printf("pathname is too long");
exit(EXIT_FAILURE);
default:
perror("mkdir");
exit(EXIT_FAILURE);
}
}
}
}
// Inicia las variables necesarias para arrancar el programa
void Director::initInput() {
// Busca si hay un mando conectado
Input::get()->discoverGameControllers();
// Teclado - Movimiento
if (options.keys == ControlScheme::CURSOR) {
Input::get()->bindKey(InputAction::JUMP, SDL_SCANCODE_UP);
Input::get()->bindKey(InputAction::LEFT, SDL_SCANCODE_LEFT);
Input::get()->bindKey(InputAction::RIGHT, SDL_SCANCODE_RIGHT);
Input::get()->bindKey(InputAction::UP, SDL_SCANCODE_UP);
Input::get()->bindKey(InputAction::DOWN, SDL_SCANCODE_DOWN);
} else if (options.keys == ControlScheme::OPQA) {
Input::get()->bindKey(InputAction::JUMP, SDL_SCANCODE_Q);
Input::get()->bindKey(InputAction::LEFT, SDL_SCANCODE_O);
Input::get()->bindKey(InputAction::RIGHT, SDL_SCANCODE_P);
Input::get()->bindKey(InputAction::UP, SDL_SCANCODE_Q);
Input::get()->bindKey(InputAction::DOWN, SDL_SCANCODE_A);
} else if (options.keys == ControlScheme::WASD) {
Input::get()->bindKey(InputAction::JUMP, SDL_SCANCODE_W);
Input::get()->bindKey(InputAction::LEFT, SDL_SCANCODE_A);
Input::get()->bindKey(InputAction::RIGHT, SDL_SCANCODE_D);
Input::get()->bindKey(InputAction::UP, SDL_SCANCODE_W);
Input::get()->bindKey(InputAction::DOWN, SDL_SCANCODE_S);
}
// Teclado - Otros
Input::get()->bindKey(InputAction::ACCEPT, SDL_SCANCODE_RETURN);
Input::get()->bindKey(InputAction::CANCEL, SDL_SCANCODE_ESCAPE);
Input::get()->bindKey(InputAction::PAUSE, SDL_SCANCODE_H);
Input::get()->bindKey(InputAction::EXIT, SDL_SCANCODE_ESCAPE);
Input::get()->bindKey(InputAction::WINDOW_DEC_ZOOM, SDL_SCANCODE_F1);
Input::get()->bindKey(InputAction::WINDOW_INC_ZOOM, SDL_SCANCODE_F2);
Input::get()->bindKey(InputAction::TOGGLE_VIDEOMODE, SDL_SCANCODE_F3);
Input::get()->bindKey(InputAction::TOGGLE_SHADERS, SDL_SCANCODE_F4);
Input::get()->bindKey(InputAction::NEXT_PALETTE, SDL_SCANCODE_F5);
Input::get()->bindKey(InputAction::PREVIOUS_PALETTE, SDL_SCANCODE_F6);
Input::get()->bindKey(InputAction::TOGGLE_INTEGER_SCALE, SDL_SCANCODE_F7);
Input::get()->bindKey(InputAction::SHOW_DEBUG_INFO, SDL_SCANCODE_F12);
Input::get()->bindKey(InputAction::TOGGLE_MUSIC, SDL_SCANCODE_M);
Input::get()->bindKey(InputAction::TOGGLE_BORDER, SDL_SCANCODE_B);
// MANDO
const int NUM_GAMEPADS = Input::get()->getNumControllers();
for (int i = 0; i < NUM_GAMEPADS; ++i) {
// Movimiento
Input::get()->bindGameControllerButton(i, InputAction::JUMP, SDL_GAMEPAD_BUTTON_SOUTH);
Input::get()->bindGameControllerButton(i, InputAction::LEFT, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
Input::get()->bindGameControllerButton(i, InputAction::RIGHT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
// Otros
Input::get()->bindGameControllerButton(i, InputAction::ACCEPT, SDL_GAMEPAD_BUTTON_SOUTH);
Input::get()->bindGameControllerButton(i, InputAction::CANCEL, SDL_GAMEPAD_BUTTON_EAST);
#ifdef GAME_CONSOLE
Input::get()->bindGameControllerButton(i, InputAction::input_pause, SDL_GAMEPAD_BUTTON_BACK);
Input::get()->bindGameControllerButton(i, InputAction::input_exit, SDL_GAMEPAD_BUTTON_START);
#else
Input::get()->bindGameControllerButton(i, InputAction::PAUSE, SDL_GAMEPAD_BUTTON_START);
Input::get()->bindGameControllerButton(i, InputAction::EXIT, SDL_GAMEPAD_BUTTON_BACK);
#endif
Input::get()->bindGameControllerButton(i, InputAction::NEXT_PALETTE, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
Input::get()->bindGameControllerButton(i, InputAction::TOGGLE_MUSIC, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
Input::get()->bindGameControllerButton(i, InputAction::TOGGLE_BORDER, SDL_GAMEPAD_BUTTON_NORTH);
}
}
// Inicializa JailAudio
void Director::initJailAudio() {
if (!SDL_Init(SDL_INIT_AUDIO)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AUDIO could not initialize! SDL Error: %s", SDL_GetError());
} else {
JA_Init(48000, SDL_AUDIO_S16LE, 2);
if (options.audio.enabled) {
JA_SetMusicVolume(options.audio.music.volume);
JA_SetSoundVolume(options.audio.sound.volume);
} else {
JA_SetMusicVolume(0);
JA_SetSoundVolume(0);
}
}
}
// Crea el indice de ficheros
bool Director::setFileList() {
#ifdef MACOS_BUNDLE
const std::string prefix = "/../Resources";
#else
const std::string prefix = "";
#endif
// Texto
Asset::get()->add(prefix + "/data/font/smb2.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/font/smb2.txt", AssetType::FONT);
Asset::get()->add(prefix + "/data/font/debug.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/font/debug.txt", AssetType::FONT);
Asset::get()->add(prefix + "/data/font/gauntlet.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/font/gauntlet.txt", AssetType::FONT);
Asset::get()->add(prefix + "/data/font/subatomic.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/font/subatomic.txt", AssetType::FONT);
Asset::get()->add(prefix + "/data/font/8bithud.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/font/8bithud.txt", AssetType::FONT);
// Paletas
Asset::get()->add(prefix + "/data/palette/zx-spectrum.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/zx-spectrum-adjusted.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/zxarne-5-2.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/black-and-white.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/green-phosphor.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/orange-screen.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/ruzx-spectrum.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/ruzx-spectrum-revision-2.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/pico-8.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/sweetie-16.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/island-joy-16.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/lost-century.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/na16.pal", AssetType::PALETTE);
Asset::get()->add(prefix + "/data/palette/steam-lords.pal", AssetType::PALETTE);
// Shaders
Asset::get()->add(prefix + "/data/shaders/crtpi_vertex.glsl", AssetType::DATA);
Asset::get()->add(prefix + "/data/shaders/crtpi_fragment.glsl", AssetType::DATA);
Asset::get()->add(prefix + "/data/shaders/crtpi_vertex_es.glsl", AssetType::DATA);
Asset::get()->add(prefix + "/data/shaders/crtpi_fragment_es.glsl", AssetType::DATA);
// Datos
Asset::get()->add(prefix + "/data/input/gamecontrollerdb.txt", AssetType::DATA);
// Ficheros de sistema
Asset::get()->add(system_folder_ + "/config.txt", AssetType::DATA, false, true);
Asset::get()->add(system_folder_ + "/stats_buffer.csv", AssetType::DATA, false, true);
Asset::get()->add(system_folder_ + "/stats.csv", AssetType::DATA, false, true);
Asset::get()->add(system_folder_ + "/cheevos.bin", AssetType::DATA, false, true);
// Tilemaps y Rooms
for (int i = 1; i <= 60; ++i) {
std::string index = (i < 10 ? "0" : "") + std::to_string(i);
Asset::get()->add(prefix + "/data/room/" + index + ".tmx", AssetType::TILEMAP);
Asset::get()->add(prefix + "/data/room/" + index + ".room", AssetType::ROOM);
}
// Tilesets
Asset::get()->add(prefix + "/data/tilesets/standard.gif", AssetType::BITMAP);
// Enemigos
Asset::get()->add(prefix + "/data/enemies/abad_bell.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/abad_bell.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/abad.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/abad.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/amstrad_cs.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/amstrad_cs.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/flying_arounder.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/flying_arounder.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/stopped_arounder.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/stopped_arounder.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/walking_arounder.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/walking_arounder.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/arounders_door.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/arounders_door.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/arounders_machine.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/arounders_machine.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/bat.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/bat.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/batman_bell.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/batman_bell.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/batman_fire.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/batman_fire.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/batman.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/batman.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/bell.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/bell.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/bin.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/bin.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/bird.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/bird.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/breakout.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/breakout.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/bry.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/bry.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/chip.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/chip.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/code.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/code.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/congo.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/congo.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/crosshair.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/crosshair.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/demon.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/demon.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/dimallas.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/dimallas.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/floppy.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/floppy.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/dong.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/dong.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/guitar.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/guitar.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/heavy.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/heavy.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/jailer_#1.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/jailer_#1.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/jailer_#2.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/jailer_#2.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/jailer_#3.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/jailer_#3.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/jailbattle_alien.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/jailbattle_alien.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/jailbattle_human.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/jailbattle_human.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/jeannine.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/jeannine.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/lamp.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/lamp.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/lord_abad.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/lord_abad.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/matatunos.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/matatunos.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/mummy.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/mummy.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/paco.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/paco.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/elsa.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/elsa.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/qvoid.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/qvoid.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/robot.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/robot.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/sam.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/sam.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/shock.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/shock.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/sigmasua.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/sigmasua.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/spark.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/spark.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/special/aerojailer.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/special/aerojailer.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/special/arounder.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/special/arounder.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/special/pepe_rosita_job.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/special/pepe_rosita_job.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/special/shooting_star.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/special/shooting_star.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/spider.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/spider.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/tree_thing.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/tree_thing.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/tuno.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/tuno.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/tv_panel.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/tv_panel.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/tv.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/tv.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/upv_student.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/upv_student.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/wave.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/wave.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/enemies/z80.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/enemies/z80.gif", AssetType::BITMAP);
// Jugador
Asset::get()->add(prefix + "/data/player/player.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/player/player.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/player/player2.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/player/player2.ani", AssetType::ANIMATION);
Asset::get()->add(prefix + "/data/player/player_game_over.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/player/player_game_over.ani", AssetType::ANIMATION);
// Items
Asset::get()->add(prefix + "/data/items/items.gif", AssetType::BITMAP);
// Musicas
Asset::get()->add(prefix + "/data/music/title.ogg", AssetType::MUSIC);
Asset::get()->add(prefix + "/data/music/game.ogg", AssetType::MUSIC);
Asset::get()->add(prefix + "/data/music/loading_sound1.ogg", AssetType::MUSIC);
Asset::get()->add(prefix + "/data/music/loading_sound2.ogg", AssetType::MUSIC);
Asset::get()->add(prefix + "/data/music/loading_sound3.ogg", AssetType::MUSIC);
Asset::get()->add(prefix + "/data/music/ending1.ogg", AssetType::MUSIC);
Asset::get()->add(prefix + "/data/music/ending2.ogg", AssetType::MUSIC);
Asset::get()->add(prefix + "/data/music/game_over.ogg", AssetType::MUSIC);
// Efectos de sonido
Asset::get()->add(prefix + "/data/sound/item.wav", AssetType::SOUND);
Asset::get()->add(prefix + "/data/sound/death.wav", AssetType::SOUND);
Asset::get()->add(prefix + "/data/sound/notify.wav", AssetType::SOUND);
// Efectos de sonido para el salto
for (int i = 1; i <= 24; ++i) {
std::string jump_index = std::to_string(i);
Asset::get()->add(prefix + "/data/sound/jump" + jump_index + ".wav", AssetType::SOUND);
}
// Logo
Asset::get()->add(prefix + "/data/logo/jailgames.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/logo/since_1998.gif", AssetType::BITMAP);
// Loading
Asset::get()->add(prefix + "/data/loading/loading_screen_bn.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/loading/loading_screen_color.gif", AssetType::BITMAP);
// Title
Asset::get()->add(prefix + "/data/title/title_logo.gif", AssetType::BITMAP);
// Ending
Asset::get()->add(prefix + "/data/ending/ending1.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/ending/ending2.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/ending/ending3.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/ending/ending4.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/ending/ending5.gif", AssetType::BITMAP);
// Credits
Asset::get()->add(prefix + "/data/credits/shine.gif", AssetType::BITMAP);
Asset::get()->add(prefix + "/data/credits/shine.ani", AssetType::ANIMATION);
return Asset::get()->check();
}
// Ejecuta la seccion de juego con el logo
void Director::runLogo() {
auto logo = std::make_unique<Logo>();
logo->run();
}
// Ejecuta la seccion de juego de la pantalla de carga
void Director::runLoadingScreen() {
auto loadingScreen = std::make_unique<LoadingScreen>();
loadingScreen->run();
}
// Ejecuta la seccion de juego con el titulo y los menus
void Director::runTitle() {
auto title = std::make_unique<Title>();
title->run();
}
// Ejecuta la seccion de los creditos del juego
void Director::runCredits() {
auto credits = std::make_unique<Credits>();
credits->run();
}
// Ejecuta la seccion de la demo, donde se ven pantallas del juego
void Director::runDemo() {
auto game = std::make_unique<Game>(GameMode::DEMO);
game->run();
}
// Ejecuta la seccion del final del juego
void Director::runEnding() {
auto ending = std::make_unique<Ending>();
ending->run();
}
// Ejecuta la seccion del final del juego
void Director::runEnding2() {
auto ending2 = std::make_unique<Ending2>();
ending2->run();
}
// Ejecuta la seccion del final de la partida
void Director::runGameOver() {
auto gameOver = std::make_unique<GameOver>();
gameOver->run();
}
// Ejecuta la seccion de juego donde se juega
void Director::runGame() {
JA_StopMusic();
auto game = std::make_unique<Game>(GameMode::GAME);
game->run();
}
int Director::run() {
// Bucle principal
while (options.section.section != Section::QUIT) {
switch (options.section.section) {
case Section::LOGO:
runLogo();
break;
case Section::LOADING_SCREEN:
runLoadingScreen();
break;
case Section::TITLE:
runTitle();
break;
case Section::CREDITS:
runCredits();
break;
case Section::DEMO:
runDemo();
break;
case Section::GAME:
runGame();
break;
case Section::GAME_OVER:
runGameOver();
break;
case Section::ENDING:
runEnding();
break;
case Section::ENDING2:
runEnding2();
break;
default:
break;
}
}
return 0;
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include <SDL3/SDL.h>
#include <string> // Para string
class Director {
private:
// Variables
std::string executable_path_; // Path del ejecutable
std::string system_folder_; // Carpeta del sistema donde guardar datos
// Comprueba los parametros del programa
std::string checkProgramArguments(int argc, const char* argv[]);
// Crea la carpeta del sistema donde guardar datos
void createSystemFolder(const std::string& folder);
// Inicializa jail_audio
void initJailAudio();
// Inicializa el objeto Input
void initInput();
// Crea el indice de ficheros
bool setFileList();
// Ejecuta la seccion de juego con el logo
void runLogo();
// Ejecuta la seccion de juego de la pantalla de carga
void runLoadingScreen();
// Ejecuta la seccion de juego con el titulo y los menus
void runTitle();
// Ejecuta la seccion de los creditos del juego
void runCredits();
// Ejecuta la seccion de la demo, donde se ven pantallas del juego
void runDemo();
// Ejecuta la seccion del final del juego
void runEnding();
// Ejecuta la seccion del final del juego
void runEnding2();
// Ejecuta la seccion del final de la partida
void runGameOver();
// Ejecuta la seccion de juego donde se juega
void runGame();
public:
// Constructor
Director(int argc, const char* argv[]);
// Destructor
~Director();
// Bucle principal
int run();
};