# 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 对象进行单元测试 - ✅ 维护:每个适配器独立维护,降低复杂度 **何时使用适配器模式:** - 需要集成第三方库或框架 - 需要支持多种实现方式 - 需要在不修改现有代码的情况下扩展功能 - 需要提高代码的可测试性