diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/.idea/CINC.iml b/.idea/CINC.iml new file mode 100644 index 0000000..d8b3f6c --- /dev/null +++ b/.idea/CINC.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1d3ce46 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..8507111 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/config/gameConfig.js b/config/gameConfig.js index d05eaef..9aee610 100644 --- a/config/gameConfig.js +++ b/config/gameConfig.js @@ -1,20 +1,36 @@ +// 导出游戏配置对象,供main.js文件使用 export default { + // 游戏渲染类型:Phaser.AUTO表示自动选择最佳渲染方式(WebGL或Canvas) type: Phaser.AUTO, + // 游戏窗口宽度:设置为浏览器窗口的宽度 width: window.innerWidth, + // 游戏窗口高度:设置为浏览器窗口的高度 height: window.innerHeight, + // 游戏背景颜色:使用十六进制颜色值设置为天蓝色 backgroundColor: '#87ceeb', + // 物理引擎配置 physics: { + // 默认使用的物理引擎:'arcade'是Phaser内置的轻量级物理引擎 default: 'arcade', + // arcade物理引擎的具体配置 arcade: { + // 重力设置:y轴方向重力为0(适合太空或顶部视图游戏) gravity: { y: 0 }, + // 调试模式:false表示关闭物理引擎调试信息显示 debug: false } }, + // 游戏缩放配置 scale: { + // 缩放模式:Phaser.Scale.RESIZE表示游戏会随着浏览器窗口大小变化而自动调整 mode: Phaser.Scale.RESIZE, + // 自动居中:Phaser.Scale.NO_CENTER表示不自动居中游戏窗口 autoCenter: Phaser.Scale.NO_CENTER, + // 扩展父容器:true表示游戏会扩展到父容器的大小 expandParent: true, + // 缩放后的宽度:设置为浏览器窗口宽度 width: window.innerWidth, + // 缩放后的高度:设置为浏览器窗口高度 height: window.innerHeight } }; \ No newline at end of file diff --git a/main.js b/main.js index 11fee11..67cb9a6 100644 --- a/main.js +++ b/main.js @@ -1,9 +1,19 @@ +// 导入游戏的基础配置文件,该文件包含游戏的宽度、高度、渲染模式等基本设置 import gameConfig from './config/gameConfig.js'; + +// 导入游戏场景类,游戏的主要逻辑和玩法都在这个场景中实现 import GameScene from './scenes/GameScene.js'; +// 创建游戏的最终配置对象 const config = { + // 使用扩展运算符(...)将gameConfig中的所有配置项复制到当前对象中 + // 这相当于把gameConfig的内容合并到这个新的config对象里 ...gameConfig, + // 指定游戏使用的场景列表,这里只使用了一个GameScene场景 + // 游戏可以有多个场景,比如开始菜单、游戏主界面、结束界面等 scene: [GameScene] }; +// 创建一个新的Phaser游戏实例,并将配置对象传入 +// 同时将游戏实例赋值给window.game,这样在浏览器的开发者工具中可以直接访问游戏实例 window.game = new Phaser.Game(config); \ No newline at end of file diff --git a/scenes/GameScene.js b/scenes/GameScene.js index 32eebb3..6e66c15 100644 --- a/scenes/GameScene.js +++ b/scenes/GameScene.js @@ -1,83 +1,126 @@ -// scenes/GameScene.js +// 游戏场景文件:这是游戏的主要场景,包含了游戏的核心逻辑和玩法实现 + +// 导入单位类:用于创建游戏中的角色/单位 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'; +// 定义GameScene类,继承自Phaser.Scene(Phaser框架的场景类) export default class GameScene extends Phaser.Scene { + // create方法:当场景创建时自动调用,用于初始化游戏元素 create() { // === 0. 添加窗口大小变化监听 === + // 监听浏览器窗口大小变化事件,当窗口大小改变时调用resizeGame方法 window.addEventListener('resize', this.resizeGame.bind(this)); - // === 1. 创建玩家纹理(红圈)=== + // === 1. 创建玩家纹理=== + // 检查是否已经存在名为'player'的纹理,如果不存在则创建 if (!this.textures.exists('player')) { + // 创建一个32x32像素的HTML5 Canvas元素 const canvas = document.createElement('canvas'); - canvas.width = 32; - canvas.height = 32; + canvas.width = 32; // 设置Canvas宽度 + canvas.height = 32; // 设置Canvas高度 + + // 获取Canvas的2D绘图上下文 const ctx = canvas.getContext('2d'); - ctx.fillStyle = '#ff0000'; - ctx.beginPath(); + ctx.fillStyle = '#000000ff'; // 设置填充颜色为完全不透明的黑色 + ctx.beginPath(); // 开始绘制路径 + // 绘制一个圆形:圆心(16,16),半径14,从0到2π(完整圆形) ctx.arc(16, 16, 14, 0, Math.PI * 2); - ctx.fill(); + ctx.fill(); // 填充圆形 + + // 将创建的Canvas添加到Phaser的纹理管理器中,命名为'player' this.textures.addCanvas('player', canvas); } - // === 2. 扩大物理世界和相机边界(关键!)=== + // === 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) - // === 3. 创建单位组 === + // === 3. 初始化系统 === + // 创建输入系统实例 + this.inputSystem = new InputSystem(this); + // 创建选择系统实例 + this.selectionSystem = new SelectionSystem(this); + // 创建命令系统实例 + this.commandSystem = new CommandSystem(this); + // 创建虚拟摇杆实例 + this.virtualJoystick = new VirtualJoystick(this); + + // === 4. 创建单位组 === + // 创建一个单位组,用于管理游戏中的所有单位 this.unitGroup = this.add.group(); + // 创建5个玩家单位 for (let i = 0; i < 5; i++) { - const worldSizePersent = worldSize/100 + // 计算单位的初始位置 + const worldSizePersent = worldSize/100; // 世界大小的1% + // 每个单位的x坐标递增2%世界大小,y坐标固定为2%世界大小 const unit = new Unit(this, worldSizePersent + 2*i * worldSizePersent, 2 * worldSizePersent, 'player'); + // 将创建的单位添加到单位组中 this.unitGroup.add(unit); } - - // === 4. 初始化系统 === - this.inputSystem = new InputSystem(this); - this.selectionSystem = new SelectionSystem(this); - this.commandSystem = new CommandSystem(this); - this.virtualJoystick = new VirtualJoystick(this); // === 5. 添加角色之间的碰撞检测 === + // 为单位组添加碰撞检测,使得单位之间可以相互碰撞 this.physics.add.collider(this.unitGroup, this.unitGroup); // === 6. 创建小地图 === + // 创建小地图实例,设置其位置和大小 this.miniMap = new MiniMap(this, { - worldSize: worldSize, - mapSize: 200, - x: 10, - y: 10 + worldSize: worldSize, // 游戏世界大小 + mapSize: 200, // 小地图显示大小 + x: this.sys.game.config.width - mapSize - 10, // 小地图在屏幕上的x坐标 + y: 10 // 小地图在屏幕上的y坐标 }); } + // 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(); } @@ -85,8 +128,10 @@ export default class GameScene extends Phaser.Scene { /** * 窗口大小变化时的处理函数 + * 当浏览器窗口大小改变时,调整游戏画布的大小以适应新窗口 */ resizeGame() { + // 调用Phaser的scale.resize方法,将游戏画布大小调整为当前窗口大小 this.scale.resize(window.innerWidth, window.innerHeight); } } \ No newline at end of file diff --git a/systems/CommandSystem.js b/systems/CommandSystem.js index f79e1f3..706633c 100644 --- a/systems/CommandSystem.js +++ b/systems/CommandSystem.js @@ -1,36 +1,53 @@ -// systems/CommandSystem.js +// 命令系统:处理玩家对游戏单位的各种命令(如移动、选择等) + +// 从ControlMode.js导入当前激活的单位和选中的单位列表 import { activeUnit, selectedUnits } from './ControlMode.js'; +// 导出CommandSystem类,用于创建命令系统实例 export class CommandSystem { + // 构造函数:初始化命令系统 constructor(scene) { + // 保存场景引用,用于访问游戏世界和发送事件 this.scene = scene; + // 监听场景中的移动相关事件 + // 当'move-relative'事件触发时,调用onMoveRelative方法 scene.events.on('move-relative', this.onMoveRelative, this); + // 当'move-to-point'事件触发时,调用onMoveToPoint方法 scene.events.on('move-to-point', this.onMoveToPoint, this); + // 当'select-unit'事件触发时,调用onSelectUnit方法 scene.events.on('select-unit', this.onSelectUnit, this); } + // 处理相对移动命令:根据方向向量移动单位 onMoveRelative(dir) { + // 确定移动目标单位:优先使用选中的单位列表,如果没有选中单位则使用当前激活的单位 const targets = selectedUnits.length > 0 ? selectedUnits : (activeUnit ? [activeUnit] : []); + // 遍历所有目标单位,调用它们的moveRelative方法进行相对移动 targets.forEach(unit => { unit.moveRelative(dir.x, dir.y); }); } + // 处理移动到指定点的命令 onMoveToPoint(x, y) { + // 确定移动目标单位:优先使用选中的单位列表,如果没有选中单位则使用当前激活的单位 const targets = selectedUnits.length > 0 ? selectedUnits : (activeUnit ? [activeUnit] : []); - + // 遍历所有目标单位,调用它们的setTarget方法设置移动目标点 targets.forEach(unit => { - unit.setTarget(x, y); // ← 使用新接口 + unit.setTarget(x, y); // ← 使用单位的新接口方法 }); } + // 处理选择单位的命令 onSelectUnit(unit) { - // 切换单位 + // 动态导入ControlMode.js模块,确保能够访问最新的模块内容 import('./ControlMode.js').then(mod => { + // 调用ControlMode模块的setActiveUnit方法设置当前激活的单位 mod.setActiveUnit(unit); + // 触发'unit-activated'事件,通知其他系统当前单位已激活 this.scene.events.emit('unit-activated', unit); }); } diff --git a/systems/ControlMode.js b/systems/ControlMode.js index a11061c..2c41307 100644 --- a/systems/ControlMode.js +++ b/systems/ControlMode.js @@ -1,24 +1,44 @@ +// 游戏控制模式常量定义 +// 这些常量用于指定游戏当前使用的控制方式 export const CONTROL_MODE = { - SINGLE: 'single', - GROUP: 'group', - JOYSTICK: 'joystick' + SINGLE: 'single', // 单单位控制模式:只控制当前激活的单个单位 + GROUP: 'group', // 组控制模式:同时控制多个选中的单位 + JOYSTICK: 'joystick' // 摇杆控制模式:使用虚拟摇杆控制单位 }; +// 当前激活的单位变量 +// 用于存储当前玩家正在直接控制的单个单位 +// 初始值为null,表示没有激活的单位 export let activeUnit = null; + +// 选中的单位列表 +// 用于存储玩家当前选中的所有单位 +// 初始值为空数组,表示没有选中任何单位 export let selectedUnits = []; +// 设置当前激活单位的函数 +// @param {Object} unit - 要设置为激活状态的单位对象 export function setActiveUnit(unit) { + // 将传入的单位设置为当前激活单位 activeUnit = unit; - // 不再重置selectedUnits数组,这样可以同时选中多个单位 + // 注意:这里不再重置selectedUnits数组,这样可以同时选中多个单位 + // 而只激活其中一个作为当前操作的单位 } +// 将单位添加到选择列表的函数 +// @param {Object} unit - 要添加到选择列表的单位对象 export function addToSelection(unit) { + // 检查单位是否已经在选择列表中,如果不在则添加 if (!selectedUnits.includes(unit)) { + // 将单位添加到选择列表的末尾 selectedUnits.push(unit); } } +// 清除选择列表的函数 export function clearSelection() { + // 将当前激活单位设置为null(取消激活) activeUnit = null; + // 将选中的单位列表重置为空数组(取消所有选择) selectedUnits = []; } \ No newline at end of file diff --git a/systems/InputSystem.js b/systems/InputSystem.js index 341d4a7..3c6bb24 100644 --- a/systems/InputSystem.js +++ b/systems/InputSystem.js @@ -1,39 +1,68 @@ -// systems/InputSystem.js +// 输入系统:处理玩家的键盘、鼠标和触摸输入,将输入转换为游戏事件 + +// 从ControlMode.js导入清除选择和设置激活单位的函数 import { clearSelection, setActiveUnit } from './ControlMode.js'; +// 导出InputSystem类,用于创建输入系统实例 export class InputSystem { + // 构造函数:初始化输入系统 + // @param {Object} scene - 游戏场景实例 constructor(scene) { + // 保存场景引用,用于访问游戏世界和发送事件 this.scene = scene; + + // 用于存储框选开始时的鼠标世界坐标 + // 初始值为null,表示当前没有进行框选操作 this.selectionStart = null; - // 初始化键盘 + // 初始化键盘输入 + // 获取Phaser的键盘输入管理器 const kb = scene.input.keyboard; + + // 创建按键映射对象,将游戏功能与键盘按键关联 this.keys = { + // 上移键:同时支持W键和方向上键 up: [kb.addKey(Phaser.Input.Keyboard.KeyCodes.W), kb.addKey(Phaser.Input.Keyboard.KeyCodes.UP)], + // 下移键:同时支持S键和方向下键 down: [kb.addKey(Phaser.Input.Keyboard.KeyCodes.S), kb.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN)], + // 左移键:同时支持A键和方向左键 left: [kb.addKey(Phaser.Input.Keyboard.KeyCodes.A), kb.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT)], + // 右移键:同时支持D键和方向右键 right: [kb.addKey(Phaser.Input.Keyboard.KeyCodes.D), kb.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT)] }; - // 禁用浏览器右键菜单 + // 禁用浏览器右键菜单,避免影响游戏操作 scene.input.mouse.disableContextMenu(); - // 鼠标/触摸事件 + // 注册鼠标/触摸事件监听器 + // 当鼠标/触摸按下时,调用onPointerDown方法 scene.input.on('pointerdown', this.onPointerDown, this); + // 当鼠标/触摸移动时,调用onPointerMove方法 scene.input.on('pointermove', this.onPointerMove, this); + // 当鼠标/触摸释放时,调用onPointerUp方法 scene.input.on('pointerup', this.onPointerUp, this); } + // 检查按键组中是否有任何按键被按下 + // @param {Array} keyGroup - 按键对象数组 + // @returns {boolean} - 如果有任何按键被按下则返回true,否则返回false isKeyDown(keyGroup) { + // 使用some方法检查数组中是否有任何一个按键处于按下状态 return keyGroup.some(key => key.isDown); } + // update方法:每帧自动调用(约60次/秒),用于处理持续的输入 update() { - // 键盘相对移动(仅当有激活单位) + // 创建一个方向对象,用于存储移动方向 const dir = { x: 0, y: 0 }; + + // 检查左移键是否被按下,如果是则x方向减1(向左移动) if (this.isKeyDown(this.keys.left)) dir.x -= 1; + // 检查右移键是否被按下,如果是则x方向加1(向右移动) if (this.isKeyDown(this.keys.right)) dir.x += 1; + // 检查上移键是否被按下,如果是则y方向减1(向上移动) if (this.isKeyDown(this.keys.up)) dir.y -= 1; + // 检查下移键是否被按下,如果是则y方向加1(向下移动) if (this.isKeyDown(this.keys.down)) dir.y += 1; // 无论是否有按键按下,都发送移动事件 @@ -41,79 +70,101 @@ export class InputSystem { this.scene.events.emit('move-relative', dir); } + // 处理鼠标/触摸按下事件 + // @param {Object} pointer - 指针事件对象,包含鼠标/触摸的位置等信息 onPointerDown(pointer) { // 检查鼠标是否在小地图或摇杆上,如果是则不处理框选 if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer)) { return; } + // 如果是左键按下 if (pointer.leftButtonDown()) { + // 获取鼠标在游戏世界中的坐标(不是屏幕坐标) const worldX = pointer.worldX; const worldY = pointer.worldY; - // 检查是否点中某个单位 + // 检查是否点中了某个单位 let clickedUnit = null; + // 遍历场景中的所有单位 this.scene.unitGroup.getChildren().forEach(unit => { + // 获取单位的边界矩形 const bounds = unit.getBounds(); + // 检查鼠标世界坐标是否在单位的边界内 if (worldX >= bounds.x && worldX <= bounds.right && worldY >= bounds.y && worldY <= bounds.bottom) { + // 如果点中了单位,存储该单位 clickedUnit = unit; } }); if (clickedUnit) { - // 切换控制目标 + // 如果点中了单位,发送选择单位事件 this.scene.events.emit('select-unit', clickedUnit); } else { - // 开始框选或移动指令 - // 存储世界坐标用于框选 + // 如果没有点中单位,开始框选或移动指令 + // 存储框选开始时的世界坐标 this.selectionStart = { x: worldX, y: worldY }; + // 发送选择开始事件 this.scene.events.emit('selection-start', this.selectionStart); } } else if (pointer.rightButtonDown()) { - // 右键点击:移动选中的单位 + // 如果是右键按下,发送移动到指定点的事件 const worldX = pointer.worldX; const worldY = pointer.worldY; this.scene.events.emit('move-to-point', worldX, worldY); } } + // 处理鼠标/触摸移动事件 + // @param {Object} pointer - 指针事件对象 onPointerMove(pointer) { // 检查鼠标是否在小地图或摇杆上,如果是则不处理框选 if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer)) { return; } + // 如果当前正在进行框选操作 if (this.selectionStart) { - // 使用世界坐标计算框选区域 + // 获取当前鼠标在游戏世界中的坐标 const currentWorldX = pointer.worldX; const currentWorldY = pointer.worldY; - // 计算当前选择框的位置和大小 + // 计算框选区域的矩形 + // 确保矩形的x和y是左上角坐标 const rect = new Phaser.Geom.Rectangle( + // 使用Math.min确保x是较小的值(左上角) Math.min(this.selectionStart.x, currentWorldX), + // 使用Math.min确保y是较小的值(左上角) Math.min(this.selectionStart.y, currentWorldY), + // 计算矩形宽度 Math.abs(currentWorldX - this.selectionStart.x), + // 计算矩形高度 Math.abs(currentWorldY - this.selectionStart.y) ); - // 更新选择框的位置和大小 + // 发送选择更新事件,更新框选区域的显示 this.scene.events.emit('selection-update', rect); } } + // 处理鼠标/触摸释放事件 + // @param {Object} pointer - 指针事件对象 onPointerUp(pointer) { // 检查鼠标是否在小地图或摇杆上,如果是则不处理框选 if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer)) { + // 重置框选起始点 this.selectionStart = null; return; } + // 如果当前正在进行框选操作 if (this.selectionStart) { - // 使用世界坐标计算最终框选区域 + // 获取当前鼠标在游戏世界中的坐标 const currentWorldX = pointer.worldX; const currentWorldY = pointer.worldY; + // 计算最终的框选区域 const rect = new Phaser.Geom.Rectangle( Math.min(this.selectionStart.x, currentWorldX), Math.min(this.selectionStart.y, currentWorldY), @@ -121,22 +172,24 @@ export class InputSystem { Math.abs(currentWorldY - this.selectionStart.y) ); - // 如果框太小,视为“点击地面移动” + // 如果框选区域太小(宽度和高度都小于10像素),视为点击地面移动 if (rect.width < 10 && rect.height < 10) { + // 发送移动到指定点的事件 this.scene.events.emit('move-to-point', pointer.worldX, pointer.worldY); } else { - // 否则执行框选 + // 否则执行框选操作 this.scene.events.emit('selection-end', rect); } + // 重置框选起始点,结束框选操作 this.selectionStart = null; } } /** * 检查鼠标是否在小地图上 - * @param {Phaser.Input.Pointer} pointer - * @returns {boolean} + * @param {Phaser.Input.Pointer} pointer - 指针事件对象 + * @returns {boolean} - 如果鼠标在小地图上返回true,否则返回false */ isMouseOnMiniMap(pointer) { // 检查场景中是否有小地图 @@ -144,25 +197,27 @@ export class InputSystem { return false; } + // 获取小地图实例 const miniMap = this.scene.miniMap; + // 获取鼠标在屏幕上的坐标 const mouseX = pointer.x; const mouseY = pointer.y; // 计算小地图的边界 - const mapLeft = miniMap.x; - const mapRight = miniMap.x + miniMap.mapSize; - const mapTop = miniMap.y; - const mapBottom = miniMap.y + miniMap.mapSize; + const mapLeft = miniMap.x; // 小地图左边界 + const mapRight = miniMap.x + miniMap.mapSize; // 小地图右边界 + const mapTop = miniMap.y; // 小地图上边界 + const mapBottom = miniMap.y + miniMap.mapSize; // 小地图下边界 - // 检查鼠标是否在小地图边界内 + // 检查鼠标坐标是否在小地图边界内 return mouseX >= mapLeft && mouseX <= mapRight && mouseY >= mapTop && mouseY <= mapBottom; } /** - * 检查鼠标是否在摇杆上 - * @param {Phaser.Input.Pointer} pointer - * @returns {boolean} + * 检查鼠标是否在虚拟摇杆上 + * @param {Phaser.Input.Pointer} pointer - 指针事件对象 + * @returns {boolean} - 如果鼠标在摇杆上返回true,否则返回false */ isMouseOnJoystick(pointer) { // 检查场景中是否有虚拟摇杆 @@ -170,18 +225,20 @@ export class InputSystem { return false; } + // 获取虚拟摇杆实例 const joystick = this.scene.virtualJoystick; + // 获取鼠标在屏幕上的坐标 const mouseX = pointer.x; const mouseY = pointer.y; // 计算摇杆的边界(包括摇杆和操作区域) const joystickRadius = joystick.radius * 2; // 操作区域是摇杆半径的两倍 - const joystickLeft = joystick.centerX - joystickRadius; - const joystickRight = joystick.centerX + joystickRadius; - const joystickTop = joystick.centerY - joystickRadius; - const joystickBottom = joystick.centerY + joystickRadius; + const joystickLeft = joystick.centerX - joystickRadius; // 摇杆左边界 + const joystickRight = joystick.centerX + joystickRadius; // 摇杆右边界 + const joystickTop = joystick.centerY - joystickRadius; // 摇杆上边界 + const joystickBottom = joystick.centerY + joystickRadius; // 摇杆下边界 - // 检查鼠标是否在摇杆边界内 + // 检查鼠标坐标是否在摇杆边界内 return mouseX >= joystickLeft && mouseX <= joystickRight && mouseY >= joystickTop && mouseY <= joystickBottom; } diff --git a/systems/SelectionSystem.js b/systems/SelectionSystem.js index 38fe6d7..abbfba5 100644 --- a/systems/SelectionSystem.js +++ b/systems/SelectionSystem.js @@ -1,70 +1,105 @@ -// systems/SelectionSystem.js +// 选择系统:处理游戏中单位的选择逻辑,包括单个单位点击选择和多个单位框选 + +// 从ControlMode.js导入选择管理相关的函数 import { clearSelection, addToSelection, setActiveUnit } from './ControlMode.js'; +// 导出SelectionSystem类,用于创建选择系统实例 export class SelectionSystem { + // 构造函数:初始化选择系统 + // @param {Object} scene - 游戏场景实例 constructor(scene) { + // 保存场景引用,用于访问游戏世界和发送事件 this.scene = scene; + + // 选择框对象,用于显示框选区域 + // 初始值为null,表示当前没有选择框 this.selectionBox = null; + // 注册事件监听器,处理选择相关的事件 + // 当开始选择时调用onStart方法 scene.events.on('selection-start', this.onStart, this); + // 当选择区域更新时调用onUpdate方法 scene.events.on('selection-update', this.onUpdate, this); + // 当选择结束时调用onEnd方法 scene.events.on('selection-end', this.onEnd, this); + // 当选择单个单位时调用onSelectUnit方法 scene.events.on('select-unit', this.onSelectUnit, this); } + // 处理选择开始事件 + // @param {Object} pos - 选择开始的位置,包含x和y坐标 onStart(pos) { + // 如果选择框不存在,则创建一个新的选择框 if (!this.selectionBox) { + // 在游戏世界中创建一个矩形作为选择框 this.selectionBox = this.scene.add.rectangle(pos.x, pos.y, 1, 1, 0x00ff00, 0.3) - .setStrokeStyle(1, 0xffffff) - .setVisible(true); + .setStrokeStyle(1, 0xffffff) // 设置白色边框,宽度为1像素 + .setVisible(true); // 设置选择框可见 } else { + // 如果选择框已存在,重置其位置和大小并设置可见 this.selectionBox.setPosition(pos.x, pos.y).setSize(1, 1).setVisible(true); } } + // 处理选择区域更新事件 + // @param {Object} rect - 更新后的选择区域矩形 onUpdate(rect) { + // 如果选择框存在且可见 if (this.selectionBox && this.selectionBox.visible) { - // 更新选择框的位置和大小 + // 更新选择框的位置到矩形中心 this.selectionBox.setPosition(rect.centerX, rect.centerY); + // 更新选择框的大小到矩形的宽度和高度 this.selectionBox.setSize(rect.width, rect.height); } } + // 处理选择单个单位事件 + // @param {Object} unit - 被选择的单位对象 onSelectUnit(unit) { // 清除所有单位的选中状态 this.scene.unitGroup.getChildren().forEach(u => { + // 检查单位是否有setSelected方法 if (u.setSelected) { + // 设置单位为未选中状态 u.setSelected(false); } }); - // 清除当前选择 + // 清除当前的选择列表和激活单位 clearSelection(); - // 选择点击的单位 + // 设置点击的单位为激活单位 setActiveUnit(unit); + // 将点击的单位添加到选择列表 addToSelection(unit); - // 设置选中状态 + // 设置点击的单位为选中状态 if (unit.setSelected) { unit.setSelected(true); } + // 发送单位选中事件,通知其他系统 this.scene.events.emit('units-selected', [unit]); } + // 处理选择结束事件 + // @param {Object} rect - 最终的选择区域矩形 onEnd(rect) { + // 如果选择框存在,将其设置为不可见 if (this.selectionBox) { this.selectionBox.setVisible(false); } + // 获取场景中所有的单位 const units = this.scene.unitGroup.getChildren(); + + // 筛选出在选择区域内的单位 const selected = units.filter(unit => { + // 获取单位的边界矩形 const unitRect = unit.getBounds(); + // 检查单位的边界矩形是否与选择区域重叠 return Phaser.Geom.Rectangle.Overlaps(rect, unitRect); }); - - // 先清除所有单位的选中状态 units.forEach(unit => { @@ -73,18 +108,23 @@ export class SelectionSystem { } }); + // 清除当前的选择列表和激活单位 clearSelection(); + // 如果有单位被选中 if (selected.length > 0) { - // 先设置激活单位 + // 设置选中列表中的第一个单位为激活单位 setActiveUnit(selected[0]); - // 然后添加所有选中的单位(包括激活单位) + + // 将所有选中的单位添加到选择列表并设置为选中状态 selected.forEach(unit => { addToSelection(unit); if (unit.setSelected) { unit.setSelected(true); } }); + + // 发送单位选中事件,通知其他系统 this.scene.events.emit('units-selected', selected); } }