integrada classe Input

This commit is contained in:
2025-12-11 12:41:03 +01:00
parent 087b8d346d
commit 0ceaa75862
16 changed files with 3437 additions and 124 deletions

View File

@@ -11,6 +11,7 @@
#include "core/defaults.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/input/input.hpp"
#include "core/rendering/shape_renderer.hpp"
#include "game/constants.hpp"
@@ -71,35 +72,43 @@ void Nau::processar_input(float delta_time, uint8_t player_id) {
if (esta_tocada_)
return;
// Obtenir estat actual del teclat (no events, sinó estat continu)
const bool* keyboard_state = SDL_GetKeyboardState(nullptr);
auto* input = Input::get();
// Seleccionar controles según player_id
SDL_Scancode key_right = (player_id == 0)
? Defaults::Controls::P1::ROTATE_RIGHT
: Defaults::Controls::P2::ROTATE_RIGHT;
SDL_Scancode key_left = (player_id == 0)
? Defaults::Controls::P1::ROTATE_LEFT
: Defaults::Controls::P2::ROTATE_LEFT;
SDL_Scancode key_thrust = (player_id == 0)
? Defaults::Controls::P1::THRUST
: Defaults::Controls::P2::THRUST;
// Processar input segons el jugador
if (player_id == 0) {
// Jugador 1
if (input->checkActionPlayer1(InputAction::RIGHT, Input::ALLOW_REPEAT)) {
angle_ += Defaults::Physics::ROTATION_SPEED * delta_time;
}
// Rotació
if (keyboard_state[key_right]) {
angle_ += Defaults::Physics::ROTATION_SPEED * delta_time;
}
if (input->checkActionPlayer1(InputAction::LEFT, Input::ALLOW_REPEAT)) {
angle_ -= Defaults::Physics::ROTATION_SPEED * delta_time;
}
if (keyboard_state[key_left]) {
angle_ -= Defaults::Physics::ROTATION_SPEED * delta_time;
}
if (input->checkActionPlayer1(InputAction::THRUST, Input::ALLOW_REPEAT)) {
if (velocitat_ < Defaults::Physics::MAX_VELOCITY) {
velocitat_ += Defaults::Physics::ACCELERATION * delta_time;
if (velocitat_ > Defaults::Physics::MAX_VELOCITY) {
velocitat_ = Defaults::Physics::MAX_VELOCITY;
}
}
}
} else {
// Jugador 2
if (input->checkActionPlayer2(InputAction::RIGHT, Input::ALLOW_REPEAT)) {
angle_ += Defaults::Physics::ROTATION_SPEED * delta_time;
}
// Acceleració
if (keyboard_state[key_thrust]) {
if (velocitat_ < Defaults::Physics::MAX_VELOCITY) {
velocitat_ += Defaults::Physics::ACCELERATION * delta_time;
if (velocitat_ > Defaults::Physics::MAX_VELOCITY) {
velocitat_ = Defaults::Physics::MAX_VELOCITY;
if (input->checkActionPlayer2(InputAction::LEFT, Input::ALLOW_REPEAT)) {
angle_ -= Defaults::Physics::ROTATION_SPEED * delta_time;
}
if (input->checkActionPlayer2(InputAction::THRUST, Input::ALLOW_REPEAT)) {
if (velocitat_ < Defaults::Physics::MAX_VELOCITY) {
velocitat_ += Defaults::Physics::ACCELERATION * delta_time;
if (velocitat_ > Defaults::Physics::MAX_VELOCITY) {
velocitat_ = Defaults::Physics::MAX_VELOCITY;
}
}
}
}

View File

@@ -11,6 +11,7 @@
#include <vector>
#include "core/audio/audio.hpp"
#include "core/input/input.hpp"
#include "core/input/mouse.hpp"
#include "core/math/easing.hpp"
#include "core/rendering/line_renderer.hpp"
@@ -74,6 +75,9 @@ void EscenaJoc::executar() {
// Actualitzar visibilitat del cursor (auto-ocultar)
Mouse::updateCursorVisibility();
// Actualitzar sistema d'input ABANS del event loop
Input::get()->update();
// Processar events SDL
while (SDL_PollEvent(&event)) {
// Manejo de finestra
@@ -82,12 +86,7 @@ void EscenaJoc::executar() {
}
// Events globals (F1/F2/F3/ESC/QUIT)
if (GlobalEvents::handle(event, sdl_, context_)) {
continue;
}
// Processament específic del joc (SPACE per disparar)
processar_input(event);
GlobalEvents::handle(event, sdl_, context_);
}
// Actualitzar física del joc amb delta_time real
@@ -180,6 +179,21 @@ void EscenaJoc::inicialitzar() {
}
void EscenaJoc::actualitzar(float delta_time) {
// Processar disparos (state-based, no event-based)
if (!game_over_) {
auto* input = Input::get();
// Jugador 1 dispara
if (input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
disparar_bala(0);
}
// Jugador 2 dispara
if (input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
disparar_bala(1);
}
}
// Check game over state first
if (game_over_) {
// Game over: only update timer, enemies, bullets, and debris
@@ -534,28 +548,6 @@ void EscenaJoc::dibuixar() {
}
}
void EscenaJoc::processar_input(const SDL_Event& event) {
// Ignore ship controls during game over
if (game_over_) {
return;
}
// Processament d'input per events puntuals (no continus)
// L'input continu (fletxes/WASD) es processa en actualitzar() amb
// SDL_GetKeyboardState()
if (event.type == SDL_EVENT_KEY_DOWN) {
// P1 shoot
if (event.key.key == Defaults::Controls::P1::SHOOT) {
disparar_bala(0);
}
// P2 shoot
else if (event.key.key == Defaults::Controls::P2::SHOOT) {
disparar_bala(1);
}
}
}
void EscenaJoc::tocado(uint8_t player_id) {
// Death sequence: 3 phases
// Phase 1: First call (itocado_per_jugador_[player_id] == 0) - trigger explosion

View File

@@ -34,7 +34,6 @@ class EscenaJoc {
void inicialitzar();
void actualitzar(float delta_time);
void dibuixar();
void processar_input(const SDL_Event& event);
private:
SDLManager& sdl_;

View File

@@ -11,6 +11,7 @@
#include "core/audio/audio.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/input/input.hpp"
#include "core/input/mouse.hpp"
#include "core/rendering/shape_renderer.hpp"
#include "core/system/context_escenes.hpp"
@@ -82,6 +83,9 @@ void EscenaLogo::executar() {
// Actualitzar visibilitat del cursor (auto-ocultar)
Mouse::updateCursorVisibility();
// Actualitzar sistema d'input ABANS del event loop
Input::get()->update();
// Processar events SDL
while (SDL_PollEvent(&event)) {
// Manejo de finestra
@@ -312,6 +316,12 @@ void EscenaLogo::actualitzar(float delta_time) {
break;
}
// Verificar botones de skip (SHOOT P1/P2)
if (checkSkipButtonPressed()) {
context_.canviar_escena(Escena::TITOL, Opcio::JUMP_TO_TITLE_MAIN);
GestorEscenes::actual = Escena::TITOL;
}
// Actualitzar animacions de debris
debris_manager_->actualitzar(delta_time);
}
@@ -404,16 +414,17 @@ void EscenaLogo::dibuixar() {
sdl_.presenta();
}
void EscenaLogo::processar_events(const SDL_Event& event) {
// Qualsevol tecla o clic de ratolí salta a la pantalla de títol
if (event.type == SDL_EVENT_KEY_DOWN ||
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
// Utilitzar context per especificar escena i opció
context_.canviar_escena(
Escena::TITOL,
Opcio::JUMP_TO_TITLE_MAIN
);
// Backward compatibility: També actualitzar GestorEscenes::actual
GestorEscenes::actual = Escena::TITOL;
auto EscenaLogo::checkSkipButtonPressed() -> bool {
auto* input = Input::get();
for (auto action : SKIP_BUTTONS_LOGO) {
if (input->checkActionPlayer1(action, Input::DO_NOT_ALLOW_REPEAT) ||
input->checkActionPlayer2(action, Input::DO_NOT_ALLOW_REPEAT)) {
return true;
}
}
return false;
}
void EscenaLogo::processar_events(const SDL_Event& event) {
// No procesar eventos genéricos aquí - la lógica se movió a actualitzar()
}

View File

@@ -6,16 +6,23 @@
#include <SDL3/SDL.h>
#include <array>
#include <memory>
#include <vector>
#include "game/effects/debris_manager.hpp"
#include "core/defaults.hpp"
#include "core/graphics/shape.hpp"
#include "core/input/input_types.hpp"
#include "core/rendering/sdl_manager.hpp"
#include "core/system/context_escenes.hpp"
#include "core/types.hpp"
// Botones que permiten saltar la escena (extensible)
static constexpr std::array<InputAction, 1> SKIP_BUTTONS_LOGO = {
InputAction::SHOOT
};
class EscenaLogo {
public:
explicit EscenaLogo(SDLManager& sdl, GestorEscenes::ContextEscenes& context);
@@ -80,6 +87,7 @@ class EscenaLogo {
void actualitzar_explosions(float delta_time);
void dibuixar();
void processar_events(const SDL_Event& event);
auto checkSkipButtonPressed() -> bool;
// Mètodes de gestió d'estats
void canviar_estat(EstatAnimacio nou_estat);

View File

@@ -10,6 +10,7 @@
#include "core/audio/audio.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/input/input.hpp"
#include "core/input/mouse.hpp"
#include "core/rendering/shape_renderer.hpp"
#include "core/system/context_escenes.hpp"
@@ -254,6 +255,9 @@ void EscenaTitol::executar() {
// Actualitzar visibilitat del cursor (auto-ocultar)
Mouse::updateCursorVisibility();
// Actualitzar sistema d'input ABANS del event loop
Input::get()->update();
// Processar events SDL
while (SDL_PollEvent(&event)) {
// Manejo de finestra
@@ -368,6 +372,37 @@ void EscenaTitol::actualitzar(float delta_time) {
}
break;
}
// Verificar botones de skip (SHOOT P1/P2)
if (checkSkipButtonPressed()) {
switch (estat_actual_) {
case EstatTitol::STARFIELD_FADE_IN:
// Saltar fade-in, ir a MAIN
estat_actual_ = EstatTitol::MAIN;
starfield_->set_brightness(BRIGHTNESS_STARFIELD);
temps_estat_main_ = 0.0f;
break;
case EstatTitol::STARFIELD:
// Saltar starfield, ir a MAIN
estat_actual_ = EstatTitol::MAIN;
temps_estat_main_ = 0.0f;
break;
case EstatTitol::MAIN:
// Iniciar partida (transición a JOC)
context_.canviar_escena(Escena::JOC);
estat_actual_ = EstatTitol::TRANSITION_TO_GAME;
temps_acumulat_ = 0.0f;
Audio::get()->fadeOutMusic(MUSIC_FADE);
Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME);
break;
case EstatTitol::TRANSITION_TO_GAME:
// Ignorar inputs durante transición
break;
}
}
}
void EscenaTitol::actualitzar_animacio_logo(float delta_time) {
@@ -540,40 +575,17 @@ void EscenaTitol::dibuixar() {
}
}
void EscenaTitol::processar_events(const SDL_Event& event) {
// Qualsevol tecla o clic de ratolí
if (event.type == SDL_EVENT_KEY_DOWN ||
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
switch (estat_actual_) {
case EstatTitol::STARFIELD_FADE_IN:
// Saltar directament a MAIN (ometre fade-in i starfield)
estat_actual_ = EstatTitol::MAIN;
starfield_->set_brightness(BRIGHTNESS_STARFIELD); // Assegurar brightness final
temps_estat_main_ = 0.0f; // Reset timer per animació de títol
break;
case EstatTitol::STARFIELD:
// Saltar a MAIN
estat_actual_ = EstatTitol::MAIN;
temps_estat_main_ = 0.0f; // Reset timer
break;
case EstatTitol::MAIN:
// Utilitzar context per transició a JOC
context_.canviar_escena(Escena::JOC);
// NO actualitzar GestorEscenes::actual aquí!
// La transició es fa en l'estat TRANSITION_TO_GAME
// Iniciar transició amb fade-out de música
estat_actual_ = EstatTitol::TRANSITION_TO_GAME;
temps_acumulat_ = 0.0f; // Reset del comptador
Audio::get()->fadeOutMusic(MUSIC_FADE); // Fade
Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME);
break;
case EstatTitol::TRANSITION_TO_GAME:
// Ignorar inputs durant la transició
break;
auto EscenaTitol::checkSkipButtonPressed() -> bool {
auto* input = Input::get();
for (auto action : SKIP_BUTTONS_TITOL) {
if (input->checkActionPlayer1(action, Input::DO_NOT_ALLOW_REPEAT) ||
input->checkActionPlayer2(action, Input::DO_NOT_ALLOW_REPEAT)) {
return true;
}
}
return false;
}
void EscenaTitol::processar_events(const SDL_Event& event) {
// No procesar eventos genéricos aquí - la lógica se movió a actualitzar()
}

View File

@@ -6,6 +6,7 @@
#include <SDL3/SDL.h>
#include <array>
#include <memory>
#include <vector>
@@ -13,10 +14,16 @@
#include "core/graphics/shape.hpp"
#include "core/graphics/starfield.hpp"
#include "core/graphics/vector_text.hpp"
#include "core/input/input_types.hpp"
#include "core/rendering/sdl_manager.hpp"
#include "core/system/context_escenes.hpp"
#include "core/types.hpp"
// Botones que permiten saltar/avanzar la escena (extensible)
static constexpr std::array<InputAction, 1> SKIP_BUTTONS_TITOL = {
InputAction::SHOOT
};
class EscenaTitol {
public:
explicit EscenaTitol(SDLManager& sdl, GestorEscenes::ContextEscenes& context);
@@ -97,5 +104,6 @@ class EscenaTitol {
void actualitzar_animacio_logo(float delta_time); // Actualitza l'animació orbital del logo
void dibuixar();
void processar_events(const SDL_Event& event);
auto checkSkipButtonPressed() -> bool;
void inicialitzar_titol(); // Carrega i posiciona les lletres del títol
};

View File

@@ -3,6 +3,7 @@
#include <fstream>
#include <iostream>
#include <string>
#include <unordered_map>
#include "core/defaults.hpp"
#include "external/fkyaml_node.hpp"
@@ -10,6 +11,111 @@
namespace Options {
// ========== FUNCIONS AUXILIARS PER CONVERSIÓ DE CONTROLES ==========
// Mapa de SDL_Scancode a string
static const std::unordered_map<SDL_Scancode, std::string> SCANCODE_TO_STRING = {
{SDL_SCANCODE_A, "A"}, {SDL_SCANCODE_B, "B"}, {SDL_SCANCODE_C, "C"}, {SDL_SCANCODE_D, "D"},
{SDL_SCANCODE_E, "E"}, {SDL_SCANCODE_F, "F"}, {SDL_SCANCODE_G, "G"}, {SDL_SCANCODE_H, "H"},
{SDL_SCANCODE_I, "I"}, {SDL_SCANCODE_J, "J"}, {SDL_SCANCODE_K, "K"}, {SDL_SCANCODE_L, "L"},
{SDL_SCANCODE_M, "M"}, {SDL_SCANCODE_N, "N"}, {SDL_SCANCODE_O, "O"}, {SDL_SCANCODE_P, "P"},
{SDL_SCANCODE_Q, "Q"}, {SDL_SCANCODE_R, "R"}, {SDL_SCANCODE_S, "S"}, {SDL_SCANCODE_T, "T"},
{SDL_SCANCODE_U, "U"}, {SDL_SCANCODE_V, "V"}, {SDL_SCANCODE_W, "W"}, {SDL_SCANCODE_X, "X"},
{SDL_SCANCODE_Y, "Y"}, {SDL_SCANCODE_Z, "Z"},
{SDL_SCANCODE_1, "1"}, {SDL_SCANCODE_2, "2"}, {SDL_SCANCODE_3, "3"}, {SDL_SCANCODE_4, "4"},
{SDL_SCANCODE_5, "5"}, {SDL_SCANCODE_6, "6"}, {SDL_SCANCODE_7, "7"}, {SDL_SCANCODE_8, "8"},
{SDL_SCANCODE_9, "9"}, {SDL_SCANCODE_0, "0"},
{SDL_SCANCODE_RETURN, "RETURN"}, {SDL_SCANCODE_ESCAPE, "ESCAPE"},
{SDL_SCANCODE_BACKSPACE, "BACKSPACE"}, {SDL_SCANCODE_TAB, "TAB"},
{SDL_SCANCODE_SPACE, "SPACE"},
{SDL_SCANCODE_UP, "UP"}, {SDL_SCANCODE_DOWN, "DOWN"},
{SDL_SCANCODE_LEFT, "LEFT"}, {SDL_SCANCODE_RIGHT, "RIGHT"},
{SDL_SCANCODE_LSHIFT, "LSHIFT"}, {SDL_SCANCODE_RSHIFT, "RSHIFT"},
{SDL_SCANCODE_LCTRL, "LCTRL"}, {SDL_SCANCODE_RCTRL, "RCTRL"},
{SDL_SCANCODE_LALT, "LALT"}, {SDL_SCANCODE_RALT, "RALT"}
};
// Mapa invers: string a SDL_Scancode
static const std::unordered_map<std::string, SDL_Scancode> STRING_TO_SCANCODE = {
{"A", SDL_SCANCODE_A}, {"B", SDL_SCANCODE_B}, {"C", SDL_SCANCODE_C}, {"D", SDL_SCANCODE_D},
{"E", SDL_SCANCODE_E}, {"F", SDL_SCANCODE_F}, {"G", SDL_SCANCODE_G}, {"H", SDL_SCANCODE_H},
{"I", SDL_SCANCODE_I}, {"J", SDL_SCANCODE_J}, {"K", SDL_SCANCODE_K}, {"L", SDL_SCANCODE_L},
{"M", SDL_SCANCODE_M}, {"N", SDL_SCANCODE_N}, {"O", SDL_SCANCODE_O}, {"P", SDL_SCANCODE_P},
{"Q", SDL_SCANCODE_Q}, {"R", SDL_SCANCODE_R}, {"S", SDL_SCANCODE_S}, {"T", SDL_SCANCODE_T},
{"U", SDL_SCANCODE_U}, {"V", SDL_SCANCODE_V}, {"W", SDL_SCANCODE_W}, {"X", SDL_SCANCODE_X},
{"Y", SDL_SCANCODE_Y}, {"Z", SDL_SCANCODE_Z},
{"1", SDL_SCANCODE_1}, {"2", SDL_SCANCODE_2}, {"3", SDL_SCANCODE_3}, {"4", SDL_SCANCODE_4},
{"5", SDL_SCANCODE_5}, {"6", SDL_SCANCODE_6}, {"7", SDL_SCANCODE_7}, {"8", SDL_SCANCODE_8},
{"9", SDL_SCANCODE_9}, {"0", SDL_SCANCODE_0},
{"RETURN", SDL_SCANCODE_RETURN}, {"ESCAPE", SDL_SCANCODE_ESCAPE},
{"BACKSPACE", SDL_SCANCODE_BACKSPACE}, {"TAB", SDL_SCANCODE_TAB},
{"SPACE", SDL_SCANCODE_SPACE},
{"UP", SDL_SCANCODE_UP}, {"DOWN", SDL_SCANCODE_DOWN},
{"LEFT", SDL_SCANCODE_LEFT}, {"RIGHT", SDL_SCANCODE_RIGHT},
{"LSHIFT", SDL_SCANCODE_LSHIFT}, {"RSHIFT", SDL_SCANCODE_RSHIFT},
{"LCTRL", SDL_SCANCODE_LCTRL}, {"RCTRL", SDL_SCANCODE_RCTRL},
{"LALT", SDL_SCANCODE_LALT}, {"RALT", SDL_SCANCODE_RALT}
};
// Mapa de botó de gamepad (int) a string
static const std::unordered_map<int, std::string> BUTTON_TO_STRING = {
{SDL_GAMEPAD_BUTTON_SOUTH, "SOUTH"}, // A (Xbox), Cross (PS)
{SDL_GAMEPAD_BUTTON_EAST, "EAST"}, // B (Xbox), Circle (PS)
{SDL_GAMEPAD_BUTTON_WEST, "WEST"}, // X (Xbox), Square (PS)
{SDL_GAMEPAD_BUTTON_NORTH, "NORTH"}, // Y (Xbox), Triangle (PS)
{SDL_GAMEPAD_BUTTON_BACK, "BACK"},
{SDL_GAMEPAD_BUTTON_START, "START"},
{SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, "LEFT_SHOULDER"},
{SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, "RIGHT_SHOULDER"},
{SDL_GAMEPAD_BUTTON_DPAD_UP, "DPAD_UP"},
{SDL_GAMEPAD_BUTTON_DPAD_DOWN, "DPAD_DOWN"},
{SDL_GAMEPAD_BUTTON_DPAD_LEFT, "DPAD_LEFT"},
{SDL_GAMEPAD_BUTTON_DPAD_RIGHT, "DPAD_RIGHT"},
{100, "L2_AS_BUTTON"}, // Trigger L2 com a botó digital
{101, "R2_AS_BUTTON"} // Trigger R2 com a botó digital
};
// Mapa invers: string a botó de gamepad
static const std::unordered_map<std::string, int> STRING_TO_BUTTON = {
{"SOUTH", SDL_GAMEPAD_BUTTON_SOUTH},
{"EAST", SDL_GAMEPAD_BUTTON_EAST},
{"WEST", SDL_GAMEPAD_BUTTON_WEST},
{"NORTH", SDL_GAMEPAD_BUTTON_NORTH},
{"BACK", SDL_GAMEPAD_BUTTON_BACK},
{"START", SDL_GAMEPAD_BUTTON_START},
{"LEFT_SHOULDER", SDL_GAMEPAD_BUTTON_LEFT_SHOULDER},
{"RIGHT_SHOULDER", SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
{"DPAD_UP", SDL_GAMEPAD_BUTTON_DPAD_UP},
{"DPAD_DOWN", SDL_GAMEPAD_BUTTON_DPAD_DOWN},
{"DPAD_LEFT", SDL_GAMEPAD_BUTTON_DPAD_LEFT},
{"DPAD_RIGHT", SDL_GAMEPAD_BUTTON_DPAD_RIGHT},
{"L2_AS_BUTTON", 100},
{"R2_AS_BUTTON", 101}
};
static auto scancodeToString(SDL_Scancode code) -> std::string {
auto it = SCANCODE_TO_STRING.find(code);
return (it != SCANCODE_TO_STRING.end()) ? it->second : "UNKNOWN";
}
static auto stringToScancode(const std::string& str) -> SDL_Scancode {
auto it = STRING_TO_SCANCODE.find(str);
return (it != STRING_TO_SCANCODE.end()) ? it->second : SDL_SCANCODE_UNKNOWN;
}
static auto buttonToString(int button) -> std::string {
auto it = BUTTON_TO_STRING.find(button);
return (it != BUTTON_TO_STRING.end()) ? it->second : "UNKNOWN";
}
static auto stringToButton(const std::string& str) -> int {
auto it = STRING_TO_BUTTON.find(str);
return (it != STRING_TO_BUTTON.end()) ? it->second : SDL_GAMEPAD_BUTTON_INVALID;
}
// ========== FI FUNCIONS AUXILIARS ==========
// Inicialitzar opcions amb valors per defecte de Defaults::
void init() {
#ifdef _DEBUG
@@ -278,6 +384,80 @@ static void loadAudioConfigFromYaml(const fkyaml::node& yaml) {
}
}
// Carregar controls del jugador 1 des de YAML
static void loadPlayer1ControlsFromYaml(const fkyaml::node& yaml) {
if (!yaml.contains("player1")) return;
const auto& p1 = yaml["player1"];
// Carregar controls de teclat
if (p1.contains("keyboard")) {
const auto& kb = p1["keyboard"];
if (kb.contains("key_left"))
player1.keyboard.key_left = stringToScancode(kb["key_left"].get_value<std::string>());
if (kb.contains("key_right"))
player1.keyboard.key_right = stringToScancode(kb["key_right"].get_value<std::string>());
if (kb.contains("key_thrust"))
player1.keyboard.key_thrust = stringToScancode(kb["key_thrust"].get_value<std::string>());
if (kb.contains("key_shoot"))
player1.keyboard.key_shoot = stringToScancode(kb["key_shoot"].get_value<std::string>());
}
// Carregar controls de gamepad
if (p1.contains("gamepad")) {
const auto& gp = p1["gamepad"];
if (gp.contains("button_left"))
player1.gamepad.button_left = stringToButton(gp["button_left"].get_value<std::string>());
if (gp.contains("button_right"))
player1.gamepad.button_right = stringToButton(gp["button_right"].get_value<std::string>());
if (gp.contains("button_thrust"))
player1.gamepad.button_thrust = stringToButton(gp["button_thrust"].get_value<std::string>());
if (gp.contains("button_shoot"))
player1.gamepad.button_shoot = stringToButton(gp["button_shoot"].get_value<std::string>());
}
// Carregar nom del gamepad
if (p1.contains("gamepad_name"))
player1.gamepad_name = p1["gamepad_name"].get_value<std::string>();
}
// Carregar controls del jugador 2 des de YAML
static void loadPlayer2ControlsFromYaml(const fkyaml::node& yaml) {
if (!yaml.contains("player2")) return;
const auto& p2 = yaml["player2"];
// Carregar controls de teclat
if (p2.contains("keyboard")) {
const auto& kb = p2["keyboard"];
if (kb.contains("key_left"))
player2.keyboard.key_left = stringToScancode(kb["key_left"].get_value<std::string>());
if (kb.contains("key_right"))
player2.keyboard.key_right = stringToScancode(kb["key_right"].get_value<std::string>());
if (kb.contains("key_thrust"))
player2.keyboard.key_thrust = stringToScancode(kb["key_thrust"].get_value<std::string>());
if (kb.contains("key_shoot"))
player2.keyboard.key_shoot = stringToScancode(kb["key_shoot"].get_value<std::string>());
}
// Carregar controls de gamepad
if (p2.contains("gamepad")) {
const auto& gp = p2["gamepad"];
if (gp.contains("button_left"))
player2.gamepad.button_left = stringToButton(gp["button_left"].get_value<std::string>());
if (gp.contains("button_right"))
player2.gamepad.button_right = stringToButton(gp["button_right"].get_value<std::string>());
if (gp.contains("button_thrust"))
player2.gamepad.button_thrust = stringToButton(gp["button_thrust"].get_value<std::string>());
if (gp.contains("button_shoot"))
player2.gamepad.button_shoot = stringToButton(gp["button_shoot"].get_value<std::string>());
}
// Carregar nom del gamepad
if (p2.contains("gamepad_name"))
player2.gamepad_name = p2["gamepad_name"].get_value<std::string>();
}
// Carregar configuració des del fitxer YAML
auto loadFromFile() -> bool {
const std::string CONFIG_VERSION = std::string(Project::VERSION);
@@ -325,6 +505,8 @@ auto loadFromFile() -> bool {
loadGameplayConfigFromYaml(yaml);
loadRenderingConfigFromYaml(yaml);
loadAudioConfigFromYaml(yaml);
loadPlayer1ControlsFromYaml(yaml);
loadPlayer2ControlsFromYaml(yaml);
if (console) {
std::cout << "Config carregada correctament des de: " << config_file_path
@@ -345,6 +527,40 @@ auto loadFromFile() -> bool {
}
}
// Guardar controls del jugador 1 a YAML
static void savePlayer1ControlsToYaml(std::ofstream& file) {
file << "# CONTROLS JUGADOR 1\n";
file << "player1:\n";
file << " keyboard:\n";
file << " key_left: " << scancodeToString(player1.keyboard.key_left) << "\n";
file << " key_right: " << scancodeToString(player1.keyboard.key_right) << "\n";
file << " key_thrust: " << scancodeToString(player1.keyboard.key_thrust) << "\n";
file << " key_shoot: " << scancodeToString(player1.keyboard.key_shoot) << "\n";
file << " gamepad:\n";
file << " button_left: " << buttonToString(player1.gamepad.button_left) << "\n";
file << " button_right: " << buttonToString(player1.gamepad.button_right) << "\n";
file << " button_thrust: " << buttonToString(player1.gamepad.button_thrust) << "\n";
file << " button_shoot: " << buttonToString(player1.gamepad.button_shoot) << "\n";
file << " gamepad_name: \"" << player1.gamepad_name << "\" # Buit = primer disponible\n\n";
}
// Guardar controls del jugador 2 a YAML
static void savePlayer2ControlsToYaml(std::ofstream& file) {
file << "# CONTROLS JUGADOR 2\n";
file << "player2:\n";
file << " keyboard:\n";
file << " key_left: " << scancodeToString(player2.keyboard.key_left) << "\n";
file << " key_right: " << scancodeToString(player2.keyboard.key_right) << "\n";
file << " key_thrust: " << scancodeToString(player2.keyboard.key_thrust) << "\n";
file << " key_shoot: " << scancodeToString(player2.keyboard.key_shoot) << "\n";
file << " gamepad:\n";
file << " button_left: " << buttonToString(player2.gamepad.button_left) << "\n";
file << " button_right: " << buttonToString(player2.gamepad.button_right) << "\n";
file << " button_thrust: " << buttonToString(player2.gamepad.button_thrust) << "\n";
file << " button_shoot: " << buttonToString(player2.gamepad.button_shoot) << "\n";
file << " gamepad_name: \"" << player2.gamepad_name << "\" # Buit = segon disponible\n\n";
}
// Guardar configuració al fitxer YAML
auto saveToFile() -> bool {
std::ofstream file(config_file_path);
@@ -399,7 +615,11 @@ auto saveToFile() -> bool {
file << " volume: " << audio.music.volume << " # 0.0 to 1.0\n";
file << " sound:\n";
file << " enabled: " << (audio.sound.enabled ? "true" : "false") << "\n";
file << " volume: " << audio.sound.volume << " # 0.0 to 1.0\n";
file << " volume: " << audio.sound.volume << " # 0.0 to 1.0\n\n";
// Guardar controls de jugadors
savePlayer1ControlsToYaml(file);
savePlayer2ControlsToYaml(file);
file.close();

View File

@@ -1,5 +1,7 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_Scancode
#include <string>
namespace Options {
@@ -48,6 +50,28 @@ struct Audio {
float volume{1.0f};
};
// Controles de jugadors
struct KeyboardControls {
SDL_Scancode key_left{SDL_SCANCODE_LEFT};
SDL_Scancode key_right{SDL_SCANCODE_RIGHT};
SDL_Scancode key_thrust{SDL_SCANCODE_UP};
SDL_Scancode key_shoot{SDL_SCANCODE_SPACE};
};
struct GamepadControls {
int button_left{SDL_GAMEPAD_BUTTON_DPAD_LEFT};
int button_right{SDL_GAMEPAD_BUTTON_DPAD_RIGHT};
int button_thrust{SDL_GAMEPAD_BUTTON_WEST}; // X button
int button_shoot{SDL_GAMEPAD_BUTTON_SOUTH}; // A button
};
struct PlayerControls {
KeyboardControls keyboard{};
GamepadControls gamepad{};
std::string gamepad_name{""}; // Buit = auto-assignar per índex
};
// Variables globals (inline per evitar ODR violations)
inline std::string version{}; // Versió del config per validació
@@ -58,6 +82,29 @@ inline Gameplay gameplay{};
inline Rendering rendering{};
inline Audio audio{};
// Controles per jugador
inline PlayerControls player1{
.keyboard =
{.key_left = SDL_SCANCODE_LEFT,
.key_right = SDL_SCANCODE_RIGHT,
.key_thrust = SDL_SCANCODE_UP,
.key_shoot = SDL_SCANCODE_SPACE},
.gamepad_name = "" // Primer gamepad disponible
};
inline PlayerControls player2{
.keyboard =
{.key_left = SDL_SCANCODE_A,
.key_right = SDL_SCANCODE_D,
.key_thrust = SDL_SCANCODE_W,
.key_shoot = SDL_SCANCODE_LSHIFT},
.gamepad_name = "" // Segon gamepad disponible
};
// Per compatibilitat amb pollo (no utilitzat en orni, però necessari per Input)
inline KeyboardControls keyboard_controls{};
inline GamepadControls gamepad_controls{};
inline std::string config_file_path{}; // Establert per setConfigFile()
// Funcions públiques