diff --git a/c.zip b/c.zip deleted file mode 100644 index b65f903..0000000 Binary files a/c.zip and /dev/null differ diff --git a/main.js b/main.js index f24b205..2bae203 100644 --- a/main.js +++ b/main.js @@ -28,6 +28,8 @@ import CharacterAnimationTestScene from './scenes/testScenes/CharacterAnimationT import MapEditTestScene from './scenes/testScenes/MapEditTestScene.js'; // 导入种田功能测试场景 import FarmingTestScene from './scenes/testScenes/FarmingTestScene.js'; +// 导入技能测试场景 +import SkillTestScene from './scenes/testScenes/SkillTestScene.js'; // 创建游戏的最终配置对象 const config = { @@ -51,7 +53,8 @@ const config = { MapEditSinglePicTestScene, CharacterAnimationTestScene, MapEditTestScene, - FarmingTestScene + FarmingTestScene, + SkillTestScene ] }; diff --git a/scenes/BootScene.js b/scenes/BootScene.js index 2ed5391..e381e8c 100644 --- a/scenes/BootScene.js +++ b/scenes/BootScene.js @@ -20,6 +20,7 @@ export default class BootScene extends Phaser.Scene { // 测试场景列表 const testScenes = [ + { key: 'SkillTestScene', name: '测试场景15:技能移动效果测试' }, { key: 'FarmingTestScene', name: '测试场景14:种田功能测试' }, { key: 'CharacterAnimationTestScene', name: '测试场景13:角色动画测试' }, { key: 'MapEditTestScene', name: '测试场景12:地图编辑测试' }, diff --git a/scenes/testScenes/SkillTestScene.js b/scenes/testScenes/SkillTestScene.js new file mode 100644 index 0000000..4fe0068 --- /dev/null +++ b/scenes/testScenes/SkillTestScene.js @@ -0,0 +1,760 @@ +// 游戏场景文件:这是游戏的主要场景,包含了游戏的核心逻辑和玩法实现 + +// 导入单位类:用于创建游戏中的角色/单位 +import Unit from '../../objects/Unit.js'; + +// 导入输入系统:处理玩家的键盘和鼠标输入 +import { InputSystem } from '../../systems/InputSystem.js'; + +// 导入选择系统:处理单位的选择逻辑 +import { SelectionSystem } from '../../systems/SelectionSystem.js'; + +// 导入命令系统:处理玩家对单位的命令(如移动、攻击等) +import { CommandSystem } from '../../systems/CommandSystem.js'; + +// 导入虚拟摇杆:用于移动设备上的操作控制 +import { VirtualJoystick } from '../../ui/VirtualJoystick.js'; + +// 导入小地图:显示游戏世界的缩略图 +import { MiniMap } from '../../ui/MiniMap.js'; + +// 定义SkillTestScene类,继承自Phaser.Scene(Phaser框架的场景类) +export default class SkillTestScene extends Phaser.Scene { + // 构造函数:初始化场景,设置场景的唯一键值 + constructor() { + super({ key: 'SkillTestScene' }); + + // 角色相关变量 + this.characters = []; + this.currentlyControlled = null; // 当前控制的角色 + this.isControllingPuppet = false; // 是否正在控制傀儡 + this.puppet = null; // 元歌的傀儡 + + // 技能相关变量 + this.skills = {}; + } + + // create方法:当场景创建时自动调用,用于初始化游戏元素 + create() { + // === 0. 添加窗口大小变化监听 === + // 监听浏览器窗口大小变化事件,当窗口大小改变时调用resizeGame方法 + window.addEventListener('resize', this.resizeGame.bind(this)); + + // === 1. 创建玩家纹理=== + // 检查是否已经存在名为'player'的纹理,如果不存在则创建 + if (!this.textures.exists('player')) { + // 创建一个32x32像素的HTML5 Canvas元素 + const canvas = document.createElement('canvas'); + canvas.width = 32; // 设置Canvas宽度 + canvas.height = 32; // 设置Canvas高度 + + // 获取Canvas的2D绘图上下文 + const ctx = canvas.getContext('2d'); + ctx.fillStyle = '#000000ff'; // 设置填充颜色为完全不透明的黑色 + ctx.beginPath(); // 开始绘制路径 + // 绘制一个圆形:圆心(16,16),半径14,从0到2π(完整圆形) + ctx.arc(16, 16, 14, 0, Math.PI * 2); + ctx.fill(); // 填充圆形 + + // 将创建的Canvas添加到Phaser的纹理管理器中,命名为'player' + this.textures.addCanvas('player', canvas); + } + + // === 2. 物理世界和相机边界=== + // 定义游戏世界的大小 + const worldSize = 2000; + + // 设置物理世界的边界:从(-worldSize,-worldSize)到(worldSize*2, worldSize*2) + // 这样游戏世界的中心就是(0,0),范围是从-2000到2000,总共4000x4000大小 + this.physics.world.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2); + + // 设置相机的边界,与物理世界边界相同 + this.cameras.main.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2); + + // 设置相机起始位置在地图中心 + // 使用centerOn方法将相机中心定位在地图坐标(0, 0) + this.cameras.main.centerOn(0, 0); // 地图中心坐标是(0, 0) + + // 添加地图网格 + this.createGrid(); + + // === 3. 初始化系统 === + // 创建输入系统实例,禁用鼠标点击移动和框选功能 + this.inputSystem = new InputSystem(this, { + mouse: false, // 禁用鼠标点击移动 + selection: false // 禁用框选功能 + }); + // 创建选择系统实例 + this.selectionSystem = new SelectionSystem(this); + // 创建命令系统实例 + this.commandSystem = new CommandSystem(this); + + // 禁用点击移动和框选功能 + // 阻止选择单位事件触发移动 + this.events.on('select-unit', (unit) => { + // 只允许单位选择,不执行任何移动操作 + console.log('单位已选中:', unit.name); + }); + + // 阻止移动到指定点事件 + this.events.on('move-to-point', (x, y) => { + // 不执行任何移动操作 + return false; + }); + + // 阻止选择开始事件 + this.events.on('selection-start', (pos) => { + // 不执行框选操作 + return false; + }); + + // 阻止选择更新事件 + this.events.on('selection-update', (rect) => { + // 不更新框选区域 + return false; + }); + + // 阻止选择结束事件 + this.events.on('selection-end', (rect) => { + // 不执行框选结束操作 + return false; + }); + // 创建虚拟摇杆实例 + this.virtualJoystick = new VirtualJoystick(this); + + // === 4. 创建角色 === + // 创建一个单位组,用于管理游戏中的所有角色 + this.unitGroup = this.add.group(); + + // 创建元歌 + const yuange = new Unit(this, -500, 0, 'player'); + yuange.name = 'yuange'; + yuange.type = 'yuange'; + this.unitGroup.add(yuange); + this.characters.push(yuange); + + // 创建貂蝉 + const diaochan = new Unit(this, 500, 0, 'player'); + diaochan.name = 'diaochan'; + diaochan.type = 'diaochan'; + this.unitGroup.add(diaochan); + this.characters.push(diaochan); + + // 默认控制元歌 + this.currentlyControlled = yuange; + yuange.setSelected(true); + + // 将默认角色添加到ControlMode的选中列表中,以便虚拟摇杆可以控制 + import('../../systems/ControlMode.js').then(mod => { + mod.clearSelection(); + mod.addToSelection(yuange); + mod.setActiveUnit(yuange); + // 默认锁定相机到元歌 + mod.setCameraLocked(true); + }); + + // === 5. 添加角色之间的碰撞检测 === + // 为单位组添加碰撞检测,使得角色之间可以相互碰撞 + this.physics.add.collider(this.unitGroup, this.unitGroup); + + // === 6. 创建技能轮盘 === + // 创建技能轮盘实例 + this.skillWheel = { + isActive: false, + base: null, + knob: null, + pointer: null, + centerX: 0, + centerY: 0, + radius: 50, + enabled: true, + activeSkill: null, + skillButtons: [], + skillInfo: { + 1: { key: 'Q', color: 0x00ff00 }, + 2: { key: 'W', color: 0x00ffff }, + 3: { key: 'E', color: 0xff00ff }, + 4: { key: 'R', color: 0xffff00 } + } + }; + + // 创建技能轮盘UI + this.createSkillWheel(); + // 绑定技能轮盘事件 + this.bindSkillWheelEvents(); + + // === 7. 添加角色切换按键 === + const kb = this.input.keyboard; + this.switchKey = kb.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); + kb.on('keydown-SPACE', () => this.switchCharacter()); + + // === 7. 添加技能UI提示 === + this.createSkillUI(); + + // === 6. 创建小地图 === + // 创建小地图实例,设置其位置和大小 + this.miniMap = new MiniMap(this, { + worldSize: worldSize, // 游戏世界大小 + mapSize: 200, // 小地图显示大小 + x: this.sys.game.config.width - 200 - 10, // 小地图在屏幕上的x坐标 + y: 10 // 小地图在屏幕上的y坐标 + }); + } + + // 创建地图网格 + createGrid() { + // 创建一个图形对象用于绘制网格 + const graphics = this.add.graphics(); + + // 设置网格线样式 + graphics.lineStyle(1, 0x555555, 0.3); + + // 定义网格参数 + const gridSize = 50; // 网格大小 + const worldSize = 2000; // 世界大小,与物理世界边界一致 + + // 绘制垂直线 + for (let x = -worldSize; x <= worldSize; x += gridSize) { + graphics.moveTo(x, -worldSize); + graphics.lineTo(x, worldSize); + } + + // 绘制水平线 + for (let y = -worldSize; y <= worldSize; y += gridSize) { + graphics.moveTo(-worldSize, y); + graphics.lineTo(worldSize, y); + } + + // 绘制完成 + graphics.strokePath(); + + // 确保网格在所有游戏对象之下 + graphics.setDepth(-10); + + // 让网格随相机移动 + graphics.setScrollFactor(1); + } + + // update方法:每帧自动调用(约60次/秒),用于更新游戏状态 + update() { + // 更新输入系统,处理玩家的键盘和鼠标输入 + this.inputSystem.update(); + + // 更新所有单位的移动逻辑和选中状态 + // 获取单位组中的所有子元素(即所有单位) + this.unitGroup.getChildren().forEach(unit => { + // 如果单位有自己的update方法,则调用它 + if (unit.update) { + unit.update(); + } + // 调用单位的updateMovement方法,更新单位的移动状态 + unit.updateMovement(); + }); + + // 更新小地图显示 + if (this.miniMap) { + this.miniMap.update(); + } + + // 更新虚拟摇杆状态 + if (this.virtualJoystick) { + this.virtualJoystick.update(); + } + + // 相机锁定逻辑:如果有锁定状态且有当前控制的角色,则让相机跟随角色 + import('../systems/ControlMode.js').then(mod => { + if (mod.cameraLocked && this.currentlyControlled) { + this.cameras.main.startFollow(this.currentlyControlled); + } else { + this.cameras.main.stopFollow(); + } + }); + } + + /** + * 创建技能UI提示 + */ + createSkillUI() { + // 创建技能提示文本 + this.skillInfoText = this.add.text(10, this.sys.game.config.height - 100, + '技能施法: 点击右侧技能轮盘拖放\n按空格键切换角色', + { fontSize: '16px', fill: '#ffffff', fontFamily: 'Arial' }) + .setScrollFactor(0) // 固定UI,不随滚动移动 + .setDepth(100); + + // 创建当前控制角色提示 + this.controlInfoText = this.add.text(10, this.sys.game.config.height - 60, + `当前控制: ${this.currentlyControlled.name === 'yuange' ? '元歌' : '貂蝉'}`, + { fontSize: '18px', fill: '#00ff00', fontFamily: 'Arial' }) + .setScrollFactor(0) + .setDepth(100); + } + + /** + * 创建技能轮盘 + */ + createSkillWheel() { + // 技能轮盘基础位置(右下角) + this.skillWheel.centerX = this.scale.width - 100; + this.skillWheel.centerY = this.scale.height - 100; + + // 创建技能轮盘基础(默认隐藏,选中技能时显示) + this.skillWheel.base = this.add.circle(this.skillWheel.centerX, this.skillWheel.centerY, this.skillWheel.radius, 0x444444, 0.5); + this.skillWheel.base.setScrollFactor(0); // 不随相机移动 + this.skillWheel.base.setDepth(100); + this.skillWheel.base.setVisible(false); + + // 创建技能按钮(扇形排列:技能1-4在右下角左上方,大半径扇形分布) + const skills = [1, 2, 3, 4]; + const startAngle = -Math.PI; // 起始角度(-90度,左) + const endAngle = -Math.PI / 2; // 结束角度(0度,上) + const angleRange = endAngle - startAngle; + const angleStep = angleRange / (skills.length - 1); + + this.skillWheel.skillButtons = []; + for (let i = 0; i < skills.length; i++) { + const skillIndex = skills[i]; + // 计算技能按钮的角度(扇形排列) + const angle = startAngle + i * angleStep; + const distance = 120; // 增大技能按钮距离中心的距离 + const x = this.skillWheel.centerX + Math.cos(angle) * distance; + const y = this.skillWheel.centerY + Math.sin(angle) * distance; + + // 创建技能按钮 + const button = this.add.circle(x, y, 25, this.skillWheel.skillInfo[skillIndex].color, 0.6); + button.setScrollFactor(0); + button.setDepth(100); + + // 添加技能按键文本(使用数字1-4代替Q-W-E-R) + const text = this.add.text(x, y, skillIndex.toString(), { + fontSize: '14px', + fill: '#000000', + fontFamily: 'Arial', + fontWeight: 'bold' + }) + .setOrigin(0.5, 0.5) + .setScrollFactor(0) + .setDepth(101); + + // 保存技能信息 + button.skillIndex = skillIndex; + button.skillKey = this.skillWheel.skillInfo[skillIndex].key; + button.x = x; + button.y = y; + + // 添加点击事件 + button.setInteractive({ useHandCursor: true }); + button.on('pointerdown', (pointer) => { + // 选中技能后,将轮盘中心设置为技能按钮的中心位置 + this.skillWheel.centerX = x; + this.skillWheel.centerY = y; + this.skillWheel.base.setPosition(x, y); + this.skillWheel.base.setVisible(true); + + this.skillWheel.activeSkill = skillIndex; + this.startSkillCast(pointer); + }); + + this.skillWheel.skillButtons.push(button); + } + + // 创建轮盘旋钮 + this.skillWheel.knob = this.add.circle(this.skillWheel.centerX, this.skillWheel.centerY, this.skillWheel.radius * 0.5, 0xff0000, 0.8); + this.skillWheel.knob.setScrollFactor(0); + this.skillWheel.knob.setDepth(101); + this.skillWheel.knob.setVisible(false); + } + + /** + * 绑定技能轮盘事件 + */ + bindSkillWheelEvents() { + // 鼠标移动事件 + this.input.on('pointermove', (ptr) => { + // 只有在技能轮盘激活时才处理 + if (!this.skillWheel.isActive || this.skillWheel.pointer !== ptr) { + return; + } + + // 更新旋钮位置(与移动轮盘相同的逻辑) + const dx = ptr.x - this.skillWheel.centerX; + const dy = ptr.y - this.skillWheel.centerY; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance > this.skillWheel.radius) { + // 限制在圆内 + const angle = Math.atan2(dy, dx); + const knobX = this.skillWheel.centerX + Math.cos(angle) * this.skillWheel.radius; + const knobY = this.skillWheel.centerY + Math.sin(angle) * this.skillWheel.radius; + this.skillWheel.knob.setPosition(knobX, knobY); + } else { + this.skillWheel.knob.setPosition(ptr.x, ptr.y); + } + }); + + // 鼠标释放事件 + this.input.on('pointerup', (ptr) => { + // 只有在技能轮盘激活时才处理 + if (!this.skillWheel.isActive || this.skillWheel.pointer !== ptr) { + return; + } + + // 完成技能释放 + this.finishSkillCast(); + }); + } + + /** + * 开始技能施法 + */ + startSkillCast(pointer) { + // 设置技能轮盘状态 + this.skillWheel.isActive = true; + this.skillWheel.pointer = pointer; + + // 显示旋钮和轮盘基础 + this.skillWheel.knob.setVisible(true); + this.skillWheel.base.setVisible(true); + + // 设置旋钮初始位置为轮盘中心 + this.skillWheel.knob.setPosition(this.skillWheel.centerX, this.skillWheel.centerY); + + // 根据技能索引改变旋钮颜色 + if (this.skillWheel.activeSkill && this.skillWheel.skillInfo[this.skillWheel.activeSkill]) { + this.skillWheel.knob.fillColor = this.skillWheel.skillInfo[this.skillWheel.activeSkill].color; + } + } + + /** + * 完成技能施法 + */ + finishSkillCast() { + // 检查是否有激活的技能 + if (!this.skillWheel.activeSkill) { + return; + } + + // 释放技能 + this.castSkill(this.skillWheel.activeSkill); + + // 重置技能轮盘状态 + this.skillWheel.isActive = false; + this.skillWheel.pointer = null; + this.skillWheel.activeSkill = null; + + // 隐藏旋钮和轮盘基础 + this.skillWheel.knob.setVisible(false); + this.skillWheel.base.setVisible(false); + + // 将旋钮和基础重置到默认位置(技能按钮扇形区域的中心) + this.skillWheel.centerX = this.scale.width - 200; + this.skillWheel.centerY = this.scale.height - 200; + this.skillWheel.base.setPosition(this.skillWheel.centerX, this.skillWheel.centerY); + this.skillWheel.knob.setPosition(this.skillWheel.centerX, this.skillWheel.centerY); + } + + /** + * 更新技能轮盘显示 + */ + updateSkillWheelDisplay() { + // 根据当前控制的角色设置技能信息 + if (this.isControllingPuppet) { + // 傀儡的技能设置 + this.skillWheel.skillInfo = { + 1: { key: 'Q', color: 0xff0000 }, // 收回傀儡 + 2: { key: 'W', color: 0x00ffff }, // 交换位置 + 3: { key: 'E', color: 0x0000ff }, // 占位 + 4: { key: 'R', color: 0xffff00 } // 快速移动 + }; + } else if (this.currentlyControlled && this.currentlyControlled.type === 'yuange') { + // 元歌本体的技能设置 + this.skillWheel.skillInfo = { + 1: { key: 'Q', color: 0x00ff00 }, // 扔出傀儡 + 2: { key: 'W', color: 0x00ffff }, // 占位 + 3: { key: 'E', color: 0xff00ff }, // 占位 + 4: { key: 'R', color: 0xffff00 } // 本体移动 + }; + } else if (this.currentlyControlled && this.currentlyControlled.type === 'diaochan') { + // 貂蝉的技能设置 + this.skillWheel.skillInfo = { + 1: { key: 'Q', color: 0x00ff00 }, // 占位 + 2: { key: 'W', color: 0x00ffff }, // 移动 + 3: { key: 'E', color: 0xff00ff }, // 占位 + 4: { key: 'R', color: 0xffff00 } // 占位 + }; + } + + // 更新现有技能按钮的颜色,不重新创建 + this.skillWheel.skillButtons.forEach(button => { + const skillIndex = button.skillIndex; + if (this.skillWheel.skillInfo[skillIndex]) { + button.fillColor = this.skillWheel.skillInfo[skillIndex].color; + } + }); + } + + /** + * 切换控制的角色 + */ + switchCharacter() { + if (this.isControllingPuppet) { + // 如果正在控制傀儡,先切换回本体 + this.switchToYuange(); + } else { + // 否则切换到另一个角色 + const currentIndex = this.characters.indexOf(this.currentlyControlled); + const nextIndex = (currentIndex + 1) % this.characters.length; + + // 取消当前角色的选中状态 + this.currentlyControlled.setSelected(false); + + // 切换到下一个角色 + this.currentlyControlled = this.characters[nextIndex]; + this.currentlyControlled.setSelected(true); + + // 更新UI提示 + this.controlInfoText.setText(`当前控制: ${this.currentlyControlled.name === 'yuange' ? '元歌' : '貂蝉'}`); + + // 更新技能轮盘显示 + this.updateSkillWheelDisplay(); + + // 更新ControlMode的选中列表 + import('../../systems/ControlMode.js').then(mod => { + mod.clearSelection(); + mod.addToSelection(this.currentlyControlled); + mod.setActiveUnit(this.currentlyControlled); + // 自动锁定相机到新角色 + mod.setCameraLocked(true); + }); + } + } + + /** + * 释放技能 + * @param {number} skillIndex 技能索引(1-4) + */ + castSkill(skillIndex) { + // 检查是否有当前控制的角色 + if (!this.currentlyControlled) return; + + // 根据当前控制角色类型和技能索引释放对应技能 + if (this.currentlyControlled.type === 'yuange' || this.currentlyControlled.type === 'yuange-puppet') { + this.castYuangeSkill(skillIndex); + } else if (this.currentlyControlled.type === 'diaochan') { + this.castDiaochanSkill(skillIndex); + } + } + + /** + * 释放元歌技能 + * @param {number} skillIndex 技能索引(1-4) + */ + castYuangeSkill(skillIndex) { + const yuange = this.characters.find(char => char.name === 'yuange'); + + // 计算技能释放方向(基于技能轮盘中心到指针的相对方向) + const pointer = this.input.activePointer; + // 计算轮盘中心到指针的方向向量 + const directionX = pointer.x - this.skillWheel.centerX; + const directionY = pointer.y - this.skillWheel.centerY; + + if (this.isControllingPuppet) { + // 控制傀儡时的技能 + switch (skillIndex) { + case 1: // 技能1:收回傀儡 + this.switchToYuange(); + break; + case 2: // 技能2:本体拉近并交换位置 + if (this.puppet) { + // 记录傀儡当前位置 + const puppetX = this.puppet.x; + const puppetY = this.puppet.y; + + // 获取本体拉近到傀儡一定范围内的坐标 + const closeX = this.puppet.x - 50 * Math.sign(this.puppet.x - yuange.x); + const closeY = this.puppet.y - 50 * Math.sign(this.puppet.y - yuange.y); + + // 傀儡移动到本体拉近后的位置 + this.puppet.setPosition(closeX, closeY); + this.puppet.setVelocity(0, 0); + this.puppet.clearTarget(); + + // 直接设置本体位置到傀儡原本位置 + yuange.setPosition(puppetX, puppetY); + yuange.setVelocity(0, 0); + + } + break; + case 4: // 技能4:傀儡快速移动并拉本体 + if (this.puppet && this.isControllingPuppet) { + // 使用轮盘相对方向计算目标位置 + const maxDistance = 300; + const distance = Math.sqrt(directionX * directionX + directionY * directionY); + let ratio = maxDistance / distance; + if (isNaN(ratio) || ratio > 1) ratio = 1; + + // 计算目标位置:傀儡当前位置 + 方向向量 * 最大距离 + const targetX = this.puppet.x + directionX * ratio; + const targetY = this.puppet.y + directionY * ratio; + + // 傀儡移动到目标位置 + // this.puppet.setTarget(targetX, targetY); + this.puppet.setPosition(targetX, targetY); + this.puppet.setVelocity(0, 0); + this.puppet.clearTarget(); + + // 本体瞬间移动到傀儡附近 + yuange.x = this.puppet.x - 50 * Math.sign(this.puppet.x - yuange.x); + yuange.y = this.puppet.y - 50 * Math.sign(this.puppet.y - yuange.y); + } + break; + } + } else { + // 控制本体时的技能 + switch (skillIndex) { + case 1: // 技能1:扔出傀儡 + if (!this.puppet) { + // 使用轮盘相对方向计算目标位置 + const maxDistance = 300; + const distance = Math.sqrt(directionX * directionX + directionY * directionY); + let ratio = maxDistance / distance; + if (isNaN(ratio) || ratio > 1) ratio = 1; + + // 计算目标位置:元歌当前位置 + 方向向量 * 最大距离 + const targetX = yuange.x + directionX * ratio; + const targetY = yuange.y + directionY * ratio; + + // 创建傀儡直接在指定位置生成,不需要从本体位置开始移动 + this.puppet = new Unit(this, targetX, targetY, 'player'); + this.puppet.name = 'yuange-puppet'; + this.puppet.type = 'yuange-puppet'; + this.puppet.setScale(0.8); + this.puppet.tint = 0x00ffff; // 用蓝色区分傀儡 + this.unitGroup.add(this.puppet); + + // 切换控制到傀儡 + this.isControllingPuppet = true; + yuange.setSelected(false); + this.currentlyControlled = this.puppet; + this.controlInfoText.setText('当前控制: 元歌傀儡'); + + // 更新技能轮盘显示为傀儡的技能 + this.updateSkillWheelDisplay(); + + // 更新ControlMode的选中列表,确保虚拟摇杆能控制傀儡 + import('../../systems/ControlMode.js').then(mod => { + mod.clearSelection(); + mod.addToSelection(this.puppet); + mod.setActiveUnit(this.puppet); + // 自动锁定相机到傀儡 + mod.setCameraLocked(true); + }); + } + break; + case 4: // 技能4:本体向技能指向方向移动 + // 确保是本体在控制 + if (!this.isControllingPuppet) { + // 使用轮盘相对方向计算目标位置 + const maxDistance = 400; + const distance = Math.sqrt(directionX * directionX + directionY * directionY); + let ratio = maxDistance / distance; + if (isNaN(ratio) || ratio > 1) ratio = 1; + + // 计算目标位置:元歌当前位置 + 方向向量 * 最大距离 + const targetX = yuange.x + directionX * ratio; + const targetY = yuange.y + directionY * ratio; + + // 短暂停顿(0.5秒) + yuange.setVelocity(0, 0); + + // 延迟后直接传送,不需要移动过程 + this.time.delayedCall(500, () => { + yuange.setPosition(targetX, targetY); + yuange.setVelocity(0, 0); + }, [], this); + } + break; + } + } + } + + /** + * 释放貂蝉技能 + * @param {number} skillIndex 技能索引(1-4) + */ + castDiaochanSkill(skillIndex) { + const diaochan = this.currentlyControlled; + + // 计算技能释放方向(基于技能轮盘中心到指针的相对方向) + const pointer = this.input.activePointer; + // 计算轮盘中心到指针的方向向量 + const directionX = pointer.x - this.skillWheel.centerX; + const directionY = pointer.y - this.skillWheel.centerY; + + switch (skillIndex) { + case 2: // 技能2:向指定方向移动 + // 使用轮盘相对方向计算移动方向 + const maxDistance = 250; + const distance = Math.sqrt(directionX * directionX + directionY * directionY); + let ratio = maxDistance / distance; + if (isNaN(ratio) || ratio > 1) ratio = 1; + + // 计算移动距离 + const moveX = directionX * ratio; + const moveY = directionY * ratio; + + // 立即向目标方向移动一定距离 + diaochan.x += moveX; + diaochan.y += moveY; + break; + } + } + + /** + * 切换回元歌本体 + */ + switchToYuange() { + if (this.puppet) { + // 删除傀儡 + this.puppet.destroy(); + this.puppet = null; + } + + // 切换回元歌本体控制 + this.isControllingPuppet = false; + this.currentlyControlled = this.characters.find(char => char.name === 'yuange'); + this.currentlyControlled.setSelected(true); + + // 更新UI提示 + this.controlInfoText.setText('当前控制: 元歌'); + + // 更新技能轮盘显示为元歌本体的技能 + this.updateSkillWheelDisplay(); + + // 更新ControlMode的选中列表,确保虚拟摇杆能控制本体 + import('../../systems/ControlMode.js').then(mod => { + mod.clearSelection(); + mod.addToSelection(this.currentlyControlled); + mod.setActiveUnit(this.currentlyControlled); + // 自动锁定相机到本体 + mod.setCameraLocked(true); + }); + } + + /** + * 窗口大小变化时的处理函数 + * 当浏览器窗口大小改变时,调整游戏画布的大小以适应新窗口 + */ + resizeGame() { + // 调用Phaser的scale.resize方法,将游戏画布大小调整为当前窗口大小 + this.scale.resize(window.innerWidth, window.innerHeight); + + // 更新UI位置 + if (this.skillInfoText) { + this.skillInfoText.setPosition(10, window.innerHeight - 100); + } + if (this.controlInfoText) { + this.controlInfoText.setPosition(10, window.innerHeight - 60); + } + } +} \ No newline at end of file diff --git a/systems/CommandSystem.js b/systems/CommandSystem.js index 5c85f00..e97e481 100644 --- a/systems/CommandSystem.js +++ b/systems/CommandSystem.js @@ -26,8 +26,8 @@ export class CommandSystem { // 遍历所有目标单位,调用它们的moveRelative方法进行相对移动 targets.forEach(unit => { - // 确保unit是有效的Unit实例 - if (unit && typeof unit.moveRelative === 'function') { + // 确保unit是有效的Unit实例,并且body存在(防止已销毁的单位被调用) + if (unit && typeof unit.moveRelative === 'function' && unit.body) { unit.moveRelative(dir.x, dir.y); } }); diff --git a/systems/InputSystem.js b/systems/InputSystem.js index f0b2010..8542169 100644 --- a/systems/InputSystem.js +++ b/systems/InputSystem.js @@ -309,11 +309,54 @@ export class InputSystem { return this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer) || + this.isMouseOnSkillWheel(pointer) || this.isMouseOnSidebar(pointer) || this.isMouseOnInventoryUI(pointer) || this.isMouseOnItemListUI(pointer); } + /** + * 检查鼠标是否在技能轮盘上 + * @param {Object} pointer - 指针事件对象 + * @returns {boolean} - 如果鼠标在技能轮盘上返回true,否则返回false + */ + isMouseOnSkillWheel(pointer) { + // 检查场景中是否有技能轮盘 + if (!this.scene.skillWheel) { + return false; + } + + // 获取技能轮盘实例 + const skillWheel = this.scene.skillWheel; + // 获取鼠标在屏幕上的坐标 + const mouseX = pointer.x; + const mouseY = pointer.y; + + // 计算技能轮盘的边界(包括轮盘和操作区域) + const skillWheelRadius = skillWheel.radius * 3; // 操作区域是轮盘半径的三倍,考虑技能按钮的位置 + const skillWheelLeft = skillWheel.centerX - skillWheelRadius; // 轮盘左边界 + const skillWheelRight = skillWheel.centerX + skillWheelRadius; // 轮盘右边界 + const skillWheelTop = skillWheel.centerY - skillWheelRadius; // 轮盘上边界 + const skillWheelBottom = skillWheel.centerY + skillWheelRadius; // 轮盘下边界 + + // 检查鼠标是否在操作区域内 + if (mouseX >= skillWheelLeft && mouseX <= skillWheelRight && + mouseY >= skillWheelTop && mouseY <= skillWheelBottom) { + return true; + } + + // 检查是否点击在技能按钮上 + for (const button of skillWheel.skillButtons) { + const buttonBounds = button.getBounds(); + if (mouseX >= buttonBounds.x && mouseX <= buttonBounds.right && + mouseY >= buttonBounds.y && mouseY <= buttonBounds.bottom) { + return true; + } + } + + return false; + } + /** * 检查当前是否处于种植或收获模式 * @returns {boolean} - 如果处于种植或收获模式返回true,否则返回false