Files
XCEngine/docs/used/输入模块的设计与实现.md
ssdfasd 16e2065c6c Unified logging: Replace LogSystem with EditorConsoleSink
- Created EditorConsoleSink (implements ILogSink interface)
- EditorConsoleSink stores logs in memory buffer (max 1000 entries)
- Added to Debug::Logger in Application::Initialize()
- ConsolePanel now reads from EditorConsoleSink via static GetInstance()
- Removed separate LogSystem singleton
- Removed editor/src/Core/LogEntry.h (no longer needed)

Now Editor and Engine share the same Debug::Logger, with ConsolePanel
displaying logs via EditorConsoleSink.
2026-03-25 16:13:02 +08:00

923 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<const KeyEvent&>& OnKeyEvent() { return m_onKeyEvent; }
Core::Event<const MouseButtonEvent&>& OnMouseButton() { return m_onMouseButton; }
Core::Event<const MouseMoveEvent&>& OnMouseMove() { return m_onMouseMove; }
Core::Event<const MouseWheelEvent&>& OnMouseWheel() { return m_onMouseWheel; }
Core::Event<const TextInputEvent&>& 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<bool> m_keyDownThisFrame;
std::vector<bool> m_keyDownLastFrame;
std::vector<bool> m_keyDown;
// 鼠标状态
Math::Vector2 m_mousePosition;
Math::Vector2 m_mouseDelta;
float m_mouseScrollDelta = 0.0f;
std::vector<bool> m_mouseButtonDownThisFrame;
std::vector<bool> m_mouseButtonDownLastFrame;
std::vector<bool> m_mouseButtonDown;
// 触摸状态
std::vector<TouchState> m_touches;
// 轴映射
std::unordered_map<Containers::String, InputAxis> m_axes;
std::unordered_map<Containers::String, KeyCode> m_buttons;
std::vector<bool> m_buttonDownThisFrame;
std::vector<bool> m_buttonDownLastFrame;
// 事件
Core::Event<const KeyEvent&> m_onKeyEvent;
Core::Event<const MouseButtonEvent&> m_onMouseButton;
Core::Event<const MouseMoveEvent&> m_onMouseMove;
Core::Event<const MouseWheelEvent&> m_onMouseWheel;
Core::Event<const TextInputEvent&> 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 <Windows.h>
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 提供完整的事件支持