From e00fc1799d57ec827f2cbe004c88b1069522d7b3 Mon Sep 17 00:00:00 2001 From: SepComet <2428390463@qq.com> Date: Wed, 10 Jun 2026 15:22:30 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E9=94=AE=E7=9B=98=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E4=B8=BA=20IKeyboardState=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=8C=E6=B6=88=E9=99=A4=20App=20=E5=B1=82=E5=AF=B9=20SDL=20?= =?UTF-8?q?=E7=9A=84=E7=9B=B4=E6=8E=A5=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构键盘输入为 IKeyboardState 接口,消除 App 层对 SDL 的直接依赖 - 新增 IKeyboardState 抽象接口及 SdlKeyboardState/EvdevKeyboardState 实现 - PlayerController 改用 IKeyboardState 替代直接调用 SDL_GetKeyboardState - 移除 Camera2D.h 中对 Core 私有头文件 Camera.h 的引用 - 将 Timer.h 从 Core/Core 移至 Core/Platform,符合架构边界规范 - 键盘输入优先级调整为高于指针输入 --- CMakeLists.txt | 2 + docs/ARCHITECTURE_BOUNDARIES.md | 64 +++++++++++++++++++ src/Apps/LightGame/src/engine/Camera2D.h | 1 - .../LightGame/src/engine/LightGameApp.cpp | 5 +- src/Apps/LightGame/src/engine/LightGameApp.h | 4 ++ .../LightGame/src/engine/PlayerController.cpp | 36 ++++------- .../LightGame/src/engine/PlayerController.h | 4 +- src/Apps/LightGame/src/main.cpp | 2 + src/Core/Platform/DefaultHardware.h | 4 ++ src/Core/Platform/EvdevKeyboardState.cpp | 10 +++ src/Core/Platform/EvdevKeyboardState.h | 13 ++++ src/Core/Platform/IKeyboardState.h | 22 +++++++ src/Core/Platform/SdlKeyboardState.cpp | 12 ++++ src/Core/Platform/SdlKeyboardState.h | 13 ++++ src/Core/{Core => Platform}/Timer.h | 0 15 files changed, 164 insertions(+), 28 deletions(-) create mode 100644 docs/ARCHITECTURE_BOUNDARIES.md create mode 100644 src/Core/Platform/EvdevKeyboardState.cpp create mode 100644 src/Core/Platform/EvdevKeyboardState.h create mode 100644 src/Core/Platform/IKeyboardState.h create mode 100644 src/Core/Platform/SdlKeyboardState.cpp create mode 100644 src/Core/Platform/SdlKeyboardState.h rename src/Core/{Core => Platform}/Timer.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3faac46..0aab60d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ set(CORE_SOURCES src/Core/Platform/AlsaAudioInput.cpp src/Core/Platform/AlsaAudioOutput.cpp src/Core/Platform/EvdevButtonInput.cpp + src/Core/Platform/EvdevKeyboardState.cpp src/Core/Platform/EvdevTouchInput.cpp src/Core/Rasterizer/Rasterizer.cpp src/Core/Rasterizer/TriangleRasterizer.cpp @@ -39,6 +40,7 @@ else() src/Core/Platform/SdlAudioInput.cpp src/Core/Platform/SdlAudioOutput.cpp src/Core/Platform/SdlKeyboardButtonInput.cpp + src/Core/Platform/SdlKeyboardState.cpp src/Core/Platform/SdlPointerInput.cpp src/Core/Platform/SdlPhotoSensor.cpp ) diff --git a/docs/ARCHITECTURE_BOUNDARIES.md b/docs/ARCHITECTURE_BOUNDARIES.md new file mode 100644 index 0000000..d931916 --- /dev/null +++ b/docs/ARCHITECTURE_BOUNDARIES.md @@ -0,0 +1,64 @@ +# Architecture Boundaries + +This document defines the boundary between **Core** (engine library) and **Apps** (application layer) in the IMX6U-Game project. + +## Public API (App may include) + +App code should only include headers from these Core subdirectories: + +| Subdirectory | Contents | +|---|---| +| `Core/RenderData/` | Color, Image, Sprite, Tilemap, BoundingBox, BitmapFont | +| `Core/Math/` | Vector2, Vector3, Vector4, Matrix4x4, MathUtil | +| `Core/Platform/` | Display, ButtonInput, IKeyboardState, PointerInput, AudioInput, AudioOutput, IPhotoSensor, TimeSource, Timer, DefaultHardware | +| `Core/Draw2D/` | DrawContext | + +## Private API (App must not include) + +These subdirectories are Core internals. App code must not include headers from them or reference their types directly. + +| Subdirectory | Contents | +|---|---| +| `Core/Core/` | FrameBuffer, DepthBuffer, Renderer | +| `Core/Scene/` | Camera, Mesh, Model, Transform, Vertex | +| `Core/Rasterizer/` | Rasterizer, TriangleRasterizer | +| `Core/Shading/` | BlinnPhongShader, ShaderTypes | + +## Rules + +1. **Dependency direction**: Core must never depend on Apps. Apps depend on Core through the public API only. +2. **Platform isolation**: No `#include `, `#include `, or `#include ` outside `src/Core/Platform/`. App code accesses hardware through Platform interfaces (`IButtonInput`, `IKeyboardState`, `IPointerInput`, etc.). +3. **Interface usage**: App code should use abstract interfaces from Platform, not concrete implementation classes. Use `DefaultHardware.h` typedefs when you need a concrete type. +4. **No Core private types in App headers**: App headers must not forward-declare or reference types from private Core subdirectories. + +## Violation Checklist + +Use this during code review: + +- [ ] No `#include ` outside `src/Core/Platform/` +- [ ] No `#include ` outside `src/Core/Platform/` +- [ ] No `#include ` outside `src/Core/Platform/` +- [ ] App code includes only from public subdirectories (RenderData, Math, Platform, Draw2D) +- [ ] No App header forward-declares or references Core private types (Scene, Rasterizer, Shading, Core/Core) +- [ ] Core source files contain no references to App namespaces or types + +## Input Interface Architecture + +The Platform layer provides separate interfaces for different input modalities: + +| Interface | Purpose | Backend (Desktop) | Backend (Embedded) | +|---|---|---|---| +| `IButtonInput` | Single physical button | `SdlKeyboardButtonInput` | `EvdevButtonInput` | +| `IKeyboardState` | Multi-key keyboard state | `SdlKeyboardState` | `EvdevKeyboardState` (stub) | +| `IPointerInput` | Mouse/touch pointer | `SdlPointerInput` | `EvdevTouchInput` | +| `IPhotoSensor` | Light sensor | `SdlPhotoSensor` | `LinuxPhotoSensor` | + +App code accesses these through `DefaultHardware.h` typedefs (`DefaultButtonInput`, `DefaultKeyboardState`, etc.). + +### Input Priority + +When multiple input sources are active simultaneously, keyboard input takes priority over pointer input for movement direction. This is an intentional design choice for desktop usability. + +### Future Consolidation + +When a 4th input modality is added (e.g., gamepad), consider consolidating `IButtonInput*` + `IKeyboardState*` + `IPointerInput*` into a single `IInputState` interface to prevent parameter accumulation in application classes. diff --git a/src/Apps/LightGame/src/engine/Camera2D.h b/src/Apps/LightGame/src/engine/Camera2D.h index faa14a5..6608a81 100644 --- a/src/Apps/LightGame/src/engine/Camera2D.h +++ b/src/Apps/LightGame/src/engine/Camera2D.h @@ -2,7 +2,6 @@ #include #include "Vector2.h" -#include "Camera.h" #include "Level.h" namespace LightGame diff --git a/src/Apps/LightGame/src/engine/LightGameApp.cpp b/src/Apps/LightGame/src/engine/LightGameApp.cpp index a5c3d06..bd9f931 100644 --- a/src/Apps/LightGame/src/engine/LightGameApp.cpp +++ b/src/Apps/LightGame/src/engine/LightGameApp.cpp @@ -2,6 +2,7 @@ #include "DrawContext.h" #include "BitmapFont.h" #include "ButtonInput.h" +#include "IKeyboardState.h" #include "PointerInput.h" #include "LevelData.h" #include "LevelLoader.h" @@ -25,11 +26,13 @@ namespace LightGame int32_t screen_width, int32_t screen_height, Platform::IButtonInput* button_input, + Platform::IKeyboardState* keyboard_input, Platform::IPointerInput* pointer_input, Platform::IPhotoSensor* photo_sensor) : screen_width_(screen_width), screen_height_(screen_height), button_input_(button_input), + keyboard_input_(keyboard_input), pointer_input_(pointer_input), photo_sensor_(photo_sensor), level_(), @@ -146,7 +149,7 @@ namespace LightGame GameObject* player = level_.get_object(player_id_); if (player) { - player_controller_.update(*player, level_, physics_, button_input_, pointer_input_, dt_ms); + player_controller_.update(*player, level_, physics_, button_input_, keyboard_input_, pointer_input_, dt_ms); camera_.follow(player->position); if (player_controller_.is_dead()) diff --git a/src/Apps/LightGame/src/engine/LightGameApp.h b/src/Apps/LightGame/src/engine/LightGameApp.h index b81ce42..47334eb 100644 --- a/src/Apps/LightGame/src/engine/LightGameApp.h +++ b/src/Apps/LightGame/src/engine/LightGameApp.h @@ -23,6 +23,7 @@ namespace RenderData namespace Platform { class IButtonInput; + class IKeyboardState; class IPointerInput; } @@ -35,8 +36,10 @@ namespace LightGame int32_t screen_height_; Platform::IButtonInput* button_input_; + Platform::IKeyboardState* keyboard_input_; Platform::IPointerInput* pointer_input_; Platform::IPhotoSensor* photo_sensor_; + // TODO: consolidate input interfaces into IInputState when a 4th modality arrives Level level_; Camera2D camera_; @@ -56,6 +59,7 @@ namespace LightGame int32_t screen_width, int32_t screen_height, Platform::IButtonInput* button_input, + Platform::IKeyboardState* keyboard_input, Platform::IPointerInput* pointer_input, Platform::IPhotoSensor* photo_sensor); diff --git a/src/Apps/LightGame/src/engine/PlayerController.cpp b/src/Apps/LightGame/src/engine/PlayerController.cpp index fa14cde..7c70c1a 100644 --- a/src/Apps/LightGame/src/engine/PlayerController.cpp +++ b/src/Apps/LightGame/src/engine/PlayerController.cpp @@ -1,12 +1,9 @@ #include "PlayerController.h" #include "ButtonInput.h" +#include "IKeyboardState.h" #include "PointerInput.h" #include -#ifndef USE_FRAMEBUFFER -#include -#endif - namespace LightGame { PlayerController::PlayerController() @@ -48,6 +45,7 @@ namespace LightGame void PlayerController::update(GameObject& obj, Level& level, Physics2D& physics, const Platform::IButtonInput* button, + const Platform::IKeyboardState* keyboard, const Platform::IPointerInput* pointer, uint32_t dt_ms) { @@ -61,7 +59,7 @@ namespace LightGame return; } - update_input(button, pointer); + update_input(button, keyboard, pointer); update_movement(obj, dt_ms); physics.apply_gravity(obj, dt_ms); @@ -114,6 +112,7 @@ namespace LightGame } void PlayerController::update_input(const Platform::IButtonInput* button, + const Platform::IKeyboardState* keyboard, const Platform::IPointerInput* pointer) { move_dir_ = 0; @@ -147,27 +146,14 @@ namespace LightGame } } - if (move_dir_ == 0) + if (keyboard) { -#ifndef USE_FRAMEBUFFER - SDL_PumpEvents(); - const Uint8* keys = SDL_GetKeyboardState(nullptr); - if (keys) - { - if (keys[SDL_SCANCODE_LEFT] || keys[SDL_SCANCODE_A]) - { - move_dir_ = -1; - } - if (keys[SDL_SCANCODE_RIGHT] || keys[SDL_SCANCODE_D]) - { - move_dir_ = 1; - } - if (keys[SDL_SCANCODE_SPACE] || keys[SDL_SCANCODE_UP] || keys[SDL_SCANCODE_W]) - { - jump_held_ = true; - } - } -#endif + if (keyboard->is_key_down(Platform::KEY_LEFT) || keyboard->is_key_down(Platform::KEY_A)) + move_dir_ = -1; + if (keyboard->is_key_down(Platform::KEY_RIGHT) || keyboard->is_key_down(Platform::KEY_D)) + 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)) + jump_held_ = true; } } diff --git a/src/Apps/LightGame/src/engine/PlayerController.h b/src/Apps/LightGame/src/engine/PlayerController.h index 74d527c..cc5d343 100644 --- a/src/Apps/LightGame/src/engine/PlayerController.h +++ b/src/Apps/LightGame/src/engine/PlayerController.h @@ -7,6 +7,7 @@ namespace Platform { class IButtonInput; + class IKeyboardState; class IPointerInput; } @@ -72,6 +73,7 @@ namespace LightGame void update(GameObject& obj, Level& level, Physics2D& physics, const Platform::IButtonInput* button, + const Platform::IKeyboardState* keyboard, const Platform::IPointerInput* pointer, uint32_t dt_ms); @@ -79,7 +81,7 @@ namespace LightGame bool is_dead() const { return state_ == PlayerState::Dead; } private: - void update_input(const Platform::IButtonInput* button, const Platform::IPointerInput* pointer); + void update_input(const Platform::IButtonInput* button, const Platform::IKeyboardState* keyboard, const Platform::IPointerInput* pointer); void update_movement(GameObject& obj, uint32_t dt_ms); void update_animation(GameObject& obj, uint32_t dt_ms); void check_death(GameObject& obj, Level& level); diff --git a/src/Apps/LightGame/src/main.cpp b/src/Apps/LightGame/src/main.cpp index 58df14e..f26510a 100644 --- a/src/Apps/LightGame/src/main.cpp +++ b/src/Apps/LightGame/src/main.cpp @@ -53,6 +53,7 @@ int main(int argc, char* argv[]) } Platform::DefaultButtonInput buttonInput; + Platform::DefaultKeyboardState keyboardState; Platform::DefaultPointerInput pointerInput; Platform::DefaultPhotoSensor photoSensor; @@ -86,6 +87,7 @@ int main(int argc, char* argv[]) ScreenWidth, ScreenHeight, &buttonInput, + &keyboardState, &pointerInput, &photoSensor); diff --git a/src/Core/Platform/DefaultHardware.h b/src/Core/Platform/DefaultHardware.h index c8e9c42..df34c2f 100644 --- a/src/Core/Platform/DefaultHardware.h +++ b/src/Core/Platform/DefaultHardware.h @@ -4,12 +4,14 @@ #include "AlsaAudioInput.h" #include "AlsaAudioOutput.h" #include "EvdevButtonInput.h" +#include "EvdevKeyboardState.h" #include "EvdevTouchInput.h" #include "LinuxPhotoSensor.h" #else #include "SdlAudioInput.h" #include "SdlAudioOutput.h" #include "SdlKeyboardButtonInput.h" +#include "SdlKeyboardState.h" #include "SdlPointerInput.h" #include "SdlPhotoSensor.h" #endif @@ -20,12 +22,14 @@ namespace Platform typedef AlsaAudioInput DefaultAudioInput; typedef AlsaAudioOutput DefaultAudioOutput; typedef EvdevButtonInput DefaultButtonInput; + typedef EvdevKeyboardState DefaultKeyboardState; typedef EvdevTouchInput DefaultPointerInput; typedef LinuxPhotoSensor DefaultPhotoSensor; #else typedef SdlAudioInput DefaultAudioInput; typedef SdlAudioOutput DefaultAudioOutput; typedef SdlKeyboardButtonInput DefaultButtonInput; + typedef SdlKeyboardState DefaultKeyboardState; typedef SdlPointerInput DefaultPointerInput; typedef SdlPhotoSensor DefaultPhotoSensor; #endif diff --git a/src/Core/Platform/EvdevKeyboardState.cpp b/src/Core/Platform/EvdevKeyboardState.cpp new file mode 100644 index 0000000..431d15d --- /dev/null +++ b/src/Core/Platform/EvdevKeyboardState.cpp @@ -0,0 +1,10 @@ +#include "EvdevKeyboardState.h" + +namespace Platform +{ + bool EvdevKeyboardState::is_key_down(int /*scancode*/) const + { + // Embedded target uses physical buttons via IButtonInput, not keyboard. + return false; + } +} diff --git a/src/Core/Platform/EvdevKeyboardState.h b/src/Core/Platform/EvdevKeyboardState.h new file mode 100644 index 0000000..d1d5bc6 --- /dev/null +++ b/src/Core/Platform/EvdevKeyboardState.h @@ -0,0 +1,13 @@ +#pragma once + +#include "IKeyboardState.h" + +namespace Platform +{ + // Embedded target uses physical buttons via IButtonInput, not keyboard. + class EvdevKeyboardState : public IKeyboardState + { + public: + bool is_key_down(int scancode) const override; + }; +} diff --git a/src/Core/Platform/IKeyboardState.h b/src/Core/Platform/IKeyboardState.h new file mode 100644 index 0000000..557f5b6 --- /dev/null +++ b/src/Core/Platform/IKeyboardState.h @@ -0,0 +1,22 @@ +#pragma once + +namespace Platform +{ + // Key constants matching SDL scancode values. + // App-layer code uses these instead of including . + constexpr int KEY_A = 4; + constexpr int KEY_D = 7; + constexpr int KEY_W = 26; + constexpr int KEY_SPACE = 44; + constexpr int KEY_LEFT = 80; + constexpr int KEY_RIGHT = 79; + constexpr int KEY_UP = 82; + constexpr int KEY_DOWN = 81; + + class IKeyboardState + { + public: + virtual ~IKeyboardState() {} + virtual bool is_key_down(int scancode) const = 0; + }; +} diff --git a/src/Core/Platform/SdlKeyboardState.cpp b/src/Core/Platform/SdlKeyboardState.cpp new file mode 100644 index 0000000..6a0739d --- /dev/null +++ b/src/Core/Platform/SdlKeyboardState.cpp @@ -0,0 +1,12 @@ +#include "SdlKeyboardState.h" +#include + +namespace Platform +{ + bool SdlKeyboardState::is_key_down(int scancode) const + { + // Relies on SdlKeyboardButtonInput::update() having called SDL_PumpEvents() in the same frame. + const Uint8* state = SDL_GetKeyboardState(nullptr); + return state != nullptr && state[scancode] != 0; + } +} diff --git a/src/Core/Platform/SdlKeyboardState.h b/src/Core/Platform/SdlKeyboardState.h new file mode 100644 index 0000000..82c55a8 --- /dev/null +++ b/src/Core/Platform/SdlKeyboardState.h @@ -0,0 +1,13 @@ +#pragma once + +#include "IKeyboardState.h" + +namespace Platform +{ + class SdlKeyboardState : public IKeyboardState + { + public: + // Must be called after SdlKeyboardButtonInput::update() or equivalent SDL_PumpEvents() call. + bool is_key_down(int scancode) const override; + }; +} diff --git a/src/Core/Core/Timer.h b/src/Core/Platform/Timer.h similarity index 100% rename from src/Core/Core/Timer.h rename to src/Core/Platform/Timer.h