Files
coffee_crisis_arcade_edition/source/screen.cpp

394 lines
12 KiB
C++

#include "screen.h"
#include <SDL2/SDL_pixels.h> // Para SDL_PIXELFORMAT_RGBA8888
#include <SDL2/SDL_timer.h> // Para SDL_GetTicks
#include <algorithm> // Para max, min
#include <fstream> // Para basic_ifstream, ifstream
#include <iterator> // Para istreambuf_iterator, operator==
#include <string> // Para allocator, char_traits, operator+, to_s...
#include "asset.h" // Para Asset
#include "dbgtxt.h" // Para dbg_print
#include "global_inputs.h" // Para service_pressed_counter
#include "jail_shader.h" // Para init, render
#include "mouse.h" // Para updateCursorVisibility
#include "notifier.h" // Para Notifier
#include "on_screen_help.h" // Para OnScreenHelp
#include "options.h" // Para Options, OptionsVideo, options, Options...
// [SINGLETON]
Screen *Screen::screen_ = nullptr;
// [SINGLETON] Crearemos el objeto con esta función estática
void Screen::init(SDL_Window *window, SDL_Renderer *renderer) { Screen::screen_ = new Screen(window, renderer); }
// [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(SDL_Window *window, SDL_Renderer *renderer)
: window_(window),
renderer_(renderer),
game_canvas_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
src_rect_({0, 0, param.game.width, param.game.height}),
dst_rect_({0, 0, param.game.width, param.game.height})
{
// Inicializa variables
SDL_DisplayMode DM;
SDL_GetCurrentDisplayMode(0, &DM);
info_resolution_ = std::to_string(DM.w) + " X " + std::to_string(DM.h) + " AT " + std::to_string(DM.refresh_rate) + " HZ";
adjustRenderLogicalSize();
// Muestra la ventana
show();
// Inicializa los shaders
initShaders(); // Se ha de ejecutar con la ventana visible
}
// Destructor
Screen::~Screen() { SDL_DestroyTexture(game_canvas_); }
// Limpia la pantalla
void Screen::clean(Color color)
{
SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 0xFF);
SDL_RenderClear(renderer_);
}
// Prepara para empezar a dibujar en la textura de juego
void Screen::start() { SDL_SetRenderTarget(renderer_, game_canvas_); }
// Vuelca el contenido del renderizador en pantalla
void Screen::render()
{
// Renderiza todos los overlays y efectos
renderOverlays();
// Renderiza el contenido del game_canvas_
renderScreen();
}
// Renderiza el contenido del game_canvas_
void Screen::renderScreen()
{
SDL_SetRenderTarget(renderer_, nullptr);
clean();
if (options.video.shaders)
{
shader::render();
}
else
{
SDL_RenderCopy(renderer_, game_canvas_, nullptr, nullptr);
SDL_RenderPresent(renderer_);
}
}
// Establece el modo de video
void Screen::setVideoMode(ScreenVideoMode video_mode)
{
// Actualiza las opciones
options.video.mode = video_mode;
// Configura el modo de pantalla
Uint32 flags = SDL_GetWindowFlags(window_);
if (flags != static_cast<Uint32>(options.video.mode))
{
SDL_SetWindowFullscreen(window_, static_cast<Uint32>(options.video.mode));
}
initShaders();
}
// Camibia entre pantalla completa y ventana
void Screen::toggleVideoMode()
{
options.video.mode = options.video.mode == ScreenVideoMode::WINDOW ? ScreenVideoMode::FULLSCREEN : ScreenVideoMode::WINDOW;
setVideoMode();
}
// Cambia el tamaño de la ventana
void Screen::setWindowZoom(int zoom)
{
options.video.window.zoom = zoom;
adjustWindowSize();
}
// Reduce el tamaño de la ventana
bool Screen::decWindowZoom()
{
if (options.video.mode == ScreenVideoMode::WINDOW)
{
const int PREVIOUS_ZOOM = options.video.window.zoom;
--options.video.window.zoom;
options.video.window.zoom = std::max(options.video.window.zoom, 1);
if (options.video.window.zoom != PREVIOUS_ZOOM)
{
adjustWindowSize();
return true;
}
}
return false;
}
// Aumenta el tamaño de la ventana
bool Screen::incWindowZoom()
{
if (options.video.mode == ScreenVideoMode::WINDOW)
{
const int PREVIOUS_ZOOM = options.video.window.zoom;
++options.video.window.zoom;
options.video.window.zoom = std::min(options.video.window.zoom, options.video.window.max_zoom);
if (options.video.window.zoom != PREVIOUS_ZOOM)
{
adjustWindowSize();
return true;
}
}
return false;
}
// Cambia el tipo de mezcla
void Screen::setBlendMode(SDL_BlendMode blendMode) { SDL_SetRenderDrawBlendMode(renderer_, blendMode); }
// Actualiza la lógica de la clase
void Screen::update()
{
updateShakeEffect();
updateFlash();
Notifier::get()->update();
updateFPS();
OnScreenHelp::get()->update();
Mouse::updateCursorVisibility();
}
// Agita la pantalla
void Screen::shake()
{
// Si no hay un shake effect activo, se guarda una copia de los valores actuales antes de modificarlos
if (!shake_effect_.enabled)
{
shake_effect_.enabled = true;
shake_effect_.originalPos = src_rect_.x;
shake_effect_.originalWidth = src_rect_.w;
src_rect_.w -= shake_effect_.desp;
dst_rect_.w = src_rect_.w;
}
// Si ya hay un shake effect en marcha no se pilla el origen, solo se renuevan los contadores
shake_effect_.remaining = shake_effect_.lenght;
shake_effect_.counter = shake_effect_.delay;
}
// Actualiza la logica para agitar la pantalla
void Screen::updateShakeEffect()
{
if (shake_effect_.enabled)
{
if (shake_effect_.counter > 0)
{
shake_effect_.counter--;
}
else
{
shake_effect_.counter = shake_effect_.delay;
const auto srcdesp = shake_effect_.remaining % 2 == 0 ? 0 : shake_effect_.desp;
const auto dstdesp = shake_effect_.remaining % 2 == 1 ? 0 : shake_effect_.desp;
src_rect_.x = shake_effect_.originalPos + srcdesp;
dst_rect_.x = shake_effect_.originalPos + dstdesp;
shake_effect_.remaining--;
shake_effect_.enabled = shake_effect_.remaining == -1 ? false : true;
if (!shake_effect_.enabled)
{
src_rect_.x = shake_effect_.originalPos;
src_rect_.w = shake_effect_.originalWidth;
dst_rect_ = src_rect_;
}
}
}
}
// Pone la pantalla de color
void Screen::flash(Color color, int lenght, int delay) { flash_effect_ = FlashEffect(true, lenght, delay, color); }
// Actualiza y dibuja el efecto de flash en la pantalla
void Screen::renderFlash()
{
if (flash_effect_.isRendarable())
{
SDL_SetRenderDrawColor(renderer_, flash_effect_.color.r, flash_effect_.color.g, flash_effect_.color.b, 0xFF);
SDL_RenderClear(renderer_);
}
}
// Actualiza el efecto de flash
void Screen::updateFlash() { flash_effect_.update(); }
// Atenua la pantalla
void Screen::renderAttenuate()
{
if (attenuate_effect_)
{
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 64);
SDL_RenderFillRect(renderer_, nullptr);
}
}
// Aplica el efecto de agitar la pantalla
void Screen::renderShake()
{
if (shake_effect_.enabled)
{
// Guarda el renderizador actual para dejarlo despues como estaba
auto current_target = SDL_GetRenderTarget(renderer_);
// Crea una textura temporal
auto temp_texture = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
// Vuelca game_canvas_ a la textura temporal
SDL_SetRenderTarget(renderer_, temp_texture);
SDL_RenderCopy(renderer_, game_canvas_, nullptr, nullptr);
// Vuelca textura temporal a game_canvas_
SDL_SetRenderTarget(renderer_, game_canvas_);
SDL_RenderCopy(renderer_, temp_texture, &src_rect_, &dst_rect_);
// Elimina la textura temporal
SDL_DestroyTexture(temp_texture);
// Restaura el renderizador de destino original
SDL_SetRenderTarget(renderer_, current_target);
}
}
// Activa / desactiva los shaders
void Screen::toggleShaders()
{
options.video.shaders = !options.video.shaders;
}
// Activa / desactiva la información de debug
void Screen::toggleDebugInfo() { show_debug_info_ = !show_debug_info_; }
// Atenua la pantalla
void Screen::attenuate(bool value) { attenuate_effect_ = value; }
// Calcula los frames por segundo
void Screen::updateFPS()
{
if (SDL_GetTicks() - fps_ticks_ > 1000)
{
fps_ticks_ = SDL_GetTicks();
fps_ = fps_counter_;
fps_counter_ = 0;
}
}
// Muestra información por pantalla
void Screen::renderInfo()
{
if (show_debug_info_)
{
// FPS
const std::string fpstext = std::to_string(fps_) + " FPS";
dbg_print(param.game.width - fpstext.length() * 8, 0, fpstext.c_str(), 255, 255, 0);
// Resolution
dbg_print(0, 0, info_resolution_.c_str(), 255, 255, 0);
// Contador de service_pressed_counter
if (const int counter = globalInputs::service_pressed_counter; counter > 0)
{
dbg_print(0, 8, std::to_string(counter).c_str(), 255, 0, 255);
}
}
}
// Carga el contenido del archivo GLSL
void Screen::loadShaders()
{
const std::string GLSL_FILE = param.game.game_area.rect.h == 256 ? "crtpi_256.glsl" : "crtpi_240.glsl";
std::ifstream f(Asset::get()->get(GLSL_FILE).c_str());
shaderSource = std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
}
// Inicializa los shaders
void Screen::initShaders()
{
if (shaderSource.empty())
{
loadShaders();
}
shader::init(window_, game_canvas_, shaderSource);
}
// Calcula el tamaño de la ventana
void Screen::adjustWindowSize()
{
options.video.window.max_zoom = getMaxZoom();
// Establece el nuevo tamaño
if (options.video.mode == ScreenVideoMode::WINDOW)
{
const int WIDTH = param.game.width * options.video.window.zoom;
const int HEIGHT = param.game.height * options.video.window.zoom;
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 - WIDTH) / 2;
const int NEW_POS_Y = old_pos_y + (old_height - HEIGHT) / 2;
SDL_Rect viewport = {0, 0, WIDTH, HEIGHT};
SDL_RenderSetViewport(renderer_, &viewport);
SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS_), std::max(NEW_POS_Y, 0));
SDL_SetWindowSize(window_, WIDTH, HEIGHT);
initShaders();
}
}
// Ajusta el tamaño lógico del renderizador
void Screen::adjustRenderLogicalSize() { SDL_RenderSetLogicalSize(renderer_, param.game.width, param.game.height); }
// Obtiene el tamaño máximo de zoom posible para la ventana
int Screen::getMaxZoom()
{
// Obtiene información sobre la pantalla
SDL_DisplayMode DM;
SDL_GetCurrentDisplayMode(0, &DM);
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
const int MAX_ZOOM = std::min(DM.w / param.game.width, (DM.h - WINDOWS_DECORATIONS_) / param.game.height);
// Normaliza los valores de zoom
options.video.window.zoom = std::min(options.video.window.zoom, MAX_ZOOM);
return MAX_ZOOM;
}
// Renderiza todos los overlays y efectos
void Screen::renderOverlays()
{
// Actualiza el contador de FPS
++fps_counter_;
// Dibuja efectos y elementos sobre el game_canvas_
renderShake();
renderFlash();
renderAttenuate();
OnScreenHelp::get()->render();
renderInfo();
Notifier::get()->render();
}