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

5.7 KiB

InputPlugin 架构设计说明

问题分析

原始设计的问题

  1. 紧耦合:直接依赖 Phaser 的 Key 对象和事件系统
  2. 无法跨引擎:切换引擎需要重写整个插件
  3. 违反开闭原则:修改输入方式需要修改插件内部代码
  4. 测试困难:难以进行单元测试(依赖具体引擎)

解决方案:适配器模式

架构层次

┌─────────────────────────────────────┐
│     业务逻辑层 (GameScene)          │
│  - 不关心底层实现                    │
│  - 只调用统一接口                    │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│     InputPlugin (核心插件)           │
│  - 提供统一接口                      │
│  - 管理适配器生命周期                │
│  - registerKey(), isDown(), isUp()  │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│     IInputAdapter (接口定义)        │
│  - 定义适配器必须实现的方法          │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│     具体适配器实现                   │
│  - PhaserInputAdapter               │
│  - PixiInputAdapter                 │
│  - CocosInputAdapter                │
│  - GodotInputAdapter (可扩展)       │
└─────────────────────────────────────┘

核心优势

1. 跨引擎复用

// 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. 易于扩展

添加新引擎支持只需实现适配器接口:

class GodotInputAdapter extends IInputAdapter {
    initialize() { /* Godot 特定实现 */ }
    registerKey(key, aliases) { /* ... */ }
    isDown(key) { /* ... */ }
    isUp(key) { /* ... */ }
    destroy() { /* ... */ }
}

3. 易于测试

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 项目

// 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 项目

// 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 对象进行单元测试
  • ✅ 维护:每个适配器独立维护,降低复杂度

何时使用适配器模式:

  • 需要集成第三方库或框架
  • 需要支持多种实现方式
  • 需要在不修改现有代码的情况下扩展功能
  • 需要提高代码的可测试性