diff --git a/docs/InputPlugin架构设计.md b/docs/InputPlugin架构设计.md
new file mode 100644
index 0000000..47da689
--- /dev/null
+++ b/docs/InputPlugin架构设计.md
@@ -0,0 +1,196 @@
+# 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 对象进行单元测试
+- ✅ 维护:每个适配器独立维护,降低复杂度
+
+**何时使用适配器模式:**
+- 需要集成第三方库或框架
+- 需要支持多种实现方式
+- 需要在不修改现有代码的情况下扩展功能
+- 需要提高代码的可测试性
diff --git a/scenes/storeScenes/BreatheLife.js b/scenes/storeScenes/BreatheLife.js
index 30624c3..b94a2a3 100644
--- a/scenes/storeScenes/BreatheLife.js
+++ b/scenes/storeScenes/BreatheLife.js
@@ -5,17 +5,34 @@
* 当创建的角色超过3个时,点击已存在的角色会为其设置纹理
* 当设置纹理的角色超过3个时,自动切换到 MenuScene 场景
*/
-export default class BreatheLife extends Phaser.Scene {
+import WorldLand from './WorldLand.js'
+export default class BreatheLife extends WorldLand {
/**
* 构造函数
- * 初始化场景并设置场景键名为 'BreatheLife'
+ * 初始化场景并设置场景键名
+ * @param {string} sceneKey - 场景键名,默认值为 'BreatheLife'
*/
- constructor() {
- super('BreatheLife')
+ constructor(sceneKey = 'BreatheLife') {
+ super(sceneKey)
this.nextScene = 'MenuScene'
+
+ this.UIDepth = 100 // UI层级深度,确保在其他元素之上
+ this.maskDepth = 99 // 黑夜遮罩层级,低于UI层级
+ this.SpriteDepth = 98 // 角色层级,确保在地图层级之上
+ this.mapDepth = 97 // 地图层级,确保在UI层级之下
+
+
+ this.tileCount = 234 // 瓦片总数,从tile_0000到tile_0233
+ this.tileSize = 16 // 瓦片大小
+ this.mapWidth = 18; // 瓦片图行数
+ this.mapHeight = 13; // 瓦片图列数
}
preload() {
+ // 加载草地瓦片图
+ this.load.image(`grass`, `assets/PNG/Tiles/Tiles/tile_0024.png`)
+ this.load.image(`smallGrass`, `assets/PNG/Tiles/Tiles/tile_0044.png`)
+
// 加载角色动画和图片资源
this.load.image(`player0-0`, `assets/PNG/Players/Tiles/tile_${String(0).padStart(4, '0')}.png`)
this.load.image(`player1-0`, `assets/PNG/Players/Tiles/tile_${String(8).padStart(4, '0')}.png`)
@@ -47,6 +64,10 @@ export default class BreatheLife extends Phaser.Scene {
this.spriteAnimCount = 0
this.targetCount = 3 // 目标角色数量
+ this.createTilemap() // 创建瓦片地图
+ this.initFrameRateText() // 初始化帧率显示文本
+ this.initDayNight(this.toGameTime('2000-01-01 12:00:00')) // 初始化时间,白天黑夜状态
+
// 监听鼠标点击事件,点击后创建一个新的角色
this.input.on('pointerdown', () => {
// 获取点击位置的坐标
@@ -84,6 +105,7 @@ export default class BreatheLife extends Phaser.Scene {
}else if (this.spriteCount < 3) {
// 创建一个新的角色 sprite,缩放为1倍,并设置原点为中心底部
const sprite = this.physics.add.sprite(x, y, 'player').setScale(1).setOrigin(0.5, 1)
+ sprite.setDepth(this.SpriteDepth) // 设置角色层级
// 设置角色碰撞世界边界
sprite.setCollideWorldBounds(true)
diff --git a/scenes/storeScenes/WorldLand.js b/scenes/storeScenes/WorldLand.js
index eb625c0..03cdaa3 100644
--- a/scenes/storeScenes/WorldLand.js
+++ b/scenes/storeScenes/WorldLand.js
@@ -4,44 +4,71 @@
* 初始引导场景,显示空白背景并在用户交互或自动延迟后
* 执行过渡动画切换到下一个场景
*/
-import GameTimeManager from '../../systems/GameTimeManager.js'
+import WorldTime from './WorldTime.js'
-export default class WorldLand extends Phaser.Scene {
+export default class WorldLand extends WorldTime {
/**
* 构造函数
- * 初始化场景并设置场景键名为 'WorldLand'
+ * 初始化场景并设置场景键名
+ * @param {string} sceneKey - 场景键名
*/
- constructor() {
- super('WorldLand')
+ constructor(sceneKey = 'WorldLand') {
+ super(sceneKey)
this.nextScene = 'BreatheLife'
- this.timeManager = new GameTimeManager() // 创建游戏时间管理器实例
-
this.UIDepth = 100 // UI层级深度,确保在其他元素之上
this.maskDepth = 99 // 黑夜遮罩层级,低于UI层级
+ this.mapDepth = 98 // 地图层级,确保在UI层级之下
+ this.tileCount = 234 // 瓦片总数,从tile_0000到tile_0233
+ this.tileSize = 16 // 瓦片大小
+ this.mapWidth = 18; // 瓦片图行数
+ this.mapHeight = 13; // 瓦片图列数
+ }
+ preload() {
+ // 加载草地瓦片图
+ this.load.image(`grass`, `assets/PNG/Tiles/Tiles/tile_0024.png`)
+ this.load.image(`smallGrass`, `assets/PNG/Tiles/Tiles/tile_0044.png`)
}
create() {
// 设置场景背景颜色为深灰色
this.cameras.main.setBackgroundColor('#999999')
+ // 初始化计数器,用于跟踪创建的小草数量
+ this.smallGrassCount = 0
- // 监听鼠标点击事件,点击后执行场景过渡动画
- this.input.on('pointerdown', () => {
- this.scene.start(this.nextScene);
- })
+ this.initInput() // 初始化输入事件
+ this.initDayNight(this.toGameTime('2000-01-01 17:57:00')) // 初始化时间,白天黑夜状态
+
+ this.createTilemap() // 创建瓦片地图
- // 监听键盘按键事件,按键后执行场景过渡动画
- this.input.keyboard.on('keydown', () => {
- this.scene.start(this.nextScene);
- })
-
- this.initDayNight(this.toGameTime('1999-12-30 23:57:00')) // 初始化时间,白天黑夜状态
this.initFrameRateText() // 初始化帧率显示文本
}
- /**
- * 更新游戏时间
- * 每帧调用,刷新游戏时间
- */
+
+ createTilemap() {
+ // 获取当前屏幕大小
+ const screenWidth = this.cameras.main.width;
+ const screenHeight = this.cameras.main.height;
+
+ // 计算需要的瓦片数量
+ const tilesX = Math.ceil(screenWidth / this.tileSize);
+ const tilesY = Math.ceil(screenHeight / this.tileSize);
+
+ // 草地铺满屏幕
+ for (let y = 0; y < tilesY; y++) {
+ for (let x = 0; x < tilesX; x++) {
+ // 使用草地瓦片
+ const tile = this.add.image(
+ x * this.tileSize + this.tileSize / 2,
+ y * this.tileSize + this.tileSize / 2,
+ 'grass'
+ );
+ tile.setDepth(this.mapDepth); // 设置深度,确保在UI层级之下
+ }
+ }
+
+ console.log(`草地铺满屏幕完成,使用了 ${tilesX}x${tilesY} 个瓦片`)
+ }
+
update() {
this.showFrameRate() // 显示当前帧率
this.updateGameTime() // 更新游戏时间
@@ -49,7 +76,31 @@ export default class WorldLand extends Phaser.Scene {
this.updateDayNight() // 更新昼夜状态
}
+ initInput() {
+ // 监听鼠标点击事件,点击后创建一个新的小草
+ this.input.on('pointerdown', () => {
+ // 获取点击位置的坐标
+ const { x, y } = this.input.activePointer
+ this.createTile(x, y)
+ })
+
+ // 监听键盘按键事件,按键后执行场景过渡动画
+ this.input.keyboard.on('keydown', () => {
+ this.scene.start(this.nextScene);
+ })
+ }
+ createTile(x, y) {
+ if (this.smallGrassCount >= 10) {
+ this.scene.start(this.nextScene);
+ }
+ // 创建瓦片图实例
+ const tile = this.add.image(x, y, 'smallGrass');
+ tile.setDepth(this.mapDepth); // 设置深度,确保在UI层级之下
+ tile.setScrollFactor(0); // 固定瓦片,不随相机移动
+ // 增加小草计数器
+ this.smallGrassCount++;
+ }
initFrameRateText() {
// 创建帧率显示文本
this.frameRateText = this.add.text(
@@ -61,74 +112,7 @@ export default class WorldLand extends Phaser.Scene {
).setScrollFactor(0).setDepth(this.UIDepth) // 固定文本,不随相机移动,UI层级
}
showFrameRate() {
- // 获取当前帧率
- const frameRate = this.game.loop.actualFps
- // 更新帧率显示文本
- this.frameRateText.setText(`Frame Rate: ${frameRate.toFixed(2)}`)
- }
- /**
- * 初始化昼夜状态
- * 设置初始为白天,添加事件监听切换状态
- */
- initDayNight(gameTime) {
- this.timeManager.init(gameTime) // 初始化时间管理器
- // 创建时间显示文本
- this.timeText = this.add.text(
- 20, 20, '', {
- fontSize: '20px',
- fill: '#ffffff',
- fontFamily: 'Arial'
- }
- ).setScrollFactor(0).setDepth(this.UIDepth) // 固定文本,不随相机移动,UI层级
- // 添加黑夜遮罩
- this.nightMask = this.add.rectangle(
- 0, 0,
- this.cameras.main.width,
- this.cameras.main.height,
- 0x000000
- ).setOrigin(0, 0).setScrollFactor(0).setDepth(this.maskDepth) // 固定遮罩,不随相机移动,遮罩层级
- this.nightMask.alpha = 0 // 初始设置为透明
- }
- /**
- * 更新游戏时间
- * 每帧调用,基于现实时间流逝更新游戏时间
- */
- updateGameTime() {
- this.timeManager.updateGameTime() // 使用时间管理器更新游戏时间
- }
- /**
- * 获取游戏中的日期
- * 根据游戏时间戳计算游戏年月日时分秒,格式为YYYY-MM-DD HH:mm:ss
- */
- getGameDate() {
- return this.timeManager.getGameDate() // 使用时间管理器获取游戏日期
- }
- /**
- * 转换游戏时间字符串为游戏时间戳
- * 接收时间字符串,格式为YYYY-MM-DD HH:mm:ss,返回游戏时间戳(单位:秒)
- */
- toGameTime(gameTimeString) {
- return this.timeManager.toGameTime(gameTimeString) // 使用时间管理器转换游戏时间
- }
- /**
- * 显示当前时间
- * 每帧调用,更新显示时间
- */
- displayTime() {
- const timeString = this.getGameDate().dateString
- const day = this.timeManager.getDayNightState() ? '白天' : '黑夜'
- // 更新显示时间
- this.timeText.setText(`${day} ${timeString}`)
- }
- /**
- * 更新昼夜状态
- * 每帧调用,根据游戏时间切换白天黑夜状态
- */
- updateDayNight() {
- const isDay = this.timeManager.updateDayNight()
- // 根据昼夜状态更新遮罩
- if (this.nightMask) {
- this.nightMask.alpha = isDay ? 0 : 0.5
- }
+ const frameRate = this.game.loop.actualFps // 获取当前帧率
+ this.frameRateText.setText(`Frame Rate: ${frameRate.toFixed(2)}`) // 更新帧率显示文本
}
}
diff --git a/scenes/storeScenes/WorldTime.js b/scenes/storeScenes/WorldTime.js
index 7b53eaf..9b8978f 100644
--- a/scenes/storeScenes/WorldTime.js
+++ b/scenes/storeScenes/WorldTime.js
@@ -7,10 +7,11 @@
export default class WorldTime extends Phaser.Scene {
/**
* 构造函数
- * 初始化场景并设置场景键名为 'WorldTime'
+ * 初始化场景并设置场景键名
+ * @param {string} sceneKey - 场景键名
*/
- constructor() {
- super('WorldTime')
+ constructor(sceneKey = 'WorldTime') {
+ super(sceneKey)
this.nextScene = 'WorldLand'
this.gameTime = 0 // 游戏中的时间戳(单位:秒)
@@ -34,6 +35,15 @@ export default class WorldTime extends Phaser.Scene {
// 设置场景背景颜色为深灰色
this.cameras.main.setBackgroundColor('#999999')
+ this.initInput() // 初始化输入事件
+
+ this.initDayNight(this.toGameTime('1999-12-30 23:57:00')) // 初始化时间,白天黑夜状态
+ }
+ /**
+ * 初始化输入事件
+ * 监听鼠标点击和键盘按键,触发场景过渡
+ */
+ initInput() {
// 监听鼠标点击事件,点击后执行场景过渡动画
this.input.on('pointerdown', () => {
this.scene.start(this.nextScene);
@@ -43,8 +53,6 @@ export default class WorldTime extends Phaser.Scene {
this.input.keyboard.on('keydown', () => {
this.scene.start(this.nextScene);
})
-
- this.initDayNight(this.toGameTime('2000-01-01 17:57:00')) // 初始化时间,白天黑夜状态
}
/**
* 更新游戏时间
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..ec56a85
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+ Phaser
+
+
+
+
+
+
+
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..038edf2
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,46 @@
+// main.js
+
+import { GameScene } from './scenes/GameScene.js';
+import { JoystickTestScene } from './scenes/JoystickTestScene.js';
+import { InputPlugin } from './plugins/InputPlugin.js';
+
+console.log('InputPlugin 导入:', InputPlugin);
+
+const config = {
+ type: Phaser.AUTO,
+ width: 800,
+ height: 600,
+ backgroundColor: '#000000',
+ physics: {
+ default: 'arcade',
+ arcade: {
+ debug: false
+ }
+ },
+ scene: [JoystickTestScene, GameScene],
+ plugins: {
+ scene: [
+ {
+ key: 'InputPlugin',
+ plugin: InputPlugin,
+ mapping: 'inputPlugin'
+ }
+ ]
+ },
+ // 添加缩放配置,解决拉伸问题
+ scale: {
+ mode: Phaser.Scale.FIT,
+ autoCenter: Phaser.Scale.CENTER_BOTH,
+ min: {
+ width: 400,
+ height: 300
+ },
+ max: {
+ width: 1600,
+ height: 1200
+ }
+ }
+};
+
+console.log('游戏配置:', config);
+window.game = new Phaser.Game(config);
diff --git a/src/plugins/InputPlugin.js b/src/plugins/InputPlugin.js
new file mode 100644
index 0000000..a0f1999
--- /dev/null
+++ b/src/plugins/InputPlugin.js
@@ -0,0 +1,113 @@
+// plugins/InputPlugin.js
+
+import { InputSystem } from './core/InputSystem.js';
+import { PhaserInputAdapter } from './adapters/PhaserInputAdapter.js';
+
+/**
+ * InputPlugin 场景插件
+ * 作为 Phaser 场景插件的包装器,使用适配器架构
+ */
+export class InputPlugin extends Phaser.Plugins.ScenePlugin {
+ constructor(scene, pluginManager) {
+ super(scene, pluginManager);
+ console.log('InputPlugin 场景插件初始化');
+
+ // 创建 Phaser 适配器
+ this.adapter = new PhaserInputAdapter(scene);
+
+ // 创建核心输入系统
+ this.inputSystem = new InputSystem(this.adapter);
+
+ // 初始化
+ this.inputSystem.initialize();
+ }
+
+ /**
+ * 启动插件
+ */
+ boot() {
+ console.log('InputPlugin 场景插件启动');
+ }
+
+ /**
+ * 测试方法
+ * @returns {string} 测试结果
+ */
+ testMethod() {
+ return 'InputPlugin 测试方法';
+ }
+
+ /**
+ * 创建触摸轮盘
+ * @param {Object} config 轮盘配置
+ * @returns {string} 轮盘ID
+ */
+ createJoystick(config) {
+ return this.inputSystem.createJoystick(config);
+ }
+
+ /**
+ * 创建屏幕按钮
+ * @param {Object} config 按钮配置
+ * @returns {string} 按钮ID
+ */
+ createButton(config) {
+ return this.inputSystem.createButton(config);
+ }
+
+ /**
+ * 获取轮盘方向向量
+ * @param {string} joystickId 轮盘ID
+ * @returns {Object} 方向向量 {x, y}
+ */
+ getJoystickDirection(joystickId) {
+ return this.inputSystem.getJoystickDirection(joystickId);
+ }
+
+ /**
+ * 获取轮盘力度
+ * @param {string} joystickId 轮盘ID
+ * @returns {number} 力度值 (0-1)
+ */
+ getJoystickMagnitude(joystickId) {
+ return this.inputSystem.getJoystickMagnitude(joystickId);
+ }
+
+ /**
+ * 获取归一化的轮盘方向
+ * @param {string} joystickId 轮盘ID
+ * @returns {Object} 归一化向量 {x, y} (-1 到 1)
+ */
+ getJoystickNormalized(joystickId) {
+ return this.inputSystem.getJoystickNormalized(joystickId);
+ }
+
+ /**
+ * 检查按钮是否按下
+ * @param {string} buttonId 按钮ID
+ * @returns {boolean} 是否按下
+ */
+ isButtonDown(buttonId) {
+ return this.inputSystem.isButtonDown(buttonId);
+ }
+
+ /**
+ * 检查按钮是否释放
+ * @param {string} buttonId 按钮ID
+ * @returns {boolean} 是否释放
+ */
+ isButtonUp(buttonId) {
+ return this.inputSystem.isButtonUp(buttonId);
+ }
+
+ /**
+ * 销毁插件
+ */
+ destroy() {
+ console.log('InputPlugin 场景插件销毁');
+ if (this.inputSystem) {
+ this.inputSystem.destroy();
+ }
+ super.destroy();
+ }
+}
diff --git a/src/plugins/adapters/CocosInputAdapter.js b/src/plugins/adapters/CocosInputAdapter.js
new file mode 100644
index 0000000..c31f2ed
--- /dev/null
+++ b/src/plugins/adapters/CocosInputAdapter.js
@@ -0,0 +1,74 @@
+// plugins/adapters/CocosInputAdapter.js
+
+export class CocosInputAdapter {
+ constructor(director) {
+ this.director = director;
+ this.keyMap = new Map();
+ this.isInitialized = false;
+ }
+
+ initialize() {
+ if (this.isInitialized) return;
+
+ cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, (event) => {
+ this.handleKeyDown(event);
+ });
+
+ cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, (event) => {
+ this.handleKeyUp(event);
+ });
+
+ this.isInitialized = true;
+ console.log('CocosInputAdapter 已初始化');
+ }
+
+ handleKeyDown(event) {
+ const keyCode = event.keyCode;
+ this.keyState.set(keyCode, true);
+ }
+
+ handleKeyUp(event) {
+ const keyCode = event.keyCode;
+ this.keyState.set(keyCode, false);
+ }
+
+ registerKey(key, aliases = []) {
+ const keyCode = this.getKeyCode(key);
+ this.keyMap.set(key, keyCode);
+
+ aliases.forEach(alias => {
+ const aliasKeyCode = this.getKeyCode(alias);
+ this.keyMap.set(alias, keyCode);
+ });
+ }
+
+ getKeyCode(key) {
+ const keyMap = {
+ 'W': cc.macro.KEY.w,
+ 'A': cc.macro.KEY.a,
+ 'S': cc.macro.KEY.s,
+ 'D': cc.macro.KEY.d,
+ 'UP': cc.macro.KEY.up,
+ 'DOWN': cc.macro.KEY.down,
+ 'LEFT': cc.macro.KEY.left,
+ 'RIGHT': cc.macro.KEY.right
+ };
+ return keyMap[key] || key;
+ }
+
+ isDown(key) {
+ const keyCode = this.keyMap.get(key);
+ return this.keyState.get(keyCode) || false;
+ }
+
+ isUp(key) {
+ return !this.isDown(key);
+ }
+
+ destroy() {
+ cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN);
+ cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP);
+ this.keyMap.clear();
+ this.keyState.clear();
+ }
+}
diff --git a/src/plugins/adapters/IInputAdapter.js b/src/plugins/adapters/IInputAdapter.js
new file mode 100644
index 0000000..8806bed
--- /dev/null
+++ b/src/plugins/adapters/IInputAdapter.js
@@ -0,0 +1,90 @@
+// plugins/adapters/IInputAdapter.js
+
+/**
+ * 输入适配器接口
+ * 所有引擎适配器必须实现这些方法
+ */
+export class IInputAdapter {
+ constructor() {
+ if (this.constructor === IInputAdapter) {
+ throw new Error('IInputAdapter 是抽象类,不能直接实例化');
+ }
+ }
+
+ /**
+ * 初始化适配器
+ */
+ initialize() {
+ throw new Error('initialize() 必须由子类实现');
+ }
+
+ /**
+ * 创建触摸轮盘
+ * @param {Object} config 轮盘配置
+ * @returns {Object} 轮盘实例
+ */
+ createJoystick(config) {
+ throw new Error('createJoystick() 必须由子类实现');
+ }
+
+ /**
+ * 创建屏幕按钮
+ * @param {Object} config 按钮配置
+ * @returns {Object} 按钮实例
+ */
+ createButton(config) {
+ throw new Error('createButton() 必须由子类实现');
+ }
+
+ /**
+ * 获取轮盘方向向量
+ * @param {string} joystickId 轮盘ID
+ * @returns {Object} 方向向量 {x, y}
+ */
+ getJoystickDirection(joystickId) {
+ throw new Error('getJoystickDirection() 必须由子类实现');
+ }
+
+ /**
+ * 获取轮盘力度
+ * @param {string} joystickId 轮盘ID
+ * @returns {number} 力度值 (0-1)
+ */
+ getJoystickMagnitude(joystickId) {
+ throw new Error('getJoystickMagnitude() 必须由子类实现');
+ }
+
+ /**
+ * 获取归一化的轮盘方向
+ * @param {string} joystickId 轮盘ID
+ * @returns {Object} 归一化向量 {x, y} (-1 到 1)
+ */
+ getJoystickNormalized(joystickId) {
+ throw new Error('getJoystickNormalized() 必须由子类实现');
+ }
+
+ /**
+ * 检查按钮是否按下
+ * @param {string} buttonId 按钮ID
+ * @returns {boolean} 是否按下
+ */
+ isButtonDown(buttonId) {
+ throw new Error('isButtonDown() 必须由子类实现');
+ }
+
+ /**
+ * 检查按钮是否释放
+ * @param {string} buttonId 按钮ID
+ * @returns {boolean} 是否释放
+ */
+ isButtonUp(buttonId) {
+ throw new Error('isButtonUp() 必须由子类实现');
+ }
+
+ /**
+ * 销毁适配器
+ */
+ destroy() {
+ throw new Error('destroy() 必须由子类实现');
+ }
+}
diff --git a/src/plugins/adapters/PhaserInputAdapter.js b/src/plugins/adapters/PhaserInputAdapter.js
new file mode 100644
index 0000000..6940f87
--- /dev/null
+++ b/src/plugins/adapters/PhaserInputAdapter.js
@@ -0,0 +1,288 @@
+// plugins/adapters/PhaserInputAdapter.js
+
+import { IInputAdapter } from './IInputAdapter.js';
+
+/**
+ * Phaser 输入适配器
+ * 实现 IInputAdapter 接口,使用 Phaser API
+ */
+export class PhaserInputAdapter extends IInputAdapter {
+ constructor(scene) {
+ super();
+ this.scene = scene;
+ this.joysticks = new Map();
+ this.buttons = new Map();
+ console.log('PhaserInputAdapter 初始化');
+ }
+
+ /**
+ * 初始化适配器
+ */
+ initialize() {
+ if (!this.scene.input) {
+ console.warn('Phaser 输入系统不可用');
+ }
+ console.log('PhaserInputAdapter 已初始化');
+ }
+
+ /**
+ * 创建触摸轮盘
+ * @param {Object} config 轮盘配置
+ * @returns {Object} 轮盘实例
+ */
+ createJoystick(config) {
+ try {
+ const joystick = {
+ id: config.id,
+ base: null,
+ thumb: null,
+ isActive: false,
+ startX: config.x || 100,
+ startY: config.y || this.scene.sys.game.config.height - 100,
+ radius: config.radius || 60,
+ maxDistance: config.maxDistance || 40,
+ currentX: 0,
+ currentY: 0,
+ normalizedX: 0,
+ normalizedY: 0,
+ magnitude: 0
+ };
+
+ // 创建轮盘底座
+ joystick.base = this.scene.add.circle(joystick.startX, joystick.startY, joystick.radius, 0x444444, 0.5);
+ joystick.base.setScrollFactor(0);
+
+ // 创建轮盘拇指
+ joystick.thumb = this.scene.add.circle(joystick.startX, joystick.startY, joystick.radius * 0.5, 0x888888, 0.8);
+ joystick.thumb.setScrollFactor(0);
+
+ // 监听触摸事件
+ const self = this;
+ this.scene.input.on('pointerdown', function(pointer) {
+ if (self.calculateDistance(pointer.x, pointer.y, joystick.startX, joystick.startY) < joystick.radius) {
+ joystick.isActive = true;
+ joystick.thumb.setPosition(pointer.x, pointer.y);
+ self.updateJoystick(joystick, pointer);
+ }
+ });
+
+ this.scene.input.on('pointermove', function(pointer) {
+ if (joystick.isActive) {
+ self.updateJoystick(joystick, pointer);
+ }
+ });
+
+ this.scene.input.on('pointerup', function() {
+ if (joystick.isActive) {
+ joystick.isActive = false;
+ joystick.thumb.setPosition(joystick.startX, joystick.startY);
+ joystick.currentX = 0;
+ joystick.currentY = 0;
+ joystick.normalizedX = 0;
+ joystick.normalizedY = 0;
+ joystick.magnitude = 0;
+ }
+ });
+
+ this.joysticks.set(config.id, joystick);
+ return joystick;
+ } catch (error) {
+ console.error('创建轮盘失败:', error);
+ return null;
+ }
+ }
+
+ /**
+ * 创建屏幕按钮
+ * @param {Object} config 按钮配置
+ * @returns {Object} 按钮实例
+ */
+ createButton(config) {
+ try {
+ const button = {
+ id: config.id,
+ sprite: null,
+ text: null,
+ isDown: false,
+ x: config.x || this.scene.sys.game.config.width - 100,
+ y: config.y || this.scene.sys.game.config.height - 100,
+ width: config.width || 80,
+ height: config.height || 80,
+ label: config.label || 'Button',
+ onDown: config.onDown || null,
+ onUp: config.onUp || null
+ };
+
+ // 创建按钮背景
+ button.sprite = this.scene.add.rectangle(button.x, button.y, button.width, button.height, 0x444444, 0.5);
+ button.sprite.setScrollFactor(0);
+ button.sprite.setInteractive();
+
+ // 创建按钮文字
+ button.text = this.scene.add.text(button.x, button.y, button.label, {
+ fill: '#ffffff',
+ fontSize: '16px',
+ fontFamily: 'Arial'
+ });
+ button.text.setScrollFactor(0);
+ button.text.setOrigin(0.5, 0.5);
+
+ // 监听触摸事件
+ button.sprite.on('pointerdown', function() {
+ button.isDown = true;
+ button.sprite.setFillStyle(0x666666, 0.8);
+ if (button.onDown) {
+ button.onDown();
+ }
+ });
+
+ button.sprite.on('pointerup', function() {
+ button.isDown = false;
+ button.sprite.setFillStyle(0x444444, 0.5);
+ if (button.onUp) {
+ button.onUp();
+ }
+ });
+
+ button.sprite.on('pointerout', function() {
+ if (button.isDown) {
+ button.isDown = false;
+ button.sprite.setFillStyle(0x444444, 0.5);
+ }
+ });
+
+ this.buttons.set(config.id, button);
+ return button;
+ } catch (error) {
+ console.error('创建按钮失败:', error);
+ return null;
+ }
+ }
+
+ /**
+ * 计算两点之间的距离
+ * @param {number} x1
+ * @param {number} y1
+ * @param {number} x2
+ * @param {number} y2
+ * @returns {number} 距离
+ */
+ calculateDistance(x1, y1, x2, y2) {
+ const dx = x2 - x1;
+ const dy = y2 - y1;
+ return Math.sqrt(dx * dx + dy * dy);
+ }
+
+ /**
+ * 计算两点之间的角度
+ * @param {number} x1
+ * @param {number} y1
+ * @param {number} x2
+ * @param {number} y2
+ * @returns {number} 角度
+ */
+ calculateAngle(x1, y1, x2, y2) {
+ return Math.atan2(y2 - y1, x2 - x1);
+ }
+
+ /**
+ * 更新轮盘状态
+ * @param {Object} joystick 轮盘实例
+ * @param {Object} pointer 指针事件
+ */
+ updateJoystick(joystick, pointer) {
+ const distance = this.calculateDistance(joystick.startX, joystick.startY, pointer.x, pointer.y);
+ const angle = this.calculateAngle(joystick.startX, joystick.startY, pointer.x, pointer.y);
+
+ // 限制最大距离
+ const limitedDistance = Math.min(distance, joystick.maxDistance);
+ joystick.magnitude = limitedDistance / joystick.maxDistance;
+
+ // 计算位置
+ joystick.currentX = Math.cos(angle) * limitedDistance;
+ joystick.currentY = Math.sin(angle) * limitedDistance;
+
+ // 归一化(-1 到 1 之间)
+ joystick.normalizedX = joystick.currentX / joystick.maxDistance;
+ joystick.normalizedY = joystick.currentY / joystick.maxDistance;
+
+ // 更新拇指位置
+ joystick.thumb.setPosition(
+ joystick.startX + joystick.currentX,
+ joystick.startY + joystick.currentY
+ );
+ }
+
+ /**
+ * 获取轮盘方向向量
+ * @param {string} joystickId 轮盘ID
+ * @returns {Object} 方向向量 {x, y}
+ */
+ getJoystickDirection(joystickId) {
+ const joystick = this.joysticks.get(joystickId);
+ if (!joystick) return { x: 0, y: 0 };
+ return { x: joystick.currentX, y: joystick.currentY };
+ }
+
+ /**
+ * 获取轮盘力度
+ * @param {string} joystickId 轮盘ID
+ * @returns {number} 力度值 (0-1)
+ */
+ getJoystickMagnitude(joystickId) {
+ const joystick = this.joysticks.get(joystickId);
+ if (!joystick) return 0;
+ return joystick.magnitude;
+ }
+
+ /**
+ * 获取归一化的轮盘方向
+ * @param {string} joystickId 轮盘ID
+ * @returns {Object} 归一化向量 {x, y} (-1 到 1)
+ */
+ getJoystickNormalized(joystickId) {
+ const joystick = this.joysticks.get(joystickId);
+ if (!joystick) return { x: 0, y: 0 };
+ return { x: joystick.normalizedX, y: joystick.normalizedY };
+ }
+
+ /**
+ * 检查按钮是否按下
+ * @param {string} buttonId 按钮ID
+ * @returns {boolean} 是否按下
+ */
+ isButtonDown(buttonId) {
+ const button = this.buttons.get(buttonId);
+ return button ? button.isDown : false;
+ }
+
+ /**
+ * 检查按钮是否释放
+ * @param {string} buttonId 按钮ID
+ * @returns {boolean} 是否释放
+ */
+ isButtonUp(buttonId) {
+ return !this.isButtonDown(buttonId);
+ }
+
+ /**
+ * 销毁适配器
+ */
+ destroy() {
+ // 清理轮盘
+ this.joysticks.forEach(joystick => {
+ if (joystick.base) joystick.base.destroy();
+ if (joystick.thumb) joystick.thumb.destroy();
+ });
+ this.joysticks.clear();
+
+ // 清理按钮
+ this.buttons.forEach(button => {
+ if (button.sprite) button.sprite.destroy();
+ if (button.text) button.text.destroy();
+ });
+ this.buttons.clear();
+
+ console.log('PhaserInputAdapter 已销毁');
+ }
+}
diff --git a/src/plugins/core/InputSystem.js b/src/plugins/core/InputSystem.js
new file mode 100644
index 0000000..1276388
--- /dev/null
+++ b/src/plugins/core/InputSystem.js
@@ -0,0 +1,116 @@
+// plugins/core/InputSystem.js
+
+/**
+ * 核心输入系统 - 引擎无关
+ * 使用适配器模式实现跨引擎兼容
+ */
+export class InputSystem {
+ constructor(adapter) {
+ if (!adapter) {
+ throw new Error('InputSystem 需要一个适配器实例');
+ }
+ this.adapter = adapter;
+ this.joysticks = new Map();
+ this.buttons = new Map();
+ console.log('InputSystem 初始化');
+ }
+
+ /**
+ * 初始化输入系统
+ */
+ initialize() {
+ this.adapter.initialize();
+ console.log('InputSystem 已初始化');
+ }
+
+ /**
+ * 创建触摸轮盘
+ * @param {Object} config 轮盘配置
+ * @returns {string} 轮盘ID
+ */
+ createJoystick(config) {
+ const joystickId = config.id || `joystick_${this.joysticks.size}`;
+ const createdJoystick = this.adapter.createJoystick({
+ ...config,
+ id: joystickId
+ });
+ if (createdJoystick) {
+ this.joysticks.set(joystickId, createdJoystick);
+ console.log('创建轮盘:', joystickId);
+ }
+ return joystickId;
+ }
+
+ /**
+ * 创建屏幕按钮
+ * @param {Object} config 按钮配置
+ * @returns {string} 按钮ID
+ */
+ createButton(config) {
+ const buttonId = config.id || `button_${this.buttons.size}`;
+ const createdButton = this.adapter.createButton({
+ ...config,
+ id: buttonId
+ });
+ if (createdButton) {
+ this.buttons.set(buttonId, createdButton);
+ console.log('创建按钮:', buttonId);
+ }
+ return buttonId;
+ }
+
+ /**
+ * 获取轮盘方向向量
+ * @param {string} joystickId 轮盘ID
+ * @returns {Object} 方向向量 {x, y}
+ */
+ getJoystickDirection(joystickId) {
+ return this.adapter.getJoystickDirection(joystickId);
+ }
+
+ /**
+ * 获取轮盘力度
+ * @param {string} joystickId 轮盘ID
+ * @returns {number} 力度值 (0-1)
+ */
+ getJoystickMagnitude(joystickId) {
+ return this.adapter.getJoystickMagnitude(joystickId);
+ }
+
+ /**
+ * 获取归一化的轮盘方向
+ * @param {string} joystickId 轮盘ID
+ * @returns {Object} 归一化向量 {x, y} (-1 到 1)
+ */
+ getJoystickNormalized(joystickId) {
+ return this.adapter.getJoystickNormalized(joystickId);
+ }
+
+ /**
+ * 检查按钮是否按下
+ * @param {string} buttonId 按钮ID
+ * @returns {boolean} 是否按下
+ */
+ isButtonDown(buttonId) {
+ return this.adapter.isButtonDown(buttonId);
+ }
+
+ /**
+ * 检查按钮是否释放
+ * @param {string} buttonId 按钮ID
+ * @returns {boolean} 是否释放
+ */
+ isButtonUp(buttonId) {
+ return this.adapter.isButtonUp(buttonId);
+ }
+
+ /**
+ * 销毁输入系统
+ */
+ destroy() {
+ this.adapter.destroy();
+ this.joysticks.clear();
+ this.buttons.clear();
+ console.log('InputSystem 已销毁');
+ }
+}
diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js
new file mode 100644
index 0000000..e8473a3
--- /dev/null
+++ b/src/scenes/GameScene.js
@@ -0,0 +1,74 @@
+// scenes/GameScene.js
+
+export class GameScene extends Phaser.Scene {
+ constructor() {
+ super('GameScene');
+ this.moveSpeed = 200;
+ this.joystickId = null;
+ this.inputPlugin = null;
+ }
+
+ preload() {
+ }
+
+ create() {
+ const graphics = this.make.graphics();
+ graphics.fillStyle(0xffffff, 1);
+ graphics.fillRect(0, 0, 32, 32);
+ graphics.generateTexture('player', 32, 32);
+ graphics.destroy();
+
+ this.player = this.physics.add.sprite(400, 300, 'player');
+ this.player.setCollideWorldBounds(true);
+
+ // 获取插件实例(更可靠的方式)
+ this.inputPlugin = this.plugins.get('InputPlugin');
+
+ if (this.inputPlugin) {
+ // 添加触摸轮盘
+ this.joystickId = this.inputPlugin.createJoystick({
+ id: 'movement',
+ x: 100,
+ y: this.sys.game.config.height - 100,
+ radius: 60,
+ maxDistance: 40
+ });
+ } else {
+ console.error('InputPlugin 未找到!');
+ }
+
+ // 添加提示文字
+ this.add.text(10, 10, '使用 WASD/方向键 或 触摸轮盘移动', { fill: '#ffffff', fontSize: '16px' });
+ }
+
+ update() {
+ // 重置速度
+ this.player.setVelocity(0);
+
+ // 键盘输入
+ const cursors = this.input.keyboard.createCursorKeys();
+ const keys = this.input.keyboard.addKeys('W,A,S,D');
+
+ if (cursors.up.isDown || keys.W.isDown) {
+ this.player.setVelocityY(-this.moveSpeed);
+ }
+ if (cursors.down.isDown || keys.S.isDown) {
+ this.player.setVelocityY(this.moveSpeed);
+ }
+ if (cursors.left.isDown || keys.A.isDown) {
+ this.player.setVelocityX(-this.moveSpeed);
+ }
+ if (cursors.right.isDown || keys.D.isDown) {
+ this.player.setVelocityX(this.moveSpeed);
+ }
+
+ // 触摸轮盘输入(带归一化)
+ if (this.inputPlugin && this.joystickId) {
+ const joystickNormalized = this.inputPlugin.getJoystickNormalized(this.joystickId);
+ if (joystickNormalized.x !== 0 || joystickNormalized.y !== 0) {
+ this.player.setVelocityX(joystickNormalized.x * this.moveSpeed);
+ this.player.setVelocityY(joystickNormalized.y * this.moveSpeed);
+ }
+ }
+ }
+}
diff --git a/src/scenes/JoystickTestScene.js b/src/scenes/JoystickTestScene.js
new file mode 100644
index 0000000..57742f5
--- /dev/null
+++ b/src/scenes/JoystickTestScene.js
@@ -0,0 +1,215 @@
+// scenes/JoystickTestScene.js
+
+import { InputPlugin } from '../plugins/InputPlugin.js';
+
+export class JoystickTestScene extends Phaser.Scene {
+ constructor() {
+ super('JoystickTestScene');
+ this.moveSpeed = 200;
+ this.sprintSpeed = 400; // 冲刺速度
+ this.inputPlugin = null;
+ this.joystickId = null;
+ this.sprintButtonId = null;
+ this.isSprinting = false;
+ }
+
+ preload() {
+ }
+
+ create() {
+ // 创建背景
+ this.cameras.main.setBackgroundColor('#2c3e50');
+
+ // 创建玩家
+ const graphics = this.make.graphics();
+ graphics.fillStyle(0x3498db, 1);
+ graphics.fillRect(0, 0, 40, 40);
+ graphics.generateTexture('player', 40, 40);
+ graphics.destroy();
+
+ this.player = this.physics.add.sprite(400, 300, 'player');
+ this.player.setCollideWorldBounds(true);
+
+ // 直接创建 InputPlugin 实例(绕过插件系统)
+ try {
+ this.inputPlugin = new InputPlugin(this, this.plugins);
+ console.log('直接创建 InputPlugin:', this.inputPlugin);
+
+ // 手动启动插件
+ this.inputPlugin.boot();
+
+ // 测试插件方法
+ const testResult = this.inputPlugin.testMethod();
+ console.log('插件测试结果:', testResult);
+
+ // 添加移动轮盘
+ this.joystickId = this.inputPlugin.createJoystick({
+ id: 'movement',
+ x: 150,
+ y: this.sys.game.config.height - 150,
+ radius: 80,
+ maxDistance: 60
+ });
+ console.log('创建轮盘:', this.joystickId);
+
+ // 添加冲刺按钮
+ this.sprintButtonId = this.inputPlugin.createButton({
+ id: 'sprint',
+ x: this.sys.game.config.width - 150,
+ y: this.sys.game.config.height - 150,
+ width: 100,
+ height: 100,
+ label: '冲刺',
+ onDown: () => {
+ this.isSprinting = true;
+ console.log('开始冲刺');
+ },
+ onUp: () => {
+ this.isSprinting = false;
+ console.log('结束冲刺');
+ }
+ });
+ console.log('创建冲刺按钮:', this.sprintButtonId);
+
+ // 添加提示文字
+ this.add.text(10, 10, '触摸轮盘测试场景', {
+ fill: '#ffffff',
+ fontSize: '24px',
+ fontFamily: 'Arial'
+ });
+
+ this.add.text(10, 50, '使用左侧轮盘移动', {
+ fill: '#ffffff',
+ fontSize: '16px',
+ fontFamily: 'Arial'
+ });
+
+ this.add.text(10, 80, '使用右侧按钮冲刺', {
+ fill: '#ffffff',
+ fontSize: '16px',
+ fontFamily: 'Arial'
+ });
+
+ this.add.text(10, 110, '使用 WASD 或方向键也可以移动', {
+ fill: '#ffffff',
+ fontSize: '16px',
+ fontFamily: 'Arial'
+ });
+
+ // 显示轮盘状态
+ this.statusText = this.add.text(10, 150, '轮盘状态: 未激活', {
+ fill: '#ffffff',
+ fontSize: '14px',
+ fontFamily: 'Arial'
+ });
+
+ // 添加适配器架构信息
+ this.add.text(10, 300, '适配器架构信息:', {
+ fill: '#ffffff',
+ fontSize: '16px',
+ fontFamily: 'Arial'
+ });
+
+ this.add.text(10, 330, '- 核心: InputSystem (引擎无关)', {
+ fill: '#ffffff',
+ fontSize: '14px',
+ fontFamily: 'Arial'
+ });
+
+ this.add.text(10, 360, '- 适配器: PhaserInputAdapter', {
+ fill: '#ffffff',
+ fontSize: '14px',
+ fontFamily: 'Arial'
+ });
+
+ this.add.text(10, 390, '- 插件: InputPlugin (场景插件)', {
+ fill: '#ffffff',
+ fontSize: '14px',
+ fontFamily: 'Arial'
+ });
+
+ } catch (error) {
+ console.error('创建 InputPlugin 失败:', error);
+ this.add.text(10, 10, 'InputPlugin 初始化失败!', {
+ fill: '#ff0000',
+ fontSize: '24px'
+ });
+ this.add.text(10, 50, '错误: ' + error.message, {
+ fill: '#ffffff',
+ fontSize: '16px'
+ });
+ }
+ }
+
+ update() {
+ // 重置速度
+ this.player.setVelocity(0);
+
+ // 确定当前速度(根据是否冲刺)
+ const currentSpeed = this.isSprinting ? this.sprintSpeed : this.moveSpeed;
+
+ // 键盘输入
+ const cursors = this.input.keyboard.createCursorKeys();
+ const keys = this.input.keyboard.addKeys('W,A,S,D,SHIFT');
+
+ let hasKeyboardInput = false;
+
+ // 检查冲刺键(Shift)
+ if (keys.SHIFT.isDown) {
+ this.isSprinting = true;
+ } else if (!this.inputPlugin || this.inputPlugin.isButtonUp(this.sprintButtonId)) {
+ this.isSprinting = false;
+ }
+
+ if (cursors.up.isDown || keys.W.isDown) {
+ this.player.setVelocityY(-currentSpeed);
+ hasKeyboardInput = true;
+ }
+ if (cursors.down.isDown || keys.S.isDown) {
+ this.player.setVelocityY(currentSpeed);
+ hasKeyboardInput = true;
+ }
+ if (cursors.left.isDown || keys.A.isDown) {
+ this.player.setVelocityX(-currentSpeed);
+ hasKeyboardInput = true;
+ }
+ if (cursors.right.isDown || keys.D.isDown) {
+ this.player.setVelocityX(currentSpeed);
+ hasKeyboardInput = true;
+ }
+
+ // 触摸轮盘输入
+ if (this.inputPlugin && this.joystickId) {
+ try {
+ const joystickNormalized = this.inputPlugin.getJoystickNormalized(this.joystickId);
+ const joystickMagnitude = this.inputPlugin.getJoystickMagnitude(this.joystickId);
+
+ if (joystickNormalized.x !== 0 || joystickNormalized.y !== 0) {
+ this.player.setVelocityX(joystickNormalized.x * currentSpeed);
+ this.player.setVelocityY(joystickNormalized.y * currentSpeed);
+ }
+
+ // 更新状态文字
+ if (this.statusText) {
+ const sprintStatus = this.isSprinting ? '(冲刺中)' : '';
+ if (joystickMagnitude > 0) {
+ this.statusText.setText(`轮盘状态: 激活${sprintStatus}\n力度: ${joystickMagnitude.toFixed(2)}\n方向: X=${joystickNormalized.x.toFixed(2)}, Y=${joystickNormalized.y.toFixed(2)}\n速度: ${currentSpeed}`);
+ } else if (hasKeyboardInput) {
+ this.statusText.setText(`轮盘状态: 未激活${sprintStatus} (使用键盘)\n速度: ${currentSpeed}`);
+ } else {
+ this.statusText.setText(`轮盘状态: 未激活${sprintStatus}\n速度: ${currentSpeed}`);
+ }
+ }
+ } catch (error) {
+ console.error('轮盘更新失败:', error);
+ }
+ }
+ }
+
+ destroy() {
+ if (this.inputPlugin) {
+ this.inputPlugin.destroy();
+ }
+ super.destroy();
+ }
+}