Adapt game input for Switch controllers
This commit is contained in:
@@ -204,6 +204,7 @@ bool Application::initCoreModules() {
|
|||||||
ScopedStartupTrace stageTrace("SDL_GameControllerOpen(0)");
|
ScopedStartupTrace stageTrace("SDL_GameControllerOpen(0)");
|
||||||
if (SDL_GameController *controller = SDL_GameControllerOpen(0)) {
|
if (SDL_GameController *controller = SDL_GameControllerOpen(0)) {
|
||||||
SDL_Log("GameController opened: %s", SDL_GameControllerName(controller));
|
SDL_Log("GameController opened: %s", SDL_GameControllerName(controller));
|
||||||
|
SDL_GameControllerClose(controller);
|
||||||
} else {
|
} else {
|
||||||
SDL_Log("No GameController found");
|
SDL_Log("No GameController found");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,11 +123,18 @@ mindmap
|
|||||||
- `core/asset_paths.h`
|
- `core/asset_paths.h`
|
||||||
- `core/input_state.*`
|
- `core/input_state.*`
|
||||||
- `core/debug_flags.h`
|
- `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`,统一传递全局服务。
|
- 增加 `GameContext`,统一传递全局服务。
|
||||||
- 输入层支持键盘和手柄统一映射。
|
- 增加可配置键位表,让 PC 和 NS 可以分别覆盖默认映射。
|
||||||
|
|
||||||
### 2. Scene
|
### 2. Scene
|
||||||
|
|
||||||
@@ -312,7 +319,6 @@ mindmap
|
|||||||
|
|
||||||
下一步:
|
下一步:
|
||||||
|
|
||||||
- 将 Switch Joy-Con 输入接入 `InputStateProvider`。
|
|
||||||
- 在 Ryujinx 或真机上验证主循环、shader 和角色纹理。
|
- 在 Ryujinx 或真机上验证主循环、shader 和角色纹理。
|
||||||
- 如果 RomFS 不可用,再补 SD 卡外部资源部署脚本。
|
- 如果 RomFS 不可用,再补 SD 卡外部资源部署脚本。
|
||||||
|
|
||||||
@@ -320,7 +326,7 @@ mindmap
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
输入
|
输入
|
||||||
SDL Keyboard / Controller
|
SDL Keyboard / Controller / Switch PadState
|
||||||
-> InputStateProvider
|
-> InputStateProvider
|
||||||
-> InputState
|
-> InputState
|
||||||
-> PlayerActor
|
-> PlayerActor
|
||||||
@@ -401,11 +407,11 @@ mindmap
|
|||||||
下一步建议按这个顺序做:
|
下一步建议按这个顺序做:
|
||||||
|
|
||||||
1. `BattleZone`:做清版推进的核心框架。
|
1. `BattleZone`:做清版推进的核心框架。
|
||||||
2. `SwitchInput`:把 Joy-Con/手柄输入接入 `InputStateProvider`。
|
2. `EnemyActor`:做第一个敌人占位。
|
||||||
3. `EnemyActor`:做第一个敌人占位。
|
3. `CombatSystem`:把 hitbox/hurtbox 真正跑起来。
|
||||||
4. `CombatSystem`:把 hitbox/hurtbox 真正跑起来。
|
4. `SpriteAnimator`:把玩家动画从硬编码改成组件。
|
||||||
5. `SpriteAnimator`:把玩家动画从硬编码改成组件。
|
5. `StageLayer`:把白盒视觉层扩展为背景、tiles 和 props。
|
||||||
6. `StageLayer`:把白盒视觉层扩展为背景、tiles 和 props。
|
6. `InputConfig`:把默认键位/手柄映射迁移到可配置数据。
|
||||||
|
|
||||||
## 素材协作提示
|
## 素材协作提示
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,141 @@
|
|||||||
#include "input_state.h"
|
#include "input_state.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace ns_game {
|
namespace ns_game {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr Sint16 kControllerAxisDeadZone = 10000;
|
||||||
|
|
||||||
bool isDown(const Uint8* keys, SDL_Scancode key) {
|
bool isDown(const Uint8* keys, SDL_Scancode key) {
|
||||||
return keys && keys[key] != 0;
|
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
|
} // 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() {
|
InputState InputStateProvider::Sample() {
|
||||||
|
ensureController();
|
||||||
|
|
||||||
const Uint8* keys = SDL_GetKeyboardState(nullptr);
|
const Uint8* keys = SDL_GetKeyboardState(nullptr);
|
||||||
InputState state;
|
InputState state;
|
||||||
|
bool moveLeftHeld = false;
|
||||||
|
bool moveRightHeld = false;
|
||||||
|
|
||||||
if (isDown(keys, SDL_SCANCODE_A) || isDown(keys, SDL_SCANCODE_LEFT)) {
|
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)) {
|
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_SPACE) || isDown(keys, SDL_SCANCODE_W) ||
|
||||||
isDown(keys, SDL_SCANCODE_UP);
|
isDown(keys, SDL_SCANCODE_UP);
|
||||||
const bool attackHeld =
|
bool attackHeld =
|
||||||
isDown(keys, SDL_SCANCODE_J) || isDown(keys, SDL_SCANCODE_K);
|
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.jumpPressed = jumpHeld && !jumpHeldLastFrame_;
|
||||||
state.attackPressed = attackHeld && !attackHeldLastFrame_;
|
state.attackPressed = attackHeld && !attackHeldLastFrame_;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL2/SDL_gamecontroller.h>
|
||||||
|
|
||||||
namespace ns_game {
|
namespace ns_game {
|
||||||
|
|
||||||
struct InputState {
|
struct InputState {
|
||||||
@@ -10,9 +12,18 @@ struct InputState {
|
|||||||
|
|
||||||
class InputStateProvider {
|
class InputStateProvider {
|
||||||
public:
|
public:
|
||||||
|
InputStateProvider();
|
||||||
|
~InputStateProvider();
|
||||||
|
|
||||||
|
InputStateProvider(const InputStateProvider&) = delete;
|
||||||
|
InputStateProvider& operator=(const InputStateProvider&) = delete;
|
||||||
|
|
||||||
InputState Sample();
|
InputState Sample();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void ensureController();
|
||||||
|
|
||||||
|
SDL_GameController* controller_ = nullptr;
|
||||||
bool jumpHeldLastFrame_ = false;
|
bool jumpHeldLastFrame_ = false;
|
||||||
bool attackHeldLastFrame_ = false;
|
bool attackHeldLastFrame_ = false;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user