soport de gamepad per a wasm

This commit is contained in:
2026-04-13 13:20:50 +02:00
parent d9c41f420b
commit 7f470361cc
15 changed files with 128 additions and 32 deletions

View File

@@ -302,6 +302,22 @@ wasm:
cp build/wasm/$(TARGET_NAME).data $(DIST_DIR)/wasm/ cp build/wasm/$(TARGET_NAME).data $(DIST_DIR)/wasm/
@echo "Output: $(DIST_DIR)/wasm/" @echo "Output: $(DIST_DIR)/wasm/"
# Versió Debug del build wasm: arrenca directament a la GAME (sense logo/loading/title)
# i activa l'editor i la consola. Sortida a dist/wasm_debug/.
wasm_debug:
@echo "Compilando WebAssembly Debug - Version: $(VERSION)"
docker run --rm \
-v $(DIR_ROOT):/src \
-w /src \
emscripten/emsdk:latest \
bash -c "emcmake cmake -S . -B build/wasm_debug -DCMAKE_BUILD_TYPE=Debug && cmake --build build/wasm_debug"
$(MKDIR) "$(DIST_DIR)/wasm_debug"
cp build/wasm_debug/$(TARGET_NAME).html $(DIST_DIR)/wasm_debug/
cp build/wasm_debug/$(TARGET_NAME).js $(DIST_DIR)/wasm_debug/
cp build/wasm_debug/$(TARGET_NAME).wasm $(DIST_DIR)/wasm_debug/
cp build/wasm_debug/$(TARGET_NAME).data $(DIST_DIR)/wasm_debug/
@echo "Output: $(DIST_DIR)/wasm_debug/"
# ============================================================================== # ==============================================================================
# REGLAS ESPECIALES # REGLAS ESPECIALES
# ============================================================================== # ==============================================================================
@@ -324,6 +340,7 @@ help:
@echo " make linux_release - Crear release para Linux" @echo " make linux_release - Crear release para Linux"
@echo " make macos_release - Crear release para macOS" @echo " make macos_release - Crear release para macOS"
@echo " make wasm - Crear release per a WebAssembly (requereix Docker)" @echo " make wasm - Crear release per a WebAssembly (requereix Docker)"
@echo " make wasm_debug - Crear build Debug per a WebAssembly (entra directe a la GAME)"
@echo "" @echo ""
@echo " Herramientas:" @echo " Herramientas:"
@echo " make compile_shaders - Compilar shaders SPIR-V" @echo " make compile_shaders - Compilar shaders SPIR-V"
@@ -334,4 +351,4 @@ help:
@echo " make show_version - Mostrar version actual ($(VERSION))" @echo " make show_version - Mostrar version actual ($(VERSION))"
@echo " make help - Mostrar esta ayuda" @echo " make help - Mostrar esta ayuda"
.PHONY: all debug release windows_release macos_release linux_release wasm compile_shaders pack_tool resources.pack show_version help .PHONY: all debug release windows_release macos_release linux_release wasm wasm_debug compile_shaders pack_tool resources.pack show_version help

View File

@@ -8,6 +8,7 @@ title:
keyboard: "2. REDEFINIR TECLES" keyboard: "2. REDEFINIR TECLES"
joystick: "3. REDEFINIR MANDO" joystick: "3. REDEFINIR MANDO"
projects: "4. PROJECTES" projects: "4. PROJECTES"
press_to_play: "PREM PER JUGAR"
keys: keys:
prompt0: "PREM UNA TECLA PER A ESQUERRA" prompt0: "PREM UNA TECLA PER A ESQUERRA"
prompt1: "PREM UNA TECLA PER A DRETA" prompt1: "PREM UNA TECLA PER A DRETA"
@@ -103,6 +104,8 @@ achievements:
ui: ui:
press_again_menu: "PREM DE NOU PER TORNAR AL MENÚ" press_again_menu: "PREM DE NOU PER TORNAR AL MENÚ"
press_again_exit: "PREM DE NOU PER EIXIR" press_again_exit: "PREM DE NOU PER EIXIR"
gamepad_connected: "CONNECTAT"
gamepad_disconnected: "DESCONNECTAT"
border_enabled: "VORA ACTIVADA" border_enabled: "VORA ACTIVADA"
border_disabled: "VORA DESACTIVADA" border_disabled: "VORA DESACTIVADA"
fullscreen_enabled: "PANTALLA COMPLETA ACTIVADA" fullscreen_enabled: "PANTALLA COMPLETA ACTIVADA"

View File

@@ -8,6 +8,7 @@ title:
keyboard: "2. REDEFINE KEYBOARD" keyboard: "2. REDEFINE KEYBOARD"
joystick: "3. REDEFINE JOYSTICK" joystick: "3. REDEFINE JOYSTICK"
projects: "4. PROJECTS" projects: "4. PROJECTS"
press_to_play: "PRESS TO PLAY"
keys: keys:
prompt0: "PRESS KEY FOR LEFT" prompt0: "PRESS KEY FOR LEFT"
prompt1: "PRESS KEY FOR RIGHT" prompt1: "PRESS KEY FOR RIGHT"
@@ -103,6 +104,8 @@ achievements:
ui: ui:
press_again_menu: "PRESS AGAIN TO RETURN TO MENU" press_again_menu: "PRESS AGAIN TO RETURN TO MENU"
press_again_exit: "PRESS AGAIN TO EXIT" press_again_exit: "PRESS AGAIN TO EXIT"
gamepad_connected: "CONNECTED"
gamepad_disconnected: "DISCONNECTED"
border_enabled: "BORDER ENABLED" border_enabled: "BORDER ENABLED"
border_disabled: "BORDER DISABLED" border_disabled: "BORDER DISABLED"
fullscreen_enabled: "FULLSCREEN ENABLED" fullscreen_enabled: "FULLSCREEN ENABLED"

View File

@@ -9,6 +9,7 @@
#include "core/locale/locale.hpp" // Para Locale #include "core/locale/locale.hpp" // Para Locale
#include "core/rendering/render_info.hpp" // Para RenderInfo #include "core/rendering/render_info.hpp" // Para RenderInfo
#include "core/rendering/screen.hpp" // Para Screen #include "core/rendering/screen.hpp" // Para Screen
#include "core/system/global_events.hpp" // Para GlobalEvents::consumeGamepadButtonPressed
#include "game/options.hpp" // Para Options, options, OptionsVideo, Section #include "game/options.hpp" // Para Options, options, OptionsVideo, Section
#include "game/scene_manager.hpp" // Para SceneManager #include "game/scene_manager.hpp" // Para SceneManager
#include "game/ui/console.hpp" // Para Console #include "game/ui/console.hpp" // Para Console
@@ -152,6 +153,12 @@ namespace GlobalInputs {
// Detecta qué acción global ha sido presionada (si alguna) // Detecta qué acción global ha sido presionada (si alguna)
auto getPressedAction() -> InputAction { // NOLINT(readability-function-cognitive-complexity) auto getPressedAction() -> InputAction { // NOLINT(readability-function-cognitive-complexity)
// Qualsevol botó del comandament actua com a ACCEPT (saltar escenes
// d'attract mode: logo, loading, credits, demo, ending...). Es prioritza
// sobre EXIT perquè s'envia com a flag d'event, no com a check d'acció.
if (GlobalEvents::consumeGamepadButtonPressed()) {
return InputAction::ACCEPT;
}
if (Input::get()->checkAction(InputAction::EXIT, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::EXIT, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::EXIT; return InputAction::EXIT;
} }

View File

@@ -421,7 +421,13 @@ auto Input::addGamepad(int device_index) -> std::string { // NOLINT(readability
auto name = gamepad->name; auto name = gamepad->name;
std::cout << "Gamepad connected (" << name << ")" << '\n'; std::cout << "Gamepad connected (" << name << ")" << '\n';
gamepads_.push_back(std::move(gamepad)); gamepads_.push_back(std::move(gamepad));
return name + " CONNECTED";
// Aplica els bindings d'Options al nou gamepad (en hot-plug/wasm el ctor
// ja ha cridat applyGamepadBindingsFromOptions però llavors gamepads_
// estava buit i no s'ha fet res).
applyGamepadBindingsFromOptions();
return name;
} }
auto Input::removeGamepad(SDL_JoystickID id) -> std::string { // NOLINT(readability-convert-member-functions-to-static) auto Input::removeGamepad(SDL_JoystickID id) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
@@ -433,7 +439,7 @@ auto Input::removeGamepad(SDL_JoystickID id) -> std::string { // NOLINT(readabi
std::string name = (*it)->name; std::string name = (*it)->name;
std::cout << "Gamepad disconnected (" << name << ")" << '\n'; std::cout << "Gamepad disconnected (" << name << ")" << '\n';
gamepads_.erase(it); gamepads_.erase(it);
return name + " DISCONNECTED"; return name;
} }
std::cerr << "No se encontró el gamepad con ID " << id << '\n'; std::cerr << "No se encontró el gamepad con ID " << id << '\n';
return {}; return {};

View File

@@ -11,19 +11,19 @@
#include <iterator> // Para istreambuf_iterator, operator== #include <iterator> // Para istreambuf_iterator, operator==
#include <string> // Para char_traits, string, operator+, operator== #include <string> // Para char_traits, string, operator+, operator==
#include "core/input/mouse.hpp" // Para updateCursorVisibility #include "core/input/mouse.hpp" // Para updateCursorVisibility
#include "core/rendering/render_info.hpp" // Para RenderInfo #include "core/rendering/render_info.hpp" // Para RenderInfo
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader (no suportat a WebGL2) #include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader (no suportat a WebGL2)
#endif #endif
#include "core/rendering/surface.hpp" // Para Surface, readPalFile #include "core/rendering/surface.hpp" // Para Surface, readPalFile
#include "core/rendering/text.hpp" // Para Text #include "core/rendering/text.hpp" // Para Text
#include "core/resources/resource_cache.hpp" // Para Resource #include "core/resources/resource_cache.hpp" // Para Resource
#include "core/resources/resource_helper.hpp" // Para ResourceHelper #include "core/resources/resource_helper.hpp" // Para ResourceHelper
#include "core/resources/resource_list.hpp" // Para Asset, AssetType #include "core/resources/resource_list.hpp" // Para Asset, AssetType
#include "game/options.hpp" // Para Options, options, OptionsVideo, Border #include "game/options.hpp" // Para Options, options, OptionsVideo, Border
#include "game/ui/console.hpp" // Para Console #include "game/ui/console.hpp" // Para Console
#include "game/ui/notifier.hpp" // Para Notifier #include "game/ui/notifier.hpp" // Para Notifier
// [SINGLETON] // [SINGLETON]
Screen* Screen::screen = nullptr; Screen* Screen::screen = nullptr;

View File

@@ -152,7 +152,9 @@ Director::Director() {
// i desactivem el borde per aprofitar al màxim l'espai del canvas. // i desactivem el borde per aprofitar al màxim l'espai del canvas.
Options::video.fullscreen = false; Options::video.fullscreen = false;
Options::window.zoom = 4; Options::window.zoom = 4;
Options::video.border.enabled = false; Options::video.border.enabled = true;
Options::video.border.height = 8;
Options::video.border.width = 8;
#endif #endif
// Configura la ruta y carga los presets de PostFX // Configura la ruta y carga los presets de PostFX
@@ -197,9 +199,15 @@ Director::Director() {
#ifdef _DEBUG #ifdef _DEBUG
Debug::init(); Debug::init();
#ifdef __EMSCRIPTEN__
// A wasm el debug.yaml viu a SYSTEM_FOLDER (MEMFS no persistent) i no està
// disponible. Saltem el loadFromFile i entrem directament a la GAME.
SceneManager::current = SceneManager::Scene::GAME;
#else
Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml")); Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml"));
Debug::get()->loadFromFile(); Debug::get()->loadFromFile();
SceneManager::current = Debug::get()->getInitialScene(); SceneManager::current = Debug::get()->getInitialScene();
#endif
MapEditor::init(); MapEditor::init();
#endif #endif

View File

@@ -22,8 +22,8 @@ class Director {
std::string executable_path_; // Path del ejecutable std::string executable_path_; // Path del ejecutable
std::string system_folder_; // Carpeta del sistema donde guardar datos std::string system_folder_; // Carpeta del sistema donde guardar datos
std::unique_ptr<Scene> active_scene_; // Escena activa std::unique_ptr<Scene> active_scene_; // Escena activa
SceneManager::Scene current_scene_{SceneManager::Scene::LOGO}; // Tipus d'escena activa SceneManager::Scene current_scene_{SceneManager::Scene::LOGO}; // Tipus d'escena activa
// --- Funciones --- // --- Funciones ---
void createSystemFolder(const std::string& folder); // Crea la carpeta del sistema donde guardar datos void createSystemFolder(const std::string& folder); // Crea la carpeta del sistema donde guardar datos

View File

@@ -1,10 +1,20 @@
#include "core/system/global_events.hpp" #include "core/system/global_events.hpp"
#include "core/input/input.hpp" // Para Input (gamepad add/remove)
#include "core/input/mouse.hpp" #include "core/input/mouse.hpp"
#include "game/options.hpp" // Para Options, options, OptionsGame, OptionsAudio #include "core/locale/locale.hpp" // Para Locale
#include "game/ui/console.hpp" // Para Console #include "game/options.hpp" // Para Options, options, OptionsGame, OptionsAudio
#include "game/ui/console.hpp" // Para Console
#include "game/ui/notifier.hpp" // Para Notifier
namespace GlobalEvents { namespace GlobalEvents {
namespace {
// Flag per saber si en aquest frame s'ha rebut un button down del gamepad.
// El consumeix GlobalInputs perquè un botó del comandament salti escenes.
bool gamepad_button_pressed_ = false;
} // namespace
// Comprueba los eventos que se pueden producir en cualquier sección del juego. // Comprueba los eventos que se pueden producir en cualquier sección del juego.
// Nota: SDL_EVENT_QUIT el gestiona Director::handleEvent() directament. // Nota: SDL_EVENT_QUIT el gestiona Director::handleEvent() directament.
void handle(const SDL_Event& event) { void handle(const SDL_Event& event) {
@@ -12,6 +22,28 @@ namespace GlobalEvents {
// reLoadTextures(); // reLoadTextures();
} }
// Connexió/desconnexió de gamepads: cal enrutar-los a Input perquè
// afegisca el dispositiu a gamepads_. Sense això, en wasm els gamepads
// mai es detecten (la Gamepad API del navegador només els exposa
// després que l'usuari els active, més tard que el discoverGamepads
// inicial). En desktop també arregla la connexió en calent.
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED) {
if (Input::get() != nullptr) {
std::string name = Input::get()->handleEvent(event);
if (!name.empty() && Notifier::get() != nullptr && Locale::get() != nullptr) {
const std::string KEY = (event.type == SDL_EVENT_GAMEPAD_ADDED)
? "ui.gamepad_connected"
: "ui.gamepad_disconnected";
Notifier::get()->show({name + " " + Locale::get()->get(KEY)});
}
}
}
// Marcar polsació de qualsevol botó del comandament (els consumirà GlobalInputs).
if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
gamepad_button_pressed_ = true;
}
// Enrutar eventos de texto a la consola cuando está activa // Enrutar eventos de texto a la consola cuando está activa
if (Console::get() != nullptr && Console::get()->isActive()) { if (Console::get() != nullptr && Console::get()->isActive()) {
if (event.type == SDL_EVENT_TEXT_INPUT || event.type == SDL_EVENT_KEY_DOWN) { if (event.type == SDL_EVENT_TEXT_INPUT || event.type == SDL_EVENT_KEY_DOWN) {
@@ -22,4 +54,10 @@ namespace GlobalEvents {
Mouse::handleEvent(event); Mouse::handleEvent(event);
} }
auto consumeGamepadButtonPressed() -> bool {
const bool RESULT = gamepad_button_pressed_;
gamepad_button_pressed_ = false;
return RESULT;
}
} // namespace GlobalEvents } // namespace GlobalEvents

View File

@@ -5,4 +5,9 @@
namespace GlobalEvents { namespace GlobalEvents {
// Comprueba los eventos que se pueden producir en cualquier sección del juego // Comprueba los eventos que se pueden producir en cualquier sección del juego
void handle(const SDL_Event& event); void handle(const SDL_Event& event);
// True si en aquest frame s'ha rebut un SDL_EVENT_GAMEPAD_BUTTON_DOWN.
// Es consumeix (i es reseteja) per GlobalInputs::getPressedAction perquè
// qualsevol botó del comandament actuï com a "ACCEPT" (saltar escena).
auto consumeGamepadButtonPressed() -> bool;
} // namespace GlobalEvents } // namespace GlobalEvents

View File

@@ -2,12 +2,12 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
#include <string> // Para string #include <string> // Para string
#include <vector> // Para vector #include <vector> // Para vector
#include "game/scenes/scene.hpp" // Para Scene #include "game/scenes/scene.hpp" // Para Scene
class AnimatedSprite; // lines 11-11 class AnimatedSprite; // lines 11-11
class Surface; class Surface;
class PixelReveal; class PixelReveal;
class DeltaTimer; class DeltaTimer;

View File

@@ -7,8 +7,8 @@
#include <vector> // Para vector #include <vector> // Para vector
#include "game/scenes/scene.hpp" // Para Scene #include "game/scenes/scene.hpp" // Para Scene
class Sprite; // lines 8-8 class Sprite; // lines 8-8
class Surface; // lines 9-9 class Surface; // lines 9-9
class PixelReveal; class PixelReveal;
class DeltaTimer; class DeltaTimer;

View File

@@ -2,12 +2,12 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
#include <vector> // Para vector #include <vector> // Para vector
#include "game/scenes/scene.hpp" // Para Scene #include "game/scenes/scene.hpp" // Para Scene
class AnimatedSprite; // lines 7-7 class AnimatedSprite; // lines 7-7
class DeltaTimer; // Forward declaration class DeltaTimer; // Forward declaration
class GameOver : public Scene { class GameOver : public Scene {
public: public:
@@ -51,9 +51,9 @@ class GameOver : public Scene {
void update(); // Actualiza el objeto void update(); // Actualiza el objeto
void render(); // Dibuja el final en pantalla void render(); // Dibuja el final en pantalla
static void handleInput(); // Comprueba las entradas static void handleInput(); // Comprueba las entradas
void updateState(); // Actualiza el estado y transiciones void updateState(); // Actualiza el estado y transiciones
void updateColor(); // Actualiza el color usado para renderizar void updateColor(); // Actualiza el color usado para renderizar
void renderSprites(); // Dibuja los sprites void renderSprites(); // Dibuja los sprites
// --- Variables miembro --- // --- Variables miembro ---
// Objetos y punteros a recursos // Objetos y punteros a recursos

View File

@@ -92,6 +92,15 @@ void Title::handleEvent(const SDL_Event& event) {
return; // No procesar más este evento return; // No procesar más este evento
} }
// Qualsevol botó del comandament al menú principal inicia partida directament
// (els bindings ja estan definits, no cal "pulsar 1" amb el teclat).
if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
state_ == State::MAIN_MENU &&
!is_remapping_keyboard_ && !is_remapping_joystick_) {
handleMainMenuKeyPress(SDLK_1); // PLAY
return;
}
if (event.type == SDL_EVENT_KEY_DOWN && !Console::get()->isActive()) { if (event.type == SDL_EVENT_KEY_DOWN && !Console::get()->isActive()) {
// Si estamos en modo remap de teclado, capturar tecla // Si estamos en modo remap de teclado, capturar tecla
if (is_remapping_keyboard_ && !remap_completed_) { if (is_remapping_keyboard_ && !remap_completed_) {

View File

@@ -6,7 +6,7 @@
namespace Texts { namespace Texts {
constexpr const char* WINDOW_CAPTION = "© 2022 JailDoctor's Dilemma — JailDesigner"; constexpr const char* WINDOW_CAPTION = "© 2022 JailDoctor's Dilemma — JailDesigner";
constexpr const char* COPYRIGHT = "@2022 JailDesigner"; constexpr const char* COPYRIGHT = "@2022 JailDesigner";
constexpr const char* VERSION = "1.13"; // Versión por defecto constexpr const char* VERSION = "1.14"; // Versión por defecto
} // namespace Texts } // namespace Texts
// Tamaño de bloque // Tamaño de bloque