# XCEngine 输入系统设计与实现 > **文档信息** > - **版本**: 1.0 > - **日期**: 2026-03-22 > - **状态**: 设计文档 > - **目标**: 设计引擎级输入系统 --- ## 1. 概述 ### 1.1 设计目标 XCEngine 输入系统需要提供: 1. **统一的跨平台输入抽象** - 支持键盘、鼠标、手柄、触摸 2. **与引擎架构无缝集成** - 使用现有的 `Core::Event` 系统 3. **轮询 + 事件混合模式** - 既支持 `IsKeyDown()` 轮询,也支持事件回调 4. **UI 系统支持** - 为 UI 组件 (Button, Slider 等) 提供指针事件 ### 1.2 当前状态分析 | 模块 | 状态 | 说明 | |-----|------|------| | Core::Event | ✅ 完备 | 线程安全,Subscribe/Unsubscribe 模式 | | RHI::RHISwapChain | ⚠️ PollEvents 空实现 | 需要填充 Windows 消息泵 | | 现有 Input (mvs) | ❌ 耦合 Windows | 直接处理 HWND 消息,不适合引擎架构 | | Platform/Window | ❌ 不存在 | 需要新建 | --- ## 2. 架构设计 ### 2.1 模块结构 ``` engine/include/XCEngine/ ├── Core/ │ └── Event.h # 已有,复用 ├── Input/ │ ├── InputTypes.h # 枚举和结构体定义 │ ├── InputEvent.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. **与现有引擎组件集成** - 使用 `Core::Event` 作为事件系统 - 使用 `Math::Vector2` 作为 2D 坐标类型 --- ## 3. 详细设计 ### 3.1 输入类型定义 (`InputTypes.h`) ```cpp #pragma once #include "Core/Types.h" #include "Math/Vector2.h" namespace XCEngine { namespace Input { enum class KeyCode : uint8 { None = 0, 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 = 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 }; enum class MouseButton : uint8 { Left = 0, Right = 1, Middle = 2, Button4 = 3, Button5 = 4 }; enum class JoystickButton : uint8 { Button0 = 0, Button1 = 1, Button2 = 2, // ... Xbox/PlayStation 标准按钮 }; 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 输入管理器 (`InputManager.h`) ```cpp #pragma once #include "Core/Event.h" #include "InputTypes.h" #include "InputEvent.h" #include "Math/Vector2.h" namespace XCEngine { namespace Input { class InputManager { public: static InputManager& Get(); void Initialize(void* platformWindowHandle); void Shutdown(); void Update(); // 每帧调用,更新输入状态 // ============ 轮询接口 ============ // 键盘 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 GetJoystickCount() const; bool IsJoystickConnected(int joystick) const; Math::Vector2 GetJoystickAxis(int joystick, JoystickAxis axis) const; bool IsJoystickButtonDown(int joystick, JoystickButton button) const; // ============ 事件接口 ============ 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; // 事件 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.4 平台输入模块接口 (`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.5 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; // 供 Window 调用的消息处理 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); HWND m_hwnd = nullptr; bool m_captureMouse = false; }; } // namespace Platform } // namespace Input } // namespace XCEngine ``` --- ## 4. 与引擎其他模块的集成 ### 4.1 与 RHI/SwapChain 的集成 `RHISwapChain::PollEvents()` 需要调用 `InputManager::Update()` 和平台输入模块的 `PumpEvents()`: ```cpp // D3D12SwapChain.cpp void D3D12SwapChain::PollEvents() { // 抽取 Windows 消息 if (m_inputModule) { m_inputModule->PumpEvents(); } // 更新输入管理器状态 Input::InputManager::Get().Update(); // 处理关闭请求 MSG msg; if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { m_shouldClose = true; } TranslateMessage(&msg); DispatchMessage(&msg); } } ``` ### 4.2 与 UI 系统的集成 UI 组件 (Button, Slider 等) 通过订阅 `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 (input.IsMouseButtonClicked(Input::MouseButton::Left)) { OnClick.Invoke(); } } } ``` ### 4.3 与场景生命周期的集成 ```cpp // Scene.cpp void Scene::Awake() { // 初始化输入系统 Input::InputManager::Get().Initialize(m_windowHandle); } void Scene::OnDestroy() { Input::InputManager::Get().Shutdown(); } ``` --- ## 5. Windows 消息到引擎事件的映射 | Windows Message | Engine Event | |----------------|---------------| | WM_KEYDOWN | `InputManager::ProcessKeyDown` | | WM_KEYUP | `InputManager::ProcessKeyUp` | | WM_CHAR | `InputManager::ProcessTextInput` | | WM_MOUSEMOVE | `InputManager::ProcessMouseMove` | | WM_LBUTTONDOWN/RBUTTONDOWN/MBUTTONDOWN | `InputManager::ProcessMouseButton` | | WM_LBUTTONUP/RBUTTONUP/MBUTTONUP | `InputManager::ProcessMouseButton` | | WM_MOUSEWHEEL | `InputManager::ProcessMouseWheel` | ### 5.1 Windows VK 码到 KeyCode 的映射 ```cpp // WindowsVKToKeyCode 映射表 static const KeyCode VK_TO_KEYCODE[] = { // VK 0-29 对应 KeyCode A-Z KeyCode::A, KeyCode::B, KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F, KeyCode::G, KeyCode::H, KeyCode::I, KeyCode::J, KeyCode::K, KeyCode::L, KeyCode::M, KeyCode::N, KeyCode::O, KeyCode::P, KeyCode::Q, KeyCode::R, KeyCode::S, KeyCode::T, KeyCode::U, KeyCode::V, KeyCode::W, KeyCode::X, KeyCode::Y, KeyCode::Z, // ... 其他映射 }; ``` --- ## 6. 实现计划 ### Phase 1: 核心输入系统 | 任务 | 文件 | 说明 | |-----|------|------| | 创建 Input 模块目录 | `engine/include/XCEngine/Input/` | | | 实现 InputTypes.h | KeyCode, MouseButton 等枚举 | | | 实现 InputEvent.h | 事件结构体 | | | 实现 InputManager.h/cpp | 单例管理器 | | | 实现 WindowsInputModule.h/cpp | Windows 平台实现 | | ### Phase 2: 与 RHI 集成 | 任务 | 文件 | 说明 | |-----|------|------| | 修改 RHISwapChain | 添加 InputModule 成员 | | | 实现 D3D12SwapChain::PollEvents | 填充消息泵逻辑 | | | 实现 OpenGLSwapChain::PollEvents | GL 消息处理 | | ### Phase 3: UI 输入支持 | 任务 | 说明 | |-----|------| | 实现 Canvas 组件的射线检测 | 将屏幕坐标转换为 UI 元素 | | Button/Slider 事件集成 | 使用 InputManager 事件 | --- ## 7. 使用示例 ### 7.1 轮询模式(角色移动) ```cpp void PlayerController::Update(float deltaTime) { auto& input = Input::InputManager::Get(); if (input.IsKeyDown(Input::KeyCode::W)) { m_velocity.z = 1.0f; } else if (input.IsKeyDown(Input::KeyCode::S)) { m_velocity.z = -1.0f; } if (input.IsKeyDown(Input::KeyCode::A)) { m_velocity.x = -1.0f; } else if (input.IsKeyDown(Input::KeyCode::D)) { m_velocity.x = 1.0f; } } ``` ### 7.2 事件模式(UI 交互) ```cpp class MyUIButton : public Component { uint64_t m_clickHandlerId = 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(); } } ); } void OnDestroy() override { Input::InputManager::Get().OnMouseButton().Unsubscribe(m_clickHandlerId); } }; ``` ### 7.3 文本输入 ```cpp void InputFieldComponent::Update(float deltaTime) { auto& input = Input::InputManager::Get(); uint64_t textHandlerId = input.OnTextInput().Subscribe( [this](const Input::TextInputEvent& event) { if (m_isFocused) { m_text += event.character; OnValueChanged.Invoke(m_text); } } ); // 清理 input.OnTextInput().Unsubscribe(textHandlerId); } ``` --- ## 8. 待讨论问题 1. **多窗口支持**: 引擎是否需要支持多窗口?如果是,输入系统如何路由事件到正确的窗口? 2. **手柄/游戏手柄支持**: 是否需要实现 XInput 支持?这会增加复杂性。 3. **原始输入 vs 输入模式**: Unity 有 "Input.GetAxis" vs "Input.GetButtonDown"。是否需要实现类似的轴概念? 4. **IME/输入法支持**: 对于中文、日文输入,是否需要特殊处理? 5. **平台模块位置**: `InputModule` 放在 `Input/` 还是 `Platform/` 命名空间下? --- ## 9. 结论 XCEngine 输入系统设计遵循以下核心原则: 1. **复用现有 Core::Event** - 不重复造轮子 2. **平台抽象** - 通过 `InputModule` 接口隔离平台差异 3. **双模式支持** - 轮询 + 事件,兼顾性能和响应性 4. **与 UI 架构协同** - 为 Canvas/Button 提供必要的事件支持 该设计参考了 Unity 的 Input 系统架构,同时适配了 XCEngine 现有的组件模式和事件系统。