From d5e876e542dd68d8dab89450e1f098c147451558 Mon Sep 17 00:00:00 2001 From: 1165565692-lang <1165565692@qq.com> Date: Tue, 9 Jun 2026 13:28:31 +0800 Subject: [PATCH] Adapt game input for Switch controllers --- .../src/frostbite2D/core/application.cpp | 1 + game/docs/planning/game_architecture_board.md | 22 ++-- game/src/core/input_state.cpp | 115 +++++++++++++++++- game/src/core/input_state.h | 11 ++ 4 files changed, 137 insertions(+), 12 deletions(-) diff --git a/Frostbite2D/src/frostbite2D/core/application.cpp b/Frostbite2D/src/frostbite2D/core/application.cpp index 7723d3a..d250aca 100644 --- a/Frostbite2D/src/frostbite2D/core/application.cpp +++ b/Frostbite2D/src/frostbite2D/core/application.cpp @@ -204,6 +204,7 @@ bool Application::initCoreModules() { ScopedStartupTrace stageTrace("SDL_GameControllerOpen(0)"); if (SDL_GameController *controller = SDL_GameControllerOpen(0)) { SDL_Log("GameController opened: %s", SDL_GameControllerName(controller)); + SDL_GameControllerClose(controller); } else { SDL_Log("No GameController found"); } diff --git a/game/docs/planning/game_architecture_board.md b/game/docs/planning/game_architecture_board.md index 63acdd3..a2fd1a2 100644 --- a/game/docs/planning/game_architecture_board.md +++ b/game/docs/planning/game_architecture_board.md @@ -123,11 +123,18 @@ mindmap - `core/asset_paths.h` - `core/input_state.*` - `core/debug_flags.h` +- `InputStateProvider` 已聚合键盘、SDL GameController 和 Switch 原生 `PadState`。 + +当前输入映射: + +- 移动:键盘 `A/D`、方向键、手柄左摇杆、方向键、Switch 左摇杆/方向键。 +- 跳跃:键盘 `Space/W/Up`、手柄 `A/B`、Switch `A/B`。 +- 攻击:键盘 `J/K`、手柄 `X/Y`、Switch `X/Y`。 下一步: - 增加 `GameContext`,统一传递全局服务。 -- 输入层支持键盘和手柄统一映射。 +- 增加可配置键位表,让 PC 和 NS 可以分别覆盖默认映射。 ### 2. Scene @@ -312,7 +319,6 @@ mindmap 下一步: -- 将 Switch Joy-Con 输入接入 `InputStateProvider`。 - 在 Ryujinx 或真机上验证主循环、shader 和角色纹理。 - 如果 RomFS 不可用,再补 SD 卡外部资源部署脚本。 @@ -320,7 +326,7 @@ mindmap ```text 输入 - SDL Keyboard / Controller + SDL Keyboard / Controller / Switch PadState -> InputStateProvider -> InputState -> PlayerActor @@ -401,11 +407,11 @@ mindmap 下一步建议按这个顺序做: 1. `BattleZone`:做清版推进的核心框架。 -2. `SwitchInput`:把 Joy-Con/手柄输入接入 `InputStateProvider`。 -3. `EnemyActor`:做第一个敌人占位。 -4. `CombatSystem`:把 hitbox/hurtbox 真正跑起来。 -5. `SpriteAnimator`:把玩家动画从硬编码改成组件。 -6. `StageLayer`:把白盒视觉层扩展为背景、tiles 和 props。 +2. `EnemyActor`:做第一个敌人占位。 +3. `CombatSystem`:把 hitbox/hurtbox 真正跑起来。 +4. `SpriteAnimator`:把玩家动画从硬编码改成组件。 +5. `StageLayer`:把白盒视觉层扩展为背景、tiles 和 props。 +6. `InputConfig`:把默认键位/手柄映射迁移到可配置数据。 ## 素材协作提示 diff --git a/game/src/core/input_state.cpp b/game/src/core/input_state.cpp index 2ef7fe4..1ea777c 100644 --- a/game/src/core/input_state.cpp +++ b/game/src/core/input_state.cpp @@ -1,34 +1,141 @@ #include "input_state.h" #include +#include + +#ifdef __SWITCH__ +#include +#endif namespace ns_game { namespace { +constexpr Sint16 kControllerAxisDeadZone = 10000; + bool isDown(const Uint8* keys, SDL_Scancode key) { return keys && keys[key] != 0; } +bool controllerButtonDown(SDL_GameController* controller, + SDL_GameControllerButton button) { + return controller && + SDL_GameControllerGetButton(controller, button) == SDL_PRESSED; +} + +Sint16 controllerAxis(SDL_GameController* controller, + SDL_GameControllerAxis axis) { + if (!controller) { + return 0; + } + return SDL_GameControllerGetAxis(controller, axis); +} + +#ifdef __SWITCH__ +PadState& switchPad() { + static PadState pad; + static bool initialized = false; + if (!initialized) { + padInitializeDefault(&pad); + initialized = true; + } + return pad; +} +#endif + } // namespace +InputStateProvider::InputStateProvider() { + ensureController(); +} + +InputStateProvider::~InputStateProvider() { + if (controller_) { + SDL_GameControllerClose(controller_); + controller_ = nullptr; + } +} + +void InputStateProvider::ensureController() { + if (controller_) { + return; + } + + const int joystickCount = SDL_NumJoysticks(); + for (int i = 0; i < joystickCount; ++i) { + if (!SDL_IsGameController(i)) { + continue; + } + + controller_ = SDL_GameControllerOpen(i); + if (controller_) { + SDL_Log("InputStateProvider opened controller: %s", + SDL_GameControllerName(controller_)); + return; + } + } +} + InputState InputStateProvider::Sample() { + ensureController(); + const Uint8* keys = SDL_GetKeyboardState(nullptr); InputState state; + bool moveLeftHeld = false; + bool moveRightHeld = false; if (isDown(keys, SDL_SCANCODE_A) || isDown(keys, SDL_SCANCODE_LEFT)) { - state.moveX -= 1.0f; + moveLeftHeld = true; } if (isDown(keys, SDL_SCANCODE_D) || isDown(keys, SDL_SCANCODE_RIGHT)) { - state.moveX += 1.0f; + moveRightHeld = true; } - const bool jumpHeld = + bool jumpHeld = isDown(keys, SDL_SCANCODE_SPACE) || isDown(keys, SDL_SCANCODE_W) || isDown(keys, SDL_SCANCODE_UP); - const bool attackHeld = + bool attackHeld = isDown(keys, SDL_SCANCODE_J) || isDown(keys, SDL_SCANCODE_K); + if (controller_) { + const Sint16 leftX = + controllerAxis(controller_, SDL_CONTROLLER_AXIS_LEFTX); + moveLeftHeld = moveLeftHeld || + leftX < -kControllerAxisDeadZone || + controllerButtonDown(controller_, + SDL_CONTROLLER_BUTTON_DPAD_LEFT); + moveRightHeld = moveRightHeld || + leftX > kControllerAxisDeadZone || + controllerButtonDown(controller_, + SDL_CONTROLLER_BUTTON_DPAD_RIGHT); + jumpHeld = jumpHeld || + controllerButtonDown(controller_, SDL_CONTROLLER_BUTTON_A) || + controllerButtonDown(controller_, SDL_CONTROLLER_BUTTON_B); + attackHeld = attackHeld || + controllerButtonDown(controller_, SDL_CONTROLLER_BUTTON_X) || + controllerButtonDown(controller_, SDL_CONTROLLER_BUTTON_Y); + } + +#ifdef __SWITCH__ + PadState& pad = switchPad(); + padUpdate(&pad); + const u64 buttons = padGetButtons(&pad); + moveLeftHeld = moveLeftHeld || (buttons & HidNpadButton_AnyLeft) != 0; + moveRightHeld = moveRightHeld || (buttons & HidNpadButton_AnyRight) != 0; + jumpHeld = jumpHeld || + (buttons & (HidNpadButton_A | HidNpadButton_B)) != 0; + attackHeld = attackHeld || + (buttons & (HidNpadButton_X | HidNpadButton_Y)) != 0; +#endif + + if (moveLeftHeld) { + state.moveX -= 1.0f; + } + if (moveRightHeld) { + state.moveX += 1.0f; + } + state.moveX = std::clamp(state.moveX, -1.0f, 1.0f); + state.jumpPressed = jumpHeld && !jumpHeldLastFrame_; state.attackPressed = attackHeld && !attackHeldLastFrame_; diff --git a/game/src/core/input_state.h b/game/src/core/input_state.h index f6b5d0a..c07f71e 100644 --- a/game/src/core/input_state.h +++ b/game/src/core/input_state.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace ns_game { struct InputState { @@ -10,9 +12,18 @@ struct InputState { class InputStateProvider { public: + InputStateProvider(); + ~InputStateProvider(); + + InputStateProvider(const InputStateProvider&) = delete; + InputStateProvider& operator=(const InputStateProvider&) = delete; + InputState Sample(); private: + void ensureController(); + + SDL_GameController* controller_ = nullptr; bool jumpHeldLastFrame_ = false; bool attackHeldLastFrame_ = false; };