# XCEngine 输入系统设计与实现 > **文档信息** > - **版本**: 1.1 > - **日期**: 2026-03-22 > - **状态**: 设计文档 > - **目标**: 设计引擎级输入系统 --- ## 1. 概述 ### 1.1 设计目标 XCEngine 输入系统需要提供: 1. **统一的跨平台输入抽象** - 支持键盘、鼠标、触摸(手柄暂不考虑) 2. **与引擎架构无缝集成** - 使用现有的 `Core::Event` 系统 3. **轮询 + 事件混合模式** - 既支持 `IsKeyDown()` 轮询,也支持事件回调 4. **UI 系统支持** - 为 UI 组件 (Button, Slider 等) 提供指针事件 5. **输入轴支持** - 参考 Unity 的 Input.GetAxis 设计,支持键盘/手柄统一映射 ### 1.2 当前状态分析 | 模块 | 状态 | 说明 | |-----|------|------| | Core::Event | ✅ 完备 | 线程安全,Subscribe/Unsubscribe 模式 | | RHI::RHISwapChain | ⚠️ PollEvents 空实现 | 需要填充 Windows 消息泵 | | 现有 Input (mvs) | ❌ 耦合 Windows | 直接处理 HWND 消息,不适合引擎架构 | | Platform/Window | ❌ 不存在 | 需要新建 | ### 1.3 设计决策 | 问题 | 决策 | |-----|------| | 多窗口 | ❌ 单窗口,简化设计 | | 手柄支持 | ❌ 暂不考虑,预留接口即可 | | 输入轴 | ✅ 参考 Unity 实现 Input.GetAxis | | IME/多语言 | ❌ 暂不考虑,只处理英文字符 | | 模块位置 | ✅ InputModule 放在 Input/ 命名空间 | --- ## 2. 架构设计 ### 2.1 模块结构 ``` engine/include/XCEngine/ ├── Core/ │ └── Event.h # 已有,复用 ├── Input/ │ ├── InputTypes.h # 枚举和结构体定义 │ ├── InputEvent.h # 输入事件结构体 │ ├── InputAxis.h # 输入轴定义 │ ├── InputManager.h # 输入管理器(单例) │ └── InputModule.h # 平台相关输入处理模块接口 └── Platform/ ├── PlatformTypes.h # 平台类型抽象 ├── Window.h # 窗口抽象接口 └── Windows/ ├── WindowsPlatform.h # Windows 平台实现 └── WindowsInputModule.h # Windows 输入模块实现 ``` ### 2.2 核心设计原则 1. **事件驱动 + 状态轮询双模式** - 事件:用于 UI 交互、一次性按键响应 - 轮询:用于连续输入检测(角色移动、视角控制) 2. **平台抽象层** - `InputModule` 接口:抽象平台特定的输入处理 - `Window` 接口:抽象平台特定的窗口管理 3. **输入轴映射** - 键盘和手柄可以映射到同一个轴,实现统一输入 - 支持在配置文件中定义轴映射关系 4. **与现有引擎组件集成** - 使用 `Core::Event` 作为事件系统 - 使用 `Math::Vector2` 作为 2D 坐标类型 --- ## 3. 详细设计 ### 3.1 输入类型定义 (`InputTypes.h`) ```cpp #pragma once #include "Core/Types.h" namespace XCEngine { namespace Input { enum class KeyCode : uint8 { None = 0, // 字母键 A-Z A = 4, B = 5, C = 6, D = 7, E = 8, F = 9, G = 10, H = 11, I = 12, J = 13, K = 14, L = 15, M = 16, N = 17, O = 18, P = 19, Q = 20, R = 21, S = 22, T = 23, U = 24, V = 25, W = 26, X = 27, Y = 28, Z = 29, // 功能键 F1-F12 F1 = 58, F2 = 59, F3 = 60, F4 = 61, F5 = 62, F6 = 63, F7 = 64, F8 = 65, F9 = 66, F10 = 67, F11 = 68, F12 = 69, // 控制键 Space = 49, Tab = 48, Enter = 36, Escape = 53, LeftShift = 56, RightShift = 60, LeftCtrl = 59, RightCtrl = 62, LeftAlt = 58, RightAlt = 61, // 方向键 Up = 126, Down = 125, Left = 123, Right = 124, // 编辑键 Home = 115, End = 119, PageUp = 116, PageDown = 121, Delete = 51, Backspace = 51, // 数字键 0-9 Zero = 39, One = 30, Two = 31, Three = 32, Four = 33, Five = 34, Six = 35, Seven = 37, Eight = 38, Nine = 40, // 符号键 Minus = 43, Equals = 46, BracketLeft = 47, BracketRight = 54, Semicolon = 42, Quote = 40, Comma = 54, Period = 55, Slash = 44, Backslash = 45, Backtick = 41 }; enum class MouseButton : uint8 { Left = 0, Right = 1, Middle = 2, Button4 = 3, Button5 = 4 }; enum class JoystickAxis : uint8 { LeftX = 0, LeftY = 1, RightX = 2, RightY = 3, LeftTrigger = 4, RightTrigger = 5 }; } // namespace Input } // namespace XCEngine ``` ### 3.2 输入事件结构体 (`InputEvent.h`) ```cpp #pragma once #include "InputTypes.h" #include "Math/Vector2.h" #include "Containers/String.h" namespace XCEngine { namespace Input { struct KeyEvent { KeyCode keyCode; bool alt; bool ctrl; bool shift; bool meta; enum Type { Down, Up, Repeat } type; }; struct MouseButtonEvent { MouseButton button; Math::Vector2 position; enum Type { Pressed, Released } type; }; struct MouseMoveEvent { Math::Vector2 position; Math::Vector2 delta; }; struct MouseWheelEvent { Math::Vector2 position; float delta; }; struct TextInputEvent { char character; Containers::String text; }; struct TouchState { int touchId; Math::Vector2 position; Math::Vector2 deltaPosition; float deltaTime; int tapCount; enum Phase { Began, Moved, Stationary, Ended, Canceled } phase; }; } // namespace Input } // namespace XCEngine ``` ### 3.3 输入轴定义 (`InputAxis.h`) ```cpp #pragma once #include "InputTypes.h" #include "Math/Vector2.h" #include "Containers/String.h" namespace XCEngine { namespace Input { class InputAxis { public: InputAxis() = default; InputAxis(const Containers::String& name, KeyCode positive, KeyCode negative = KeyCode::None) : m_name(name), m_positiveKey(positive), m_negativeKey(negative) {} const Containers::String& GetName() const { return m_name; } KeyCode GetPositiveKey() const { return m_positiveKey; } KeyCode GetNegativeKey() const { return m_negativeKey; } void SetKeys(KeyCode positive, KeyCode negative) { m_positiveKey = positive; m_negativeKey = negative; } float GetValue() const { return m_value; } void SetValue(float value) { m_value = value; } private: Containers::String m_name; KeyCode m_positiveKey = KeyCode::None; KeyCode m_negativeKey = KeyCode::None; float m_value = 0.0f; }; } // namespace Input } // namespace XCEngine ``` ### 3.4 输入管理器 (`InputManager.h`) ```cpp #pragma once #include "Core/Event.h" #include "InputTypes.h" #include "InputEvent.h" #include "InputAxis.h" #include "Math/Vector2.h" namespace XCEngine { namespace Input { class InputManager { public: static InputManager& Get(); void Initialize(void* platformWindowHandle); void Shutdown(); void Update(float deltaTime); // ============ 轮询接口 ============ // 键盘 bool IsKeyDown(KeyCode key) const; bool IsKeyUp(KeyCode key) const; bool IsKeyPressed(KeyCode key) const; // 鼠标 Math::Vector2 GetMousePosition() const; Math::Vector2 GetMouseDelta() const; float GetMouseScrollDelta() const; bool IsMouseButtonDown(MouseButton button) const; bool IsMouseButtonUp(MouseButton button) const; bool IsMouseButtonClicked(MouseButton button) const; // 触摸 int GetTouchCount() const; TouchState GetTouch(int index) const; // ============ 轴接口 (参考 Unity) ============ float GetAxis(const Containers::String& axisName) const; float GetAxisRaw(const Containers::String& axisName) const; bool GetButton(const Containers::String& buttonName) const; bool GetButtonDown(const Containers::String& buttonName) const; bool GetButtonUp(const Containers::String& buttonName) const; // 注册轴 void RegisterAxis(const InputAxis& axis); void RegisterButton(const Containers::String& name, KeyCode key); void ClearAxes(); // ============ 事件接口 ============ Core::Event& OnKeyEvent() { return m_onKeyEvent; } Core::Event& OnMouseButton() { return m_onMouseButton; } Core::Event& OnMouseMove() { return m_onMouseMove; } Core::Event& OnMouseWheel() { return m_onMouseWheel; } Core::Event& OnTextInput() { return m_onTextInput; } // 内部方法(供 PlatformInputModule 调用) void ProcessKeyDown(KeyCode key, bool repeat); void ProcessKeyUp(KeyCode key); void ProcessMouseMove(int x, int y, int deltaX, int deltaY); void ProcessMouseButton(MouseButton button, bool pressed, int x, int y); void ProcessMouseWheel(float delta, int x, int y); void ProcessTextInput(char c); private: InputManager() = default; ~InputManager() = default; void* m_platformWindowHandle = nullptr; // 键盘状态 std::vector m_keyDownThisFrame; std::vector m_keyDownLastFrame; std::vector m_keyDown; // 鼠标状态 Math::Vector2 m_mousePosition; Math::Vector2 m_mouseDelta; float m_mouseScrollDelta = 0.0f; std::vector m_mouseButtonDownThisFrame; std::vector m_mouseButtonDownLastFrame; std::vector m_mouseButtonDown; // 触摸状态 std::vector m_touches; // 轴映射 std::unordered_map m_axes; std::unordered_map m_buttons; std::vector m_buttonDownThisFrame; std::vector m_buttonDownLastFrame; // 事件 Core::Event m_onKeyEvent; Core::Event m_onMouseButton; Core::Event m_onMouseMove; Core::Event m_onMouseWheel; Core::Event m_onTextInput; }; } // namespace Input } // namespace XCEngine ``` ### 3.5 平台输入模块接口 (`InputModule.h`) ```cpp #pragma once namespace XCEngine { namespace Input { class InputModule { public: virtual ~InputModule() = default; virtual void Initialize(void* windowHandle) = 0; virtual void Shutdown() = 0; virtual void PumpEvents() = 0; protected: InputModule() = default; }; } // namespace Input } // namespace XCEngine ``` ### 3.6 Windows 输入模块实现 (`WindowsInputModule.h`) ```cpp #pragma once #include "InputModule.h" #include namespace XCEngine { namespace Input { namespace Platform { class WindowsInputModule : public InputModule { public: void Initialize(void* windowHandle) override; void Shutdown() override; void PumpEvents() override; void HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); private: void ProcessKeyDown(WPARAM wParam, LPARAM lParam); void ProcessKeyUp(WPARAM wParam, LPARAM lParam); void ProcessMouseMove(WPARAM wParam, LPARAM lParam); void ProcessMouseButton(WPARAM wParam, LPARAM lParam, bool pressed); void ProcessMouseWheel(WPARAM wParam, LPARAM lParam); void ProcessCharInput(WPARAM wParam, LPARAM lParam); KeyCode VKCodeToKeyCode(int vkCode); HWND m_hwnd = nullptr; Math::Vector2 m_lastMousePosition; bool m_captureMouse = false; bool m_isInitialized = false; }; } // namespace Platform } // namespace Input } // namespace XCEngine ``` --- ## 4. InputManager 实现细节 ### 4.1 状态追踪机制 ``` 每帧 Update() 调用时: 1. m_keyDownLastFrame = m_keyDownThisFrame 2. 处理 m_keyDownThisFrame = m_keyDown (当前帧按下状态) 3. 清零 m_mouseDelta, m_mouseScrollDelta IsKeyPressed(key) = m_keyDownThisFrame[key] && !m_keyDownLastFrame[key] // 当前帧按下 IsKeyDown(key) = m_keyDown[key] // 当前正按下 IsKeyUp(key) = !m_keyDown[key] // 当前已释放 ``` ### 4.2 轴值计算 ```cpp float InputManager::GetAxis(const Containers::String& axisName) const { auto it = m_axes.find(axisName); if (it == m_axes.end()) return 0.0f; const auto& axis = it->second; float value = 0.0f; if (axis.GetPositiveKey() != KeyCode::None && IsKeyDown(axis.GetPositiveKey())) { value += 1.0f; } if (axis.GetNegativeKey() != KeyCode::None && IsKeyDown(axis.GetNegativeKey())) { value -= 1.0f; } return value; } float InputManager::GetAxisRaw(const Containers::String& axisName) const { auto it = m_axes.find(axisName); if (it == m_axes.end()) return 0.0f; const auto& axis = it->second; float value = 0.0f; if (axis.GetPositiveKey() != KeyCode::None && IsKeyPressed(axis.GetPositiveKey())) { value += 1.0f; } if (axis.GetNegativeKey() != KeyCode::None && IsKeyPressed(axis.GetNegativeKey())) { value -= 1.0f; } return value; } ``` ### 4.3 默认轴配置 ```cpp void InputManager::Initialize(void* platformWindowHandle) { // 注册默认轴 (类似 Unity) RegisterAxis(InputAxis("Horizontal", KeyCode::D, KeyCode::A)); RegisterAxis(InputAxis("Vertical", KeyCode::W, KeyCode::S)); RegisterAxis(InputAxis("Mouse X", KeyCode::None, KeyCode::None)); // 鼠标驱动 RegisterAxis(InputAxis("Mouse Y", KeyCode::None, KeyCode::None)); // 鼠标驱动 // 注册默认按钮 RegisterButton("Jump", KeyCode::Space); RegisterButton("Fire1", KeyCode::LeftCtrl); RegisterButton("Fire2", KeyCode::LeftAlt); RegisterButton("Fire3", KeyCode::LeftShift); } ``` --- ## 5. Windows 消息到引擎事件的映射 ### 5.1 消息映射表 | Windows Message | Engine Method | 说明 | |----------------|---------------|------| | WM_KEYDOWN | `ProcessKeyDown` | 按键按下,wParam=VK码 | | WM_KEYUP | `ProcessKeyUp` | 按键释放,wParam=VK码 | | WM_CHAR | `ProcessTextInput` | 字符输入,wParam=字符 | | WM_MOUSEMOVE | `ProcessMouseMove` | 鼠标移动 | | WM_LBUTTONDOWN | `ProcessMouseButton(Left, Pressed)` | 左键按下 | | WM_LBUTTONUP | `ProcessMouseButton(Left, Released)` | 左键释放 | | WM_RBUTTONDOWN | `ProcessMouseButton(Right, Pressed)` | 右键按下 | | WM_RBUTTONUP | `ProcessMouseButton(Right, Released)` | 右键释放 | | WM_MBUTTONDOWN | `ProcessMouseButton(Middle, Pressed)` | 中键按下 | | WM_MBUTTONUP | `ProcessMouseButton(Middle, Released)` | 中键释放 | | WM_MOUSEWHEEL | `ProcessMouseWheel` | 滚轮滚动 | | WM_XBUTTONDOWN | `ProcessMouseButton(Button4/5, Pressed)` | 侧键按下 | | WM_XBUTTONUP | `ProcessMouseButton(Button4/5, Released)` | 侧键释放 | ### 5.2 VK 码到 KeyCode 映射 ```cpp KeyCode WindowsInputModule::VKCodeToKeyCode(int vkCode) { switch (vkCode) { case 'A': return KeyCode::A; case 'B': return KeyCode::B; case 'C': return KeyCode::C; // ... Z case VK_SPACE: return KeyCode::Space; case VK_TAB: return KeyCode::Tab; case VK_RETURN: return KeyCode::Enter; case VK_ESCAPE: return KeyCode::Escape; case VK_SHIFT: return KeyCode::LeftShift; case VK_CONTROL: return KeyCode::LeftCtrl; case VK_MENU: return KeyCode::LeftAlt; case VK_UP: return KeyCode::Up; case VK_DOWN: return KeyCode::Down; case VK_LEFT: return KeyCode::Left; case VK_RIGHT: return KeyCode::Right; case VK_HOME: return KeyCode::Home; case VK_END: return KeyCode::End; case VK_PRIOR: return KeyCode::PageUp; case VK_NEXT: return KeyCode::PageDown; case VK_DELETE: return KeyCode::Delete; case VK_BACK: return KeyCode::Backspace; case VK_F1: return KeyCode::F1; case VK_F2: return KeyCode::F2; // ... F12 case '0': return KeyCode::Zero; case '1': return KeyCode::One; // ... 9 case VK_OEM_MINUS: return KeyCode::Minus; case VK_OEM_PLUS: return KeyCode::Equals; // ... 其他 OEM 键 default: return KeyCode::None; } } ``` --- ## 6. 与引擎其他模块的集成 ### 6.1 与 RHI/SwapChain 的集成 `RHISwapChain::PollEvents()` 需要调用 `InputModule::PumpEvents()` 和 `InputManager::Update()`: ```cpp // D3D12SwapChain.cpp void D3D12SwapChain::PollEvents() { // 抽取 Windows 消息 if (m_inputModule) { m_inputModule->PumpEvents(); } // 处理其他 Windows 消息(关闭请求等) MSG msg; while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { m_shouldClose = true; break; } TranslateMessage(&msg); DispatchMessage(&msg); } } // 在 GameLoop 中每帧调用 void GameLoop::Update(float deltaTime) { // 更新输入状态(计算 IsKeyPressed 等) Input::InputManager::Get().Update(deltaTime); // 更新场景和组件 m_currentScene->Update(deltaTime); } ``` ### 6.2 与 UI 系统的集成 UI 组件通过订阅 `InputManager` 的事件来响应用户输入: ```cpp // ButtonComponent.cpp void ButtonComponent::Update(float deltaTime) { if (!IsEnabled()) return; auto& input = Input::InputManager::Get(); Vector2 mousePos = input.GetMousePosition(); // 检测鼠标是否在按钮区域内 if (IsPointInRect(mousePos, m_rect)) { // 检测悬停状态变化 if (!m_isHovered) { m_isHovered = true; OnPointerEnter.Invoke(); } // 检测点击 if (input.IsMouseButtonClicked(Input::MouseButton::Left)) { OnClick.Invoke(); } // 检测按下/释放 if (input.IsMouseButtonDown(Input::MouseButton::Left)) { OnPointerDown.Invoke(); } else if (m_wasMouseDown && !input.IsMouseButtonDown(Input::MouseButton::Left)) { OnPointerUp.Invoke(); } } else if (m_isHovered) { m_isHovered = false; OnPointerExit.Invoke(); } m_wasMouseDown = input.IsMouseButtonDown(Input::MouseButton::Left); } ``` ### 6.3 与场景生命周期的集成 ```cpp // Scene.cpp void Scene::Awake() { // 获取窗口句柄并初始化输入系统 void* windowHandle = GetEngine()->GetWindowHandle(); Input::InputManager::Get().Initialize(windowHandle); } void Scene::OnDestroy() { Input::InputManager::Get().Shutdown(); } ``` --- ## 7. 使用示例 ### 7.1 轮询模式(角色移动) ```cpp void PlayerController::Update(float deltaTime) { auto& input = Input::InputManager::Get(); // 使用轴 (推荐方式,兼容手柄) float horizontal = input.GetAxis("Horizontal"); float vertical = input.GetAxis("Vertical"); m_velocity.x = horizontal * m_moveSpeed; m_velocity.z = vertical * m_moveSpeed; // 或者使用原始轴(无平滑) float rawH = input.GetAxisRaw("Horizontal"); // 直接轮询也可以 if (input.IsKeyDown(Input::KeyCode::W)) { m_velocity.z = 1.0f; } } ``` ### 7.2 事件模式(UI 交互) ```cpp class MyUIButton : public Component { uint64_t m_clickHandlerId = 0; uint64_t m_hoverHandlerId = 0; void Awake() override { // 订阅鼠标按钮事件 m_clickHandlerId = Input::InputManager::Get().OnMouseButton().Subscribe( [this](const Input::MouseButtonEvent& event) { if (event.type == Input::MouseButtonEvent::Pressed && event.button == Input::MouseButton::Left && IsPointInButton(event.position)) { OnClicked(); } } ); // 订阅鼠标移动事件用于悬停检测 m_hoverHandlerId = Input::InputManager::Get().OnMouseMove().Subscribe( [this](const Input::MouseMoveEvent& event) { bool isHovering = IsPointInButton(event.position); if (isHovering && !m_isHovered) { m_isHovered = true; OnPointerEnter.Invoke(); } else if (!isHovering && m_isHovered) { m_isHovered = false; OnPointerExit.Invoke(); } } ); } void OnDestroy() override { Input::InputManager::Get().OnMouseButton().Unsubscribe(m_clickHandlerId); Input::InputManager::Get().OnMouseMove().Unsubscribe(m_hoverHandlerId); } }; ``` ### 7.3 文本输入 ```cpp void InputFieldComponent::Update(float deltaTime) { static uint64_t textHandlerId = 0; if (m_isFocused) { if (textHandlerId == 0) { textHandlerId = Input::InputManager::Get().OnTextInput().Subscribe( [this](const Input::TextInputEvent& event) { if (event.character == '\b') { // Backspace if (!m_text.empty()) { m_text.pop_back(); } } else if (event.character == '\r') { // Enter OnSubmit.Invoke(); } else if (isprint(event.character)) { if (m_characterLimit == 0 || m_text.length() < m_characterLimit) { m_text += event.character; OnValueChanged.Invoke(m_text); } } } ); } } else if (textHandlerId != 0) { Input::InputManager::Get().OnTextInput().Unsubscribe(textHandlerId); textHandlerId = 0; } } ``` ### 7.4 射击游戏开火 ```cpp void WeaponComponent::Update(float deltaTime) { auto& input = Input::InputManager::Get(); // 方式1: 按钮按下检测 if (input.GetButtonDown("Fire1")) { Fire(); } // 方式2: 轮询检测(适合连发) if (input.GetButton("Fire1") && m_fireRateTimer <= 0) { Fire(); m_fireRateTimer = m_fireRate; } } ``` --- ## 8. 实现计划 ### Phase 1: 核心输入系统 | 任务 | 文件 | 优先级 | |-----|------|-------| | 创建目录 | `engine/include/XCEngine/Input/` | P0 | | 实现 InputTypes.h | KeyCode, MouseButton 枚举 | P0 | | 实现 InputEvent.h | 事件结构体 | P0 | | 实现 InputAxis.h | 输入轴定义 | P1 | | 实现 InputManager.h/cpp | 单例管理器 | P0 | | 实现 InputModule.h | 平台输入模块接口 | P0 | | 实现 WindowsInputModule.h/cpp | Windows 平台实现 | P0 | ### Phase 2: 与 RHI 集成 | 任务 | 文件 | 优先级 | |-----|------|-------| | 修改 RHISwapChain | 添加 InputModule 成员 | P0 | | 实现 D3D12SwapChain::PollEvents | 填充消息泵逻辑 | P0 | | 实现 OpenGLSwapChain::PollEvents | GL 消息处理 | P1 | ### Phase 3: 轴系统完善 | 任务 | 说明 | 优先级 | |-----|------|-------| | 默认轴配置 | 注册 Horizontal, Vertical 等默认轴 | P1 | | 轴平滑处理 | GetAxis 的平滑插值 | P2 | | 配置文件支持 | 从 JSON/配置加载轴映射 | P2 | ### Phase 4: UI 输入支持 | 任务 | 说明 | 优先级 | |-----|------|-------| | Canvas 射线检测 | 将屏幕坐标转换为 UI 元素 | P1 | | Button 事件 | OnClick, OnPointerDown/Up/Enter/Exit | P1 | | Slider 拖拽 | 使用鼠标事件实现滑块拖拽 | P1 | | InputField 文本输入 | 接收 TextInputEvent | P1 | --- ## 9. 测试策略 ### 9.1 单元测试 ```cpp // test_input_manager.cpp TEST(InputManager, KeyDownDetection) { InputManager::Get().Initialize(nullptr); // 模拟按下 W 键 InputManager::Get().ProcessKeyDown(KeyCode::W, false); EXPECT_TRUE(InputManager::Get().IsKeyDown(KeyCode::W)); EXPECT_TRUE(InputManager::Get().IsKeyPressed(KeyCode::W)); // 第一帧按下 EXPECT_FALSE(InputManager::Get().IsKeyUp(KeyCode::W)); // 模拟释放 InputManager::Get().ProcessKeyUp(KeyCode::W); EXPECT_FALSE(InputManager::Get().IsKeyDown(KeyCode::W)); EXPECT_TRUE(InputManager::Get().IsKeyUp(KeyCode::W)); InputManager::Get().Shutdown(); } TEST(InputManager, AxisRegistration) { InputManager::Get().Initialize(nullptr); InputManager::Get().RegisterAxis(InputAxis("TestAxis", KeyCode::W, KeyCode::S)); EXPECT_EQ(InputManager::Get().GetAxis("TestAxis"), 0.0f); InputManager::Get().ProcessKeyDown(KeyCode::W, false); EXPECT_EQ(InputManager::Get().GetAxis("TestAxis"), 1.0f); InputManager::Get().ProcessKeyUp(KeyCode::W); InputManager::Get().ProcessKeyDown(KeyCode::S, false); EXPECT_EQ(InputManager::Get().GetAxis("TestAxis"), -1.0f); InputManager::Get().Shutdown(); } TEST(InputManager, MousePositionTracking) { InputManager::Get().Initialize(nullptr); InputManager::Get().ProcessMouseMove(100, 200, 0, 0); EXPECT_EQ(InputManager::Get().GetMousePosition(), Math::Vector2(100, 200)); InputManager::Get().ProcessMouseMove(105, 205, 5, 5); EXPECT_EQ(InputManager::Get().GetMouseDelta(), Math::Vector2(5, 5)); InputManager::Get().Shutdown(); } ``` ### 9.2 集成测试 - 测试输入系统与 SwapChain 的集成 - 测试输入系统与 Scene 的集成 - 测试 UI Button 点击响应 --- ## 10. 文件清单 ### 头文件 ``` engine/include/XCEngine/Input/ ├── InputTypes.h # 48 行 ├── InputEvent.h # 78 行 ├── InputAxis.h # 45 行 ├── InputManager.h # 130 行 └── InputModule.h # 20 行 engine/include/XCEngine/Platform/ ├── PlatformTypes.h # (新建) ├── Window.h # (新建) └── Windows/ └── WindowsInputModule.h # 55 行 ``` ### 源文件 ``` engine/src/Input/ ├── InputManager.cpp # 180 行 └── Platform/ └── Windows/ └── WindowsInputModule.cpp # 200 行 ``` ### 测试文件 ``` tests/Input/ ├── test_input_types.cpp # KeyCode/MouseButton 枚举测试 ├── test_input_event.cpp # 事件结构体测试 ├── test_input_axis.cpp # 轴注册和值计算测试 └── test_input_manager.cpp # 核心功能测试 ``` --- ## 11. 设计原则总结 1. **复用 Core::Event** - 使用现有的线程安全事件系统 2. **平台抽象** - InputModule 接口隔离 Windows 消息处理 3. **双模式支持** - 轮询 + 事件,兼顾性能和响应性 4. **轴系统** - 参考 Unity,支持键盘/手柄统一输入映射 5. **单窗口** - 简化设计,满足当前需求 6. **UI 协同** - 为 Canvas/Button 提供完整的事件支持