修复 LightGame Windows 输入问题并完善手动亮度控制

- 重构 PlayerController 输入:移除 IButtonInput 依赖,纯使用 IKeyboardState 与 IPointerInput;触摸屏改为上半屏跳跃、左右 1/3 分区分左右移动
- GameStateManager 增加 ESC 键暂停/恢复支持
- 将键盘亮度控制从 SdlPhotoSensor 平台层上提到 LightGameApp 游戏层(W/S 键),并引入 has_manual_override_ 标志防止 photo sensor 默认值覆盖手动调整
- SDLDisplay 初始化时调用 SDL_StopTextInput(),缓解 Windows 中文输入法在按字母键后拦截方向键/空格的问题
- IKeyboardState.h 补充 KEY_S、KEY_ESC、KEY_RETURN 常量
This commit is contained in:
SepComet 2026-06-11 10:41:53 +08:00
parent fda8b120d3
commit acf162d1b9
9 changed files with 98 additions and 79 deletions

View File

@ -2,6 +2,7 @@
#include "DrawContext.h" #include "DrawContext.h"
#include "BitmapFont.h" #include "BitmapFont.h"
#include "ButtonInput.h" #include "ButtonInput.h"
#include "IKeyboardState.h"
#include <cstdio> #include <cstdio>
namespace LightGame namespace LightGame
@ -10,14 +11,19 @@ namespace LightGame
: state_(GameState::Title), : state_(GameState::Title),
hud_(), hud_(),
title_blink_ms_(0), title_blink_ms_(0),
title_show_prompt_(true) title_show_prompt_(true),
esc_was_down_(false)
{ {
} }
void GameStateManager::update(GameState current_state, const Platform::IButtonInput* button, uint32_t dt_ms) void GameStateManager::update(GameState current_state, const Platform::IButtonInput* button, const Platform::IKeyboardState* keyboard, uint32_t dt_ms)
{ {
state_ = current_state; state_ = current_state;
const bool esc_down = keyboard && keyboard->is_key_down(Platform::KEY_ESC);
const bool esc_pressed = esc_down && !esc_was_down_;
esc_was_down_ = esc_down;
switch (state_) switch (state_)
{ {
case GameState::Title: case GameState::Title:
@ -30,14 +36,14 @@ namespace LightGame
break; break;
case GameState::Playing: case GameState::Playing:
if (button && button->was_pressed()) if (esc_pressed || (button && button->was_pressed()))
{ {
state_ = GameState::Paused; state_ = GameState::Paused;
} }
break; break;
case GameState::Paused: case GameState::Paused:
if (button && button->was_pressed()) if (esc_pressed || (button && button->was_pressed()))
{ {
state_ = GameState::Playing; state_ = GameState::Playing;
} }

View File

@ -15,6 +15,7 @@ namespace RenderData
namespace Platform namespace Platform
{ {
class IButtonInput; class IButtonInput;
class IKeyboardState;
} }
namespace LightGame namespace LightGame
@ -53,11 +54,12 @@ namespace LightGame
HudData hud_; HudData hud_;
uint32_t title_blink_ms_; uint32_t title_blink_ms_;
bool title_show_prompt_; bool title_show_prompt_;
bool esc_was_down_;
public: public:
GameStateManager(); GameStateManager();
void update(GameState current_state, const Platform::IButtonInput* button, uint32_t dt_ms); void update(GameState current_state, const Platform::IButtonInput* button, const Platform::IKeyboardState* keyboard, uint32_t dt_ms);
void draw(Core::DrawContext& ctx, const RenderData::BitmapFont& font, int32_t screen_w, int32_t screen_h); void draw(Core::DrawContext& ctx, const RenderData::BitmapFont& font, int32_t screen_w, int32_t screen_h);
void set_state(GameState state) { state_ = state; } void set_state(GameState state) { state_ = state; }

View File

@ -9,6 +9,7 @@
#include "Level1Data.h" #include "Level1Data.h"
#include "Level2Data.h" #include "Level2Data.h"
#include "Level3Data.h" #include "Level3Data.h"
#include "sprite_atlas.h"
namespace LightGame namespace LightGame
{ {
@ -44,21 +45,58 @@ namespace LightGame
renderer_(), renderer_(),
current_level_index_(0), current_level_index_(0),
player_id_(0), player_id_(0),
manual_light_level_(2048),
light_step_(256),
has_manual_override_(false),
debug_mode_(false) debug_mode_(false)
{ {
camera_.configure(screen_width, screen_height); camera_.configure(screen_width, screen_height);
camera_.set_dead_zone(60, 40); camera_.set_dead_zone(60, 40);
static const RenderData::Sprite player_idle[] = {
sprite_atlas::spr_player_idle_0,
sprite_atlas::spr_player_idle_1
};
static const RenderData::Sprite player_run[] = {
sprite_atlas::spr_player_run_0,
sprite_atlas::spr_player_run_1,
sprite_atlas::spr_player_run_2,
sprite_atlas::spr_player_run_3
};
player_controller_.set_sprites(
player_idle, 2,
player_run, 4,
&sprite_atlas::spr_player_jump,
&sprite_atlas::spr_player_fall);
} }
void LightGameApp::update(uint32_t dt_ms) void LightGameApp::update(uint32_t dt_ms)
{ {
if (photo_sensor_ && photo_sensor_->is_open()) if (keyboard_input_)
{
if (keyboard_input_->is_key_down(Platform::KEY_W))
{
const uint32_t next = static_cast<uint32_t>(manual_light_level_) + light_step_;
manual_light_level_ = static_cast<uint16_t>(next > 4095 ? 4095 : next);
has_manual_override_ = true;
}
if (keyboard_input_->is_key_down(Platform::KEY_S))
{
manual_light_level_ = manual_light_level_ >= light_step_
? manual_light_level_ - light_step_
: 0;
has_manual_override_ = true;
}
}
if (photo_sensor_ && photo_sensor_->is_open() && !has_manual_override_)
{ {
photo_sensor_->update(); photo_sensor_->update();
manual_light_level_ = photo_sensor_->read_level();
} }
const GameState prev_state = state_manager_.get_state(); const GameState prev_state = state_manager_.get_state();
state_manager_.update(prev_state, button_input_, dt_ms); state_manager_.update(prev_state, button_input_, keyboard_input_, dt_ms);
switch (state_manager_.get_state()) switch (state_manager_.get_state())
{ {
@ -86,7 +124,7 @@ namespace LightGame
if (state_manager_.get_state() == GameState::Playing || if (state_manager_.get_state() == GameState::Playing ||
state_manager_.get_state() == GameState::Paused) state_manager_.get_state() == GameState::Paused)
{ {
const uint16_t light = photo_sensor_ ? photo_sensor_->read_level() : 2048; const uint16_t light = manual_light_level_;
renderer_.draw(ctx, level_, camera_, light_system_, light); renderer_.draw(ctx, level_, camera_, light_system_, light);
if (debug_mode_) if (debug_mode_)
@ -99,7 +137,7 @@ namespace LightGame
if (debug_mode_ && state_manager_.get_state() == GameState::Playing) if (debug_mode_ && state_manager_.get_state() == GameState::Playing)
{ {
const uint16_t light = photo_sensor_ ? photo_sensor_->read_level() : 2048; const uint16_t light = manual_light_level_;
char buf[32]; char buf[32];
snprintf(buf, sizeof(buf), "L:%d", light); snprintf(buf, sizeof(buf), "L:%d", light);
ctx.draw_text(font, screen_width_ - 64, screen_height_ - 16, ctx.draw_text(font, screen_width_ - 64, screen_height_ - 16,
@ -130,10 +168,15 @@ namespace LightGame
state_manager_.get_hud().current_level = level_index + 1; state_manager_.get_hud().current_level = level_index + 1;
} }
bool LightGameApp::is_action_pressed()
{
return button_input_ && button_input_->was_pressed();
}
void LightGameApp::update_title(uint32_t dt_ms) void LightGameApp::update_title(uint32_t dt_ms)
{ {
(void)dt_ms; (void)dt_ms;
if (button_input_ && button_input_->was_pressed()) if (is_action_pressed())
{ {
load_level(0); load_level(0);
state_manager_.set_state(GameState::Playing); state_manager_.set_state(GameState::Playing);
@ -142,14 +185,14 @@ namespace LightGame
void LightGameApp::update_playing(uint32_t dt_ms) void LightGameApp::update_playing(uint32_t dt_ms)
{ {
const uint16_t light = photo_sensor_ ? photo_sensor_->read_level() : 2048; const uint16_t light = manual_light_level_;
light_system_.update(level_.get_all_objects(), light); light_system_.update(level_.get_all_objects(), light);
state_manager_.get_hud().light_level = light; state_manager_.get_hud().light_level = light;
GameObject* player = level_.get_object(player_id_); GameObject* player = level_.get_object(player_id_);
if (player) if (player)
{ {
player_controller_.update(*player, level_, physics_, button_input_, keyboard_input_, pointer_input_, dt_ms); player_controller_.update(*player, level_, physics_, keyboard_input_, pointer_input_, screen_width_, screen_height_, dt_ms);
camera_.follow(player->position); camera_.follow(player->position);
if (player_controller_.is_dead()) if (player_controller_.is_dead())
@ -168,7 +211,7 @@ namespace LightGame
void LightGameApp::update_game_over(uint32_t dt_ms) void LightGameApp::update_game_over(uint32_t dt_ms)
{ {
(void)dt_ms; (void)dt_ms;
if (button_input_ && button_input_->was_pressed()) if (is_action_pressed())
{ {
state_manager_.get_hud().lives = 3; state_manager_.get_hud().lives = 3;
state_manager_.get_hud().collectibles = 0; state_manager_.get_hud().collectibles = 0;
@ -180,7 +223,7 @@ namespace LightGame
void LightGameApp::update_level_complete(uint32_t dt_ms) void LightGameApp::update_level_complete(uint32_t dt_ms)
{ {
(void)dt_ms; (void)dt_ms;
if (button_input_ && button_input_->was_pressed()) if (is_action_pressed())
{ {
const int32_t next = current_level_index_ + 1; const int32_t next = current_level_index_ + 1;
if (next < kLevelCount) if (next < kLevelCount)

View File

@ -52,6 +52,10 @@ namespace LightGame
int32_t current_level_index_; int32_t current_level_index_;
uint32_t player_id_; uint32_t player_id_;
uint16_t manual_light_level_;
uint16_t light_step_;
bool has_manual_override_;
bool debug_mode_; bool debug_mode_;
public: public:
@ -76,5 +80,6 @@ namespace LightGame
void update_game_over(uint32_t dt_ms); void update_game_over(uint32_t dt_ms);
void update_level_complete(uint32_t dt_ms); void update_level_complete(uint32_t dt_ms);
void check_triggers(); void check_triggers();
bool is_action_pressed();
}; };
} }

View File

@ -1,5 +1,4 @@
#include "PlayerController.h" #include "PlayerController.h"
#include "ButtonInput.h"
#include "IKeyboardState.h" #include "IKeyboardState.h"
#include "PointerInput.h" #include "PointerInput.h"
#include <algorithm> #include <algorithm>
@ -44,9 +43,9 @@ namespace LightGame
} }
void PlayerController::update(GameObject& obj, Level& level, Physics2D& physics, void PlayerController::update(GameObject& obj, Level& level, Physics2D& physics,
const Platform::IButtonInput* button,
const Platform::IKeyboardState* keyboard, const Platform::IKeyboardState* keyboard,
const Platform::IPointerInput* pointer, const Platform::IPointerInput* pointer,
int32_t screen_width, int32_t screen_height,
uint32_t dt_ms) uint32_t dt_ms)
{ {
if (state_ == PlayerState::Dead) if (state_ == PlayerState::Dead)
@ -59,7 +58,7 @@ namespace LightGame
return; return;
} }
update_input(button, keyboard, pointer); update_input(keyboard, pointer, screen_width, screen_height);
update_movement(obj, dt_ms); update_movement(obj, dt_ms);
physics.apply_gravity(obj, dt_ms); physics.apply_gravity(obj, dt_ms);
@ -111,48 +110,45 @@ namespace LightGame
check_death(obj, level); check_death(obj, level);
} }
void PlayerController::update_input(const Platform::IButtonInput* button, void PlayerController::update_input(const Platform::IKeyboardState* keyboard,
const Platform::IKeyboardState* keyboard, const Platform::IPointerInput* pointer,
const Platform::IPointerInput* pointer) int32_t screen_width, int32_t screen_height)
{ {
move_dir_ = 0; move_dir_ = 0;
jump_held_ = false; jump_held_ = false;
if (button)
{
if (button->is_down())
{
jump_held_ = true;
}
}
if (pointer) if (pointer)
{ {
if (pointer->is_down()) if (pointer->is_down())
{ {
const int32_t px = pointer->get_x(); const int32_t px = pointer->get_x();
if (px < 400) const int32_t py = pointer->get_y();
// Top half of screen = jump (works across the full width)
if (py < screen_height / 2)
{
jump_held_ = true;
}
// Left 1/3 = move left, right 1/3 = move right
if (px < screen_width / 3)
{ {
move_dir_ = -1; move_dir_ = -1;
} }
else if (px > 600) else if (px > screen_width * 2 / 3)
{ {
move_dir_ = 1; move_dir_ = 1;
} }
else
{
jump_held_ = true;
}
} }
} }
if (keyboard) if (keyboard)
{ {
if (keyboard->is_key_down(Platform::KEY_LEFT) || keyboard->is_key_down(Platform::KEY_A)) if (keyboard->is_key_down(Platform::KEY_LEFT))
move_dir_ = -1; move_dir_ = -1;
if (keyboard->is_key_down(Platform::KEY_RIGHT) || keyboard->is_key_down(Platform::KEY_D)) if (keyboard->is_key_down(Platform::KEY_RIGHT))
move_dir_ = 1; move_dir_ = 1;
if (keyboard->is_key_down(Platform::KEY_SPACE) || keyboard->is_key_down(Platform::KEY_UP) || keyboard->is_key_down(Platform::KEY_W)) if (keyboard->is_key_down(Platform::KEY_UP))
jump_held_ = true; jump_held_ = true;
} }
} }

View File

@ -6,7 +6,6 @@
namespace Platform namespace Platform
{ {
class IButtonInput;
class IKeyboardState; class IKeyboardState;
class IPointerInput; class IPointerInput;
} }
@ -72,16 +71,16 @@ namespace LightGame
const RenderData::Sprite* fall); const RenderData::Sprite* fall);
void update(GameObject& obj, Level& level, Physics2D& physics, void update(GameObject& obj, Level& level, Physics2D& physics,
const Platform::IButtonInput* button,
const Platform::IKeyboardState* keyboard, const Platform::IKeyboardState* keyboard,
const Platform::IPointerInput* pointer, const Platform::IPointerInput* pointer,
int32_t screen_width, int32_t screen_height,
uint32_t dt_ms); uint32_t dt_ms);
PlayerState get_state() const { return state_; } PlayerState get_state() const { return state_; }
bool is_dead() const { return state_ == PlayerState::Dead; } bool is_dead() const { return state_ == PlayerState::Dead; }
private: private:
void update_input(const Platform::IButtonInput* button, const Platform::IKeyboardState* keyboard, const Platform::IPointerInput* pointer); void update_input(const Platform::IKeyboardState* keyboard, const Platform::IPointerInput* pointer, int32_t screen_width, int32_t screen_height);
void update_movement(GameObject& obj, uint32_t dt_ms); void update_movement(GameObject& obj, uint32_t dt_ms);
void update_animation(GameObject& obj, uint32_t dt_ms); void update_animation(GameObject& obj, uint32_t dt_ms);
void check_death(GameObject& obj, Level& level); void check_death(GameObject& obj, Level& level);

View File

@ -7,11 +7,14 @@ namespace Platform
constexpr int KEY_A = 4; constexpr int KEY_A = 4;
constexpr int KEY_D = 7; constexpr int KEY_D = 7;
constexpr int KEY_W = 26; constexpr int KEY_W = 26;
constexpr int KEY_S = 22;
constexpr int KEY_SPACE = 44; constexpr int KEY_SPACE = 44;
constexpr int KEY_LEFT = 80; constexpr int KEY_LEFT = 80;
constexpr int KEY_RIGHT = 79; constexpr int KEY_RIGHT = 79;
constexpr int KEY_UP = 82; constexpr int KEY_UP = 82;
constexpr int KEY_DOWN = 81; constexpr int KEY_DOWN = 81;
constexpr int KEY_ESC = 41;
constexpr int KEY_RETURN = 40;
class IKeyboardState class IKeyboardState
{ {

View File

@ -57,6 +57,7 @@ namespace Platform
return false; return false;
} }
SDL_StopTextInput();
return true; return true;
} }

View File

@ -50,45 +50,9 @@ namespace Platform
void SdlPhotoSensor::update() void SdlPhotoSensor::update()
{ {
if (!opened_) // No-op: keyboard-based brightness control is handled in the game layer
{ // (LightGameApp) through IKeyboardState, not at the platform level.
return; (void)opened_;
}
SDL_PumpEvents();
const Uint8* keyboard_state = SDL_GetKeyboardState(nullptr);
if (!keyboard_state)
{
return;
}
if (keyboard_state[SDL_SCANCODE_UP])
{
uint32_t next = static_cast<uint32_t>(level_) + step_;
level_ = static_cast<uint16_t>(std::min(next, static_cast<uint32_t>(max_level_)));
}
if (keyboard_state[SDL_SCANCODE_DOWN])
{
if (level_ >= step_)
{
level_ -= step_;
}
else
{
level_ = min_level_;
}
}
if (keyboard_state[SDL_SCANCODE_HOME])
{
level_ = max_level_;
}
if (keyboard_state[SDL_SCANCODE_END])
{
level_ = min_level_;
}
} }
void SdlPhotoSensor::shutdown() void SdlPhotoSensor::shutdown()