Files
CINC/docs/InputPlugin架构设计.md
T
2026-02-04 21:48:37 +08:00

197 lines
5.7 KiB
Markdown

# InputPlugin 架构设计说明
## 问题分析
### 原始设计的问题
1. **紧耦合**:直接依赖 Phaser 的 Key 对象和事件系统
2. **无法跨引擎**:切换引擎需要重写整个插件
3. **违反开闭原则**:修改输入方式需要修改插件内部代码
4. **测试困难**:难以进行单元测试(依赖具体引擎)
## 解决方案:适配器模式
### 架构层次
```
┌─────────────────────────────────────┐
│ 业务逻辑层 (GameScene) │
│ - 不关心底层实现 │
│ - 只调用统一接口 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ InputPlugin (核心插件) │
│ - 提供统一接口 │
│ - 管理适配器生命周期 │
│ - registerKey(), isDown(), isUp() │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ IInputAdapter (接口定义) │
│ - 定义适配器必须实现的方法 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 具体适配器实现 │
│ - PhaserInputAdapter │
│ - PixiInputAdapter │
│ - CocosInputAdapter │
│ - GodotInputAdapter (可扩展) │
└─────────────────────────────────────┘
```
### 核心优势
#### 1. **跨引擎复用**
```javascript
// Phaser 中使用
const phaserPlugin = new InputPlugin(scene);
phaserPlugin.registerKey('W', ['ArrowUp']);
if (phaserPlugin.isDown('W')) { /* 移动 */ }
// PixiJS 中使用(代码完全相同)
const pixiPlugin = new PixiInputPlugin(app);
pixiPlugin.registerKey('W', ['ArrowUp']);
if (pixiPlugin.isDown('W')) { /* 移动 */ }
```
#### 2. **易于扩展**
添加新引擎支持只需实现适配器接口:
```javascript
class GodotInputAdapter extends IInputAdapter {
initialize() { /* Godot 特定实现 */ }
registerKey(key, aliases) { /* ... */ }
isDown(key) { /* ... */ }
isUp(key) { /* ... */ }
destroy() { /* ... */ }
}
```
#### 3. **易于测试**
```javascript
class MockInputAdapter extends IInputAdapter {
constructor() {
super();
this.mockKeys = new Map();
}
initialize() {}
registerKey(key, aliases) {
this.mockKeys.set(key, false);
}
isDown(key) {
return this.mockKeys.get(key) || false;
}
isUp(key) {
return !this.isDown(key);
}
destroy() {}
// 测试辅助方法
setKeyDown(key) {
this.mockKeys.set(key, true);
}
setKeyUp(key) {
this.mockKeys.set(key, false);
}
}
// 单元测试示例
const mockAdapter = new MockInputAdapter();
const plugin = new InputPlugin(null, null);
plugin.inputAdapter = mockAdapter;
plugin.registerKey('W');
mockAdapter.setKeyDown('W');
assert(plugin.isDown('W') === true);
```
#### 4. **符合设计原则**
- **单一职责原则**:每个适配器只负责一个引擎的输入处理
- **开闭原则**:对扩展开放,对修改关闭
- **依赖倒置原则**:依赖抽象接口,不依赖具体实现
- **里氏替换原则**:所有适配器可以互相替换
## 使用示例
### Phaser 项目
```javascript
// main.js
import { InputPlugin } from './plugins/InputPlugin.js';
const config = {
scene: [GameScene],
plugins: {
scene: [
{
key: 'InputPlugin',
plugin: InputPlugin,
mapping: 'inputPlugin'
}
]
}
};
// GameScene.js
create() {
// 注册按键
this.inputPlugin.registerKey('W', ['ArrowUp']);
this.inputPlugin.registerKey('S', ['ArrowDown']);
this.inputPlugin.registerKey('A', ['ArrowLeft']);
this.inputPlugin.registerKey('D', ['ArrowRight']);
}
update() {
// 使用统一接口
if (this.inputPlugin.isDown('W')) {
this.player.setVelocityY(-200);
}
if (this.inputPlugin.isDown('S')) {
this.player.setVelocityY(200);
}
// ...
}
```
### PixiJS 项目
```javascript
// main.js
import { PixiInputPlugin } from './plugins/PixiInputPlugin.js';
const app = new PIXI.Application({ width: 800, height: 600 });
const inputPlugin = new PixiInputPlugin(app);
inputPlugin.registerKey('W', ['ArrowUp']);
inputPlugin.registerKey('S', ['ArrowDown']);
// 游戏循环
app.ticker.add(() => {
if (inputPlugin.isDown('W')) {
player.y -= 2;
}
if (inputPlugin.isDown('S')) {
player.y += 2;
}
});
```
## 总结
**适配器模式的优势:**
- ✅ 解耦:业务逻辑与具体引擎实现分离
- ✅ 复用:同一套业务逻辑可在不同引擎中使用
- ✅ 扩展:添加新引擎支持无需修改现有代码
- ✅ 测试:可以轻松创建 Mock 对象进行单元测试
- ✅ 维护:每个适配器独立维护,降低复杂度
**何时使用适配器模式:**
- 需要集成第三方库或框架
- 需要支持多种实现方式
- 需要在不修改现有代码的情况下扩展功能
- 需要提高代码的可测试性