feat: Add input bindings configuration for keyboard and gamepad actions

This commit is contained in:
2026-01-05 06:55:13 +00:00
parent 5548d3b3ce
commit ca9830b978
11 changed files with 476 additions and 37 deletions

View File

@@ -45,6 +45,48 @@ SDL3 + Vulkan demo app with Lua-driven runtime configuration, audio playback, an
- `sdl3_app --create-seed-json config/seed_runtime.json` writes a starter file assuming `scripts/cube_logic.lua` sits beside the binary.
- `sdl3_app --set-default-json [path]` stores or overrides the runtime JSON; Windows writes `%APPDATA%/sdl3cpp`, other OSes use `$XDG_CONFIG_HOME/sdl3cpp/default_runtime.json` (fallback `~/.config/sdl3cpp`).
### Input bindings
`config/seed_runtime.json` includes an `input_bindings` section that maps keyboard keys and gamepad inputs to action names consumed by the Lua script (see `scripts/cube_logic.lua`).
Keyboard bindings use SDL key names (e.g. `W`, `Space`, `Left Shift`). Gamepad bindings use SDL gamepad names (e.g. `start`, `south`, `dpad_up`, `leftx`).
Example:
```
"input_bindings": {
"move_forward": "W",
"move_back": "S",
"move_left": "A",
"move_right": "D",
"music_toggle": "M",
"music_toggle_gamepad": "start",
"gamepad_move_x_axis": "leftx",
"gamepad_move_y_axis": "lefty",
"gamepad_look_x_axis": "rightx",
"gamepad_look_y_axis": "righty",
"gamepad_dpad_up": "dpad_up",
"gamepad_dpad_down": "dpad_down",
"gamepad_dpad_left": "dpad_left",
"gamepad_dpad_right": "dpad_right",
"gamepad_button_actions": {
"south": "gamepad_a",
"east": "gamepad_b",
"west": "gamepad_x",
"north": "gamepad_y",
"left_shoulder": "gamepad_lb",
"right_shoulder": "gamepad_rb",
"left_stick": "gamepad_ls",
"right_stick": "gamepad_rs",
"back": "gamepad_back",
"start": "gamepad_start"
},
"gamepad_axis_actions": {
"left_trigger": "gamepad_lt",
"right_trigger": "gamepad_rt"
},
"gamepad_axis_action_threshold": 0.5
}
```
## GUI demo
`scripts/gui_demo.lua` paints the Lua GUI framework on top of the Vulkan scene. Launch it as `python scripts/dev_commands.py run -- --json-file-in config/gui_runtime.json` or register that config via `sdl3_app --set-default-json`.

View File

@@ -3,6 +3,39 @@
"window_height": 768,
"lua_script": "scripts/cube_logic.lua",
"scripts_directory": "scripts",
"input_bindings": {
"move_forward": "W",
"move_back": "S",
"move_left": "A",
"move_right": "D",
"music_toggle": "M",
"music_toggle_gamepad": "start",
"gamepad_move_x_axis": "leftx",
"gamepad_move_y_axis": "lefty",
"gamepad_look_x_axis": "rightx",
"gamepad_look_y_axis": "righty",
"gamepad_dpad_up": "dpad_up",
"gamepad_dpad_down": "dpad_down",
"gamepad_dpad_left": "dpad_left",
"gamepad_dpad_right": "dpad_right",
"gamepad_button_actions": {
"south": "gamepad_a",
"east": "gamepad_b",
"west": "gamepad_x",
"north": "gamepad_y",
"left_shoulder": "gamepad_lb",
"right_shoulder": "gamepad_rb",
"left_stick": "gamepad_ls",
"right_stick": "gamepad_rs",
"back": "gamepad_back",
"start": "gamepad_start"
},
"gamepad_axis_actions": {
"left_trigger": "gamepad_lt",
"right_trigger": "gamepad_rt"
},
"gamepad_axis_action_threshold": 0.5
},
"project_root": "../",
"shaders_directory": "shaders",
"device_extensions": [

View File

@@ -257,16 +257,16 @@ local function update_camera(dt)
local move_x = 0.0
local move_z = 0.0
if gui_input.keyStates["w"] then
if gui_input.keyStates["move_forward"] then
move_z = move_z + 1.0
end
if gui_input.keyStates["s"] then
if gui_input.keyStates["move_back"] then
move_z = move_z - 1.0
end
if gui_input.keyStates["d"] then
if gui_input.keyStates["move_right"] then
move_x = move_x + 1.0
end
if gui_input.keyStates["a"] then
if gui_input.keyStates["move_left"] then
move_x = move_x - 1.0
end
@@ -295,7 +295,7 @@ local function update_audio_controls()
end
local pad = gui_input.gamepad
local toggle_pressed = gui_input.keyStates["m"]
local toggle_pressed = gui_input.keyStates["music_toggle"]
if pad and pad.connected and pad.togglePressed then
toggle_pressed = true
end

View File

@@ -224,6 +224,7 @@ void ServiceBasedApp::RegisterServices() {
// Input service
registry_.RegisterService<services::IInputService, services::impl::SdlInputService>(
registry_.GetService<events::IEventBus>(),
registry_.GetService<services::IConfigService>(),
registry_.GetService<services::ILogger>());
// Audio service (needed before script bindings execute)

View File

@@ -176,6 +176,73 @@ RuntimeConfig JsonConfigService::LoadFromJson(std::shared_ptr<ILogger> logger, c
config.windowTitle = value.GetString();
}
if (document.HasMember("input_bindings")) {
const auto& bindingsValue = document["input_bindings"];
if (!bindingsValue.IsObject()) {
throw std::runtime_error("JSON member 'input_bindings' must be an object");
}
auto readBinding = [&](const char* name, std::string& target) {
if (!bindingsValue.HasMember(name)) {
return;
}
const auto& value = bindingsValue[name];
if (!value.IsString()) {
throw std::runtime_error("JSON member 'input_bindings." + std::string(name) + "' must be a string");
}
target = value.GetString();
};
readBinding("move_forward", config.inputBindings.moveForwardKey);
readBinding("move_back", config.inputBindings.moveBackKey);
readBinding("move_left", config.inputBindings.moveLeftKey);
readBinding("move_right", config.inputBindings.moveRightKey);
readBinding("music_toggle", config.inputBindings.musicToggleKey);
readBinding("music_toggle_gamepad", config.inputBindings.musicToggleGamepadButton);
readBinding("gamepad_move_x_axis", config.inputBindings.gamepadMoveXAxis);
readBinding("gamepad_move_y_axis", config.inputBindings.gamepadMoveYAxis);
readBinding("gamepad_look_x_axis", config.inputBindings.gamepadLookXAxis);
readBinding("gamepad_look_y_axis", config.inputBindings.gamepadLookYAxis);
readBinding("gamepad_dpad_up", config.inputBindings.gamepadDpadUpButton);
readBinding("gamepad_dpad_down", config.inputBindings.gamepadDpadDownButton);
readBinding("gamepad_dpad_left", config.inputBindings.gamepadDpadLeftButton);
readBinding("gamepad_dpad_right", config.inputBindings.gamepadDpadRightButton);
auto readMapping = [&](const char* name,
std::unordered_map<std::string, std::string>& target) {
if (!bindingsValue.HasMember(name)) {
return;
}
const auto& mappingValue = bindingsValue[name];
if (!mappingValue.IsObject()) {
throw std::runtime_error("JSON member 'input_bindings." + std::string(name) + "' must be an object");
}
for (auto it = mappingValue.MemberBegin(); it != mappingValue.MemberEnd(); ++it) {
if (!it->name.IsString() || !it->value.IsString()) {
throw std::runtime_error("JSON member 'input_bindings." + std::string(name) +
"' must contain string pairs");
}
std::string key = it->name.GetString();
std::string value = it->value.GetString();
if (value.empty()) {
target.erase(key);
} else {
target[key] = value;
}
}
};
readMapping("gamepad_button_actions", config.inputBindings.gamepadButtonActions);
readMapping("gamepad_axis_actions", config.inputBindings.gamepadAxisActions);
if (bindingsValue.HasMember("gamepad_axis_action_threshold")) {
const auto& value = bindingsValue["gamepad_axis_action_threshold"];
if (!value.IsNumber()) {
throw std::runtime_error("JSON member 'input_bindings.gamepad_axis_action_threshold' must be a number");
}
config.inputBindings.gamepadAxisActionThreshold = static_cast<float>(value.GetDouble());
}
}
return config;
}

View File

@@ -76,6 +76,12 @@ public:
return config_.windowTitle;
}
std::vector<const char*> GetDeviceExtensions() const override;
const InputBindings& GetInputBindings() const override {
if (logger_) {
logger_->Trace("JsonConfigService", "GetInputBindings");
}
return config_.inputBindings;
}
/**
* @brief Get the full runtime configuration.

View File

@@ -49,6 +49,45 @@ void JsonConfigWriterService::WriteConfig(const RuntimeConfig& config, const std
std::filesystem::path scriptsDir = config.scriptPath.parent_path();
addStringMember("scripts_directory", scriptsDir.string());
rapidjson::Value bindingsObject(rapidjson::kObjectType);
auto addBindingMember = [&](const char* name, const std::string& value) {
rapidjson::Value nameValue(name, allocator);
rapidjson::Value stringValue(value.c_str(), allocator);
bindingsObject.AddMember(nameValue, stringValue, allocator);
};
addBindingMember("move_forward", config.inputBindings.moveForwardKey);
addBindingMember("move_back", config.inputBindings.moveBackKey);
addBindingMember("move_left", config.inputBindings.moveLeftKey);
addBindingMember("move_right", config.inputBindings.moveRightKey);
addBindingMember("music_toggle", config.inputBindings.musicToggleKey);
addBindingMember("music_toggle_gamepad", config.inputBindings.musicToggleGamepadButton);
addBindingMember("gamepad_move_x_axis", config.inputBindings.gamepadMoveXAxis);
addBindingMember("gamepad_move_y_axis", config.inputBindings.gamepadMoveYAxis);
addBindingMember("gamepad_look_x_axis", config.inputBindings.gamepadLookXAxis);
addBindingMember("gamepad_look_y_axis", config.inputBindings.gamepadLookYAxis);
addBindingMember("gamepad_dpad_up", config.inputBindings.gamepadDpadUpButton);
addBindingMember("gamepad_dpad_down", config.inputBindings.gamepadDpadDownButton);
addBindingMember("gamepad_dpad_left", config.inputBindings.gamepadDpadLeftButton);
addBindingMember("gamepad_dpad_right", config.inputBindings.gamepadDpadRightButton);
auto addMappingObject = [&](const char* name,
const std::unordered_map<std::string, std::string>& mappings,
rapidjson::Value& target) {
rapidjson::Value mappingObject(rapidjson::kObjectType);
for (const auto& [key, value] : mappings) {
rapidjson::Value keyValue(key.c_str(), allocator);
rapidjson::Value stringValue(value.c_str(), allocator);
mappingObject.AddMember(keyValue, stringValue, allocator);
}
target.AddMember(rapidjson::Value(name, allocator), mappingObject, allocator);
};
addMappingObject("gamepad_button_actions", config.inputBindings.gamepadButtonActions, bindingsObject);
addMappingObject("gamepad_axis_actions", config.inputBindings.gamepadAxisActions, bindingsObject);
bindingsObject.AddMember("gamepad_axis_action_threshold",
config.inputBindings.gamepadAxisActionThreshold, allocator);
document.AddMember("input_bindings", bindingsObject, allocator);
std::filesystem::path projectRoot = scriptsDir.parent_path();
if (!projectRoot.empty()) {
addStringMember("project_root", projectRoot.string());

View File

@@ -1,5 +1,9 @@
#include "sdl_input_service.hpp"
#include <algorithm>
#include <cctype>
#include <cmath>
namespace {
constexpr float kAxisPositiveMax = static_cast<float>(SDL_JOYSTICK_AXIS_MAX);
constexpr float kAxisNegativeMax = static_cast<float>(-SDL_JOYSTICK_AXIS_MIN);
@@ -23,12 +27,15 @@ const std::unordered_map<SDL_Keycode, std::string> SdlInputService::kGuiKeyNames
{SDLK_DELETE, "delete"}, {SDLK_RETURN, "return"}, {SDLK_TAB, "tab"},
{SDLK_ESCAPE, "escape"}, {SDLK_LCTRL, "lctrl"}, {SDLK_RCTRL, "rctrl"},
{SDLK_LSHIFT, "lshift"}, {SDLK_RSHIFT, "rshift"}, {SDLK_LALT, "lalt"},
{SDLK_RALT, "ralt"}, {SDLK_w, "w"}, {SDLK_a, "a"}, {SDLK_s, "s"},
{SDLK_d, "d"}, {SDLK_m, "m"}
{SDLK_RALT, "ralt"}
};
SdlInputService::SdlInputService(std::shared_ptr<events::IEventBus> eventBus, std::shared_ptr<ILogger> logger)
: eventBus_(std::move(eventBus)), logger_(logger) {
SdlInputService::SdlInputService(std::shared_ptr<events::IEventBus> eventBus,
std::shared_ptr<IConfigService> configService,
std::shared_ptr<ILogger> logger)
: eventBus_(std::move(eventBus)),
configService_(std::move(configService)),
logger_(logger) {
// Subscribe to input events
eventBus_->Subscribe(events::EventType::KeyPressed, [this](const events::Event& e) {
@@ -63,6 +70,7 @@ SdlInputService::SdlInputService(std::shared_ptr<events::IEventBus> eventBus, st
logger_->Trace("SdlInputService", "SdlInputService",
"eventBus=" + std::string(eventBus_ ? "set" : "null"));
}
BuildActionKeyMapping();
EnsureGamepadSubsystem();
}
@@ -80,24 +88,12 @@ void SdlInputService::ProcessEvent(const SDL_Event& event) {
switch (event.type) {
case SDL_EVENT_KEY_DOWN:
state_.keysPressed.insert(event.key.key);
// GUI input processing
{
auto it = kGuiKeyNames.find(event.key.key);
if (it != kGuiKeyNames.end()) {
guiInputSnapshot_.keyStates[it->second] = true;
}
}
ApplyKeyMapping(event.key.key, true);
break;
case SDL_EVENT_KEY_UP:
state_.keysPressed.erase(event.key.key);
// GUI input processing
{
auto it = kGuiKeyNames.find(event.key.key);
if (it != kGuiKeyNames.end()) {
guiInputSnapshot_.keyStates[it->second] = false;
}
}
ApplyKeyMapping(event.key.key, false);
break;
case SDL_EVENT_MOUSE_MOTION:
@@ -189,10 +185,7 @@ void SdlInputService::OnKeyPressed(const events::Event& event) {
", repeat=" + std::string(keyEvent.repeat ? "true" : "false"));
}
state_.keysPressed.insert(keyEvent.key);
auto it = kGuiKeyNames.find(keyEvent.key);
if (it != kGuiKeyNames.end()) {
guiInputSnapshot_.keyStates[it->second] = true;
}
ApplyKeyMapping(keyEvent.key, true);
}
void SdlInputService::OnKeyReleased(const events::Event& event) {
@@ -205,10 +198,7 @@ void SdlInputService::OnKeyReleased(const events::Event& event) {
", repeat=" + std::string(keyEvent.repeat ? "true" : "false"));
}
state_.keysPressed.erase(keyEvent.key);
auto it = kGuiKeyNames.find(keyEvent.key);
if (it != kGuiKeyNames.end()) {
guiInputSnapshot_.keyStates[it->second] = false;
}
ApplyKeyMapping(keyEvent.key, false);
}
void SdlInputService::OnMouseMoved(const events::Event& event) {
@@ -279,6 +269,167 @@ void SdlInputService::OnTextInput(const events::Event& event) {
guiInputSnapshot_.textInput += textEvent.text;
}
void SdlInputService::BuildActionKeyMapping() {
actionKeyNames_.clear();
gamepadButtonActions_.clear();
gamepadAxisActions_.clear();
if (!configService_) {
if (logger_) {
logger_->Trace("SdlInputService", "BuildActionKeyMapping", "configService=null");
}
return;
}
const auto& bindings = configService_->GetInputBindings();
auto addKey = [&](const char* actionName, const std::string& keyName) {
if (keyName.empty()) {
return;
}
SDL_Keycode key = SDL_GetKeyFromName(keyName.c_str());
if (key == SDLK_UNKNOWN) {
if (logger_) {
logger_->Error("SdlInputService: unknown key binding for " + std::string(actionName) +
" -> " + keyName);
}
return;
}
actionKeyNames_[key] = actionName;
if (logger_) {
logger_->Trace("SdlInputService", "BuildActionKeyMapping",
"action=" + std::string(actionName) +
", keyName=" + keyName +
", keyCode=" + std::to_string(static_cast<int>(key)));
}
};
addKey("move_forward", bindings.moveForwardKey);
addKey("move_back", bindings.moveBackKey);
addKey("move_left", bindings.moveLeftKey);
addKey("move_right", bindings.moveRightKey);
addKey("music_toggle", bindings.musicToggleKey);
auto toLower = [](std::string value) {
std::transform(value.begin(), value.end(), value.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return value;
};
auto setButton = [&](const char* bindingName, const std::string& buttonValue, SDL_GamepadButton& target) {
if (buttonValue.empty()) {
return;
}
std::string normalized = toLower(buttonValue);
SDL_GamepadButton button = SDL_GetGamepadButtonFromString(normalized.c_str());
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
target = button;
} else if (logger_) {
logger_->Error("SdlInputService: unknown gamepad button binding for " +
std::string(bindingName) + " -> " + buttonValue);
}
};
setButton("music_toggle_gamepad", bindings.musicToggleGamepadButton, musicToggleButton_);
setButton("gamepad_dpad_up", bindings.gamepadDpadUpButton, dpadUpButton_);
setButton("gamepad_dpad_down", bindings.gamepadDpadDownButton, dpadDownButton_);
setButton("gamepad_dpad_left", bindings.gamepadDpadLeftButton, dpadLeftButton_);
setButton("gamepad_dpad_right", bindings.gamepadDpadRightButton, dpadRightButton_);
auto setAxis = [&](const char* axisName, const std::string& axisValue, SDL_GamepadAxis& target) {
if (axisValue.empty()) {
return;
}
std::string normalized = toLower(axisValue);
SDL_GamepadAxis axis = SDL_GetGamepadAxisFromString(normalized.c_str());
if (axis != SDL_GAMEPAD_AXIS_INVALID) {
target = axis;
} else if (logger_) {
logger_->Error("SdlInputService: unknown gamepad axis binding for " +
std::string(axisName) + " -> " + axisValue);
}
};
setAxis("gamepad_move_x_axis", bindings.gamepadMoveXAxis, moveXAxis_);
setAxis("gamepad_move_y_axis", bindings.gamepadMoveYAxis, moveYAxis_);
setAxis("gamepad_look_x_axis", bindings.gamepadLookXAxis, lookXAxis_);
setAxis("gamepad_look_y_axis", bindings.gamepadLookYAxis, lookYAxis_);
for (const auto& [buttonName, actionName] : bindings.gamepadButtonActions) {
if (buttonName.empty() || actionName.empty()) {
continue;
}
std::string normalized = toLower(buttonName);
SDL_GamepadButton button = SDL_GetGamepadButtonFromString(normalized.c_str());
if (button == SDL_GAMEPAD_BUTTON_INVALID) {
if (logger_) {
logger_->Error("SdlInputService: unknown gamepad button mapping " +
buttonName + " -> " + actionName);
}
continue;
}
gamepadButtonActions_[button] = actionName;
}
for (const auto& [axisName, actionName] : bindings.gamepadAxisActions) {
if (axisName.empty() || actionName.empty()) {
continue;
}
std::string normalized = toLower(axisName);
SDL_GamepadAxis axis = SDL_GetGamepadAxisFromString(normalized.c_str());
if (axis == SDL_GAMEPAD_AXIS_INVALID) {
if (logger_) {
logger_->Error("SdlInputService: unknown gamepad axis mapping " +
axisName + " -> " + actionName);
}
continue;
}
gamepadAxisActions_[axis] = actionName;
}
gamepadAxisActionThreshold_ = bindings.gamepadAxisActionThreshold;
if (gamepadAxisActionThreshold_ < 0.0f) {
gamepadAxisActionThreshold_ = 0.0f;
} else if (gamepadAxisActionThreshold_ > 1.0f) {
gamepadAxisActionThreshold_ = 1.0f;
}
if (logger_) {
logger_->Trace("SdlInputService", "BuildActionKeyMapping",
"musicToggleButton=" + std::to_string(static_cast<int>(musicToggleButton_)) +
", dpadUpButton=" + std::to_string(static_cast<int>(dpadUpButton_)) +
", dpadDownButton=" + std::to_string(static_cast<int>(dpadDownButton_)) +
", dpadLeftButton=" + std::to_string(static_cast<int>(dpadLeftButton_)) +
", dpadRightButton=" + std::to_string(static_cast<int>(dpadRightButton_)) +
", moveXAxis=" + std::to_string(static_cast<int>(moveXAxis_)) +
", moveYAxis=" + std::to_string(static_cast<int>(moveYAxis_)) +
", lookXAxis=" + std::to_string(static_cast<int>(lookXAxis_)) +
", lookYAxis=" + std::to_string(static_cast<int>(lookYAxis_)) +
", buttonActions=" + std::to_string(gamepadButtonActions_.size()) +
", axisActions=" + std::to_string(gamepadAxisActions_.size()) +
", axisThreshold=" + std::to_string(gamepadAxisActionThreshold_));
}
}
void SdlInputService::ApplyKeyMapping(SDL_Keycode key, bool isDown) {
auto actionIt = actionKeyNames_.find(key);
if (actionIt != actionKeyNames_.end()) {
guiInputSnapshot_.keyStates[actionIt->second] = isDown;
}
auto guiIt = kGuiKeyNames.find(key);
if (guiIt != kGuiKeyNames.end()) {
guiInputSnapshot_.keyStates[guiIt->second] = isDown;
}
}
bool SdlInputService::IsActionKeyPressed(const std::string& action) const {
for (const auto& key : state_.keysPressed) {
auto it = actionKeyNames_.find(key);
if (it != actionKeyNames_.end() && it->second == action) {
return true;
}
}
return false;
}
void SdlInputService::EnsureGamepadSubsystem() {
uint32_t initialized = SDL_WasInit(0);
if ((initialized & SDL_INIT_GAMEPAD) != 0) {
@@ -355,12 +506,45 @@ void SdlInputService::UpdateGamepadSnapshot() {
}
guiInputSnapshot_.gamepadConnected = true;
guiInputSnapshot_.gamepadLeftX = NormalizeAxis(SDL_GetGamepadAxis(gamepad_, SDL_GAMEPAD_AXIS_LEFTX));
guiInputSnapshot_.gamepadLeftY = NormalizeAxis(SDL_GetGamepadAxis(gamepad_, SDL_GAMEPAD_AXIS_LEFTY));
guiInputSnapshot_.gamepadRightX = NormalizeAxis(SDL_GetGamepadAxis(gamepad_, SDL_GAMEPAD_AXIS_RIGHTX));
guiInputSnapshot_.gamepadRightY = NormalizeAxis(SDL_GetGamepadAxis(gamepad_, SDL_GAMEPAD_AXIS_RIGHTY));
guiInputSnapshot_.gamepadLeftX = 0.0f;
guiInputSnapshot_.gamepadLeftY = 0.0f;
guiInputSnapshot_.gamepadRightX = 0.0f;
guiInputSnapshot_.gamepadRightY = 0.0f;
if (moveXAxis_ != SDL_GAMEPAD_AXIS_INVALID) {
guiInputSnapshot_.gamepadLeftX = NormalizeAxis(SDL_GetGamepadAxis(gamepad_, moveXAxis_));
}
if (moveYAxis_ != SDL_GAMEPAD_AXIS_INVALID) {
guiInputSnapshot_.gamepadLeftY = NormalizeAxis(SDL_GetGamepadAxis(gamepad_, moveYAxis_));
}
if (lookXAxis_ != SDL_GAMEPAD_AXIS_INVALID) {
guiInputSnapshot_.gamepadRightX = NormalizeAxis(SDL_GetGamepadAxis(gamepad_, lookXAxis_));
}
if (lookYAxis_ != SDL_GAMEPAD_AXIS_INVALID) {
guiInputSnapshot_.gamepadRightY = NormalizeAxis(SDL_GetGamepadAxis(gamepad_, lookYAxis_));
}
guiInputSnapshot_.gamepadTogglePressed =
SDL_GetGamepadButton(gamepad_, SDL_GAMEPAD_BUTTON_START);
SDL_GetGamepadButton(gamepad_, musicToggleButton_);
auto updateActionState = [&](const std::string& actionName, bool gamepadPressed) {
bool keyboardPressed = IsActionKeyPressed(actionName);
bool current = guiInputSnapshot_.keyStates[actionName];
guiInputSnapshot_.keyStates[actionName] = current || keyboardPressed || gamepadPressed;
};
updateActionState("move_forward", SDL_GetGamepadButton(gamepad_, dpadUpButton_));
updateActionState("move_back", SDL_GetGamepadButton(gamepad_, dpadDownButton_));
updateActionState("move_left", SDL_GetGamepadButton(gamepad_, dpadLeftButton_));
updateActionState("move_right", SDL_GetGamepadButton(gamepad_, dpadRightButton_));
for (const auto& [button, actionName] : gamepadButtonActions_) {
updateActionState(actionName, SDL_GetGamepadButton(gamepad_, button));
}
for (const auto& [axis, actionName] : gamepadAxisActions_) {
float value = NormalizeAxis(SDL_GetGamepadAxis(gamepad_, axis));
bool pressed = std::fabs(value) >= gamepadAxisActionThreshold_;
updateActionState(actionName, pressed);
}
}
void SdlInputService::SetGuiScriptService(IGuiScriptService* guiScriptService) {

View File

@@ -3,8 +3,10 @@
#include "../interfaces/i_input_service.hpp"
#include "../interfaces/i_gui_script_service.hpp"
#include "../interfaces/i_logger.hpp"
#include "../interfaces/i_config_service.hpp"
#include "../../events/i_event_bus.hpp"
#include <memory>
#include <unordered_map>
namespace sdl3cpp::services::impl {
@@ -23,7 +25,9 @@ public:
*
* @param eventBus Event bus to subscribe to
*/
explicit SdlInputService(std::shared_ptr<events::IEventBus> eventBus, std::shared_ptr<ILogger> logger);
explicit SdlInputService(std::shared_ptr<events::IEventBus> eventBus,
std::shared_ptr<IConfigService> configService,
std::shared_ptr<ILogger> logger);
~SdlInputService() override;
// IInputService interface
@@ -43,11 +47,25 @@ public:
private:
std::shared_ptr<events::IEventBus> eventBus_;
std::shared_ptr<IConfigService> configService_;
std::shared_ptr<ILogger> logger_;
InputState state_;
GuiInputSnapshot guiInputSnapshot_;
IGuiScriptService* guiScriptService_ = nullptr;
SDL_Gamepad* gamepad_ = nullptr;
SDL_GamepadButton musicToggleButton_ = SDL_GAMEPAD_BUTTON_START;
SDL_GamepadButton dpadUpButton_ = SDL_GAMEPAD_BUTTON_DPAD_UP;
SDL_GamepadButton dpadDownButton_ = SDL_GAMEPAD_BUTTON_DPAD_DOWN;
SDL_GamepadButton dpadLeftButton_ = SDL_GAMEPAD_BUTTON_DPAD_LEFT;
SDL_GamepadButton dpadRightButton_ = SDL_GAMEPAD_BUTTON_DPAD_RIGHT;
SDL_GamepadAxis moveXAxis_ = SDL_GAMEPAD_AXIS_LEFTX;
SDL_GamepadAxis moveYAxis_ = SDL_GAMEPAD_AXIS_LEFTY;
SDL_GamepadAxis lookXAxis_ = SDL_GAMEPAD_AXIS_RIGHTX;
SDL_GamepadAxis lookYAxis_ = SDL_GAMEPAD_AXIS_RIGHTY;
std::unordered_map<SDL_GamepadButton, std::string> gamepadButtonActions_;
std::unordered_map<SDL_GamepadAxis, std::string> gamepadAxisActions_;
float gamepadAxisActionThreshold_ = 0.5f;
std::unordered_map<SDL_Keycode, std::string> actionKeyNames_;
// Event bus listeners
void OnKeyPressed(const events::Event& event);
@@ -61,6 +79,9 @@ private:
void TryOpenGamepad();
void CloseGamepad();
void UpdateGamepadSnapshot();
void BuildActionKeyMapping();
void ApplyKeyMapping(SDL_Keycode key, bool isDown);
bool IsActionKeyPressed(const std::string& action) const;
// GUI key mapping (extracted from old Sdl3App)
static const std::unordered_map<SDL_Keycode, std::string> kGuiKeyNames;

View File

@@ -3,9 +3,47 @@
#include <cstdint>
#include <filesystem>
#include <string>
#include <unordered_map>
namespace sdl3cpp::services {
/**
* @brief Input bindings for game and UI actions.
*/
struct InputBindings {
std::string moveForwardKey = "W";
std::string moveBackKey = "S";
std::string moveLeftKey = "A";
std::string moveRightKey = "D";
std::string musicToggleKey = "M";
std::string musicToggleGamepadButton = "start";
std::string gamepadMoveXAxis = "leftx";
std::string gamepadMoveYAxis = "lefty";
std::string gamepadLookXAxis = "rightx";
std::string gamepadLookYAxis = "righty";
std::string gamepadDpadUpButton = "dpad_up";
std::string gamepadDpadDownButton = "dpad_down";
std::string gamepadDpadLeftButton = "dpad_left";
std::string gamepadDpadRightButton = "dpad_right";
std::unordered_map<std::string, std::string> gamepadButtonActions = {
{"south", "gamepad_a"},
{"east", "gamepad_b"},
{"west", "gamepad_x"},
{"north", "gamepad_y"},
{"left_shoulder", "gamepad_lb"},
{"right_shoulder", "gamepad_rb"},
{"left_stick", "gamepad_ls"},
{"right_stick", "gamepad_rs"},
{"back", "gamepad_back"},
{"start", "gamepad_start"}
};
std::unordered_map<std::string, std::string> gamepadAxisActions = {
{"left_trigger", "gamepad_lt"},
{"right_trigger", "gamepad_rt"}
};
float gamepadAxisActionThreshold = 0.5f;
};
/**
* @brief Runtime configuration values used across services.
*/
@@ -15,6 +53,7 @@ struct RuntimeConfig {
std::filesystem::path scriptPath;
bool luaDebug = false;
std::string windowTitle = "SDL3 Vulkan Demo";
InputBindings inputBindings{};
};
} // namespace sdl3cpp::services

View File

@@ -1,5 +1,6 @@
#pragma once
#include "config_types.hpp"
#include <cstdint>
#include <filesystem>
#include <string>
@@ -52,6 +53,12 @@ public:
* @return List of extension names
*/
virtual std::vector<const char*> GetDeviceExtensions() const = 0;
/**
* @brief Get configured input bindings.
* @return Input bindings structure
*/
virtual const InputBindings& GetInputBindings() const = 0;
};
} // namespace sdl3cpp::services