mirror of
http://junepve.top:3000/chicrobot/CINC.git
synced 2026-10-29 20:51:29 +08:00
197 lines
5.7 KiB
Markdown
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 对象进行单元测试
|
|
- ✅ 维护:每个适配器独立维护,降低复杂度
|
|
|
|
**何时使用适配器模式:**
|
|
- 需要集成第三方库或框架
|
|
- 需要支持多种实现方式
|
|
- 需要在不修改现有代码的情况下扩展功能
|
|
- 需要提高代码的可测试性
|