Files
XCEngine/docs/used/XCEngine输入系统设计.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

547 lines
14 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.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<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;
// 事件
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.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 <Windows.h>
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 现有的组件模式和事件系统。