feat(item) 优化移动及添加测试场景

This commit is contained in:
chicrobot committed 2026-01-04 00:55:48 +08:00
1 parent 2f7210f47e
commit b9a5180293
15 files changed
+1428 -62

No files matched your search

+64 -10
View File
@@ -8,6 +8,8 @@ CINC是一个基于Phaser.js开发的2D策略/模拟游戏项目。该项目采
- **选择系统**:支持单选和框选多个单位
- **命令系统**:处理单位移动命令,支持相对移动和目标点移动
- **输入系统**:统一处理键盘和鼠标输入
- **物品系统**:支持物品的创建、拾取、使用和管理
- **侧边栏UI**:显示游戏信息、角色属性、物品详情和交互提示
- **响应式设计**:适配不同屏幕尺寸
- **虚拟摇杆**:支持触摸设备操作
@@ -21,20 +23,34 @@ CINC是一个基于Phaser.js开发的2D策略/模拟游戏项目。该项目采
```
├── config/ # 游戏配置文件
│ └── gameConfig.js
├── data/ # 游戏数据文件
│ └── items.json # 物品数据定义
├── index.html # 入口HTML文件
├── items/ # 物品系统
│ ├── Item.js # 物品类
│ └── ItemManager.js # 物品管理器
├── main.js # 游戏初始化入口
├── objects/ # 游戏对象定义
│ └── Unit.js # 单位类
├── phaser.min.js # Phaser.js库
├── scenes/ # 游戏场景
│ └── GameScene.js
│ ├── BootScene.js # 启动场景
│ ├── GameScene.js # 主游戏场景
│ ├── ItemTestScene.js # 物品系统测试场景
│ ├── MovementTestScene1.js # 移动测试场景1
│ ├── MovementTestScene2.js # 移动测试场景2
│ ├── MovementTestScene3.js # 移动测试场景3
│ ├── MovementTestScene4.js # 移动测试场景4
│ ├── MovementTestScene5.js # 移动测试场景5
│ └── MovementTestScene6.js # 移动测试场景6
├── systems/ # 游戏系统
│ ├── CommandSystem.js # 命令处理系统
│ ├── ControlMode.js # 控制模式管理
│ ├── InputSystem.js # 输入处理系统
│ └── SelectionSystem.js # 单位选择系统
├── ui/ # 用户界面组件
│ └── VirtualJoystick.js # 虚拟摇杆
│ ├── MiniMap.js # 小地图组件
│ └── VirtualJoystick.js # 虚拟摇杆
└── utils/ # 工具文件
```
@@ -50,22 +66,60 @@ http://localhost:8000
```
## 游戏操作
### 单位操作
- **单选单位**:鼠标左键点击单位
- **框选单位**:按住鼠标左键拖动框选多个单位
- **移动命令**:点击空地指定移动目标点
- **键盘控制**:使用WASD或方向键进行相对移动(需先选中单位)
### 物品操作
- **查看物品信息**:鼠标左键点击物品
- **拾取物品**:点击物品后按E键
- **快捷使用物品**:点击物品后按Q键
- **创建随机物品**:按1键
- **创建食物类物品**:按2键
- **创建工具类物品**:按3键
- **清理所有物品**:按4键
### 场景导航
- **返回启动场景**:按空格键
### 相机控制
- **移动视角**:使用WASD或方向键
- **小地图交互**:点击小地图快速移动视角,拖拽小地图调整视角
## 开发说明
项目采用模块化设计,各系统之间通过事件系统通信,便于扩展和维护。主要系统职责:
- **InputSystem**:处理所有输入事件,转换为游戏命令
- **SelectionSystem**:管理单位选择状态
- **CommandSystem**:执行具体游戏命令
- **InputSystem**:处理所有输入事件,转换为游戏命令,支持键盘、鼠标和虚拟摇杆输入
- **SelectionSystem**:管理单位选择状态,支持单选和框选
- **CommandSystem**:执行具体游戏命令,处理单位移动和交互
- **ItemManager**:加载和管理物品数据,创建和维护物品实例
- **Unit**:单位对象,处理自身逻辑和渲染
- **Item**:物品对象,处理物品交互和使用逻辑
## 扩展建议
- 添加单位属性系统(生命值、攻击力等)
- 实现战斗系统
- 添加地图和关卡系统
- 实现资源管理系统
- 添加UI界面(控制面板、状态栏等)
- **物品系统扩展**:
- 实现背包系统,管理拾取的物品
- 添加物品合成和制作系统
- 实现物品耐久度和消耗机制
- 添加更多物品类型和属性
- **战斗系统**:
- 实现单位属性系统(生命值、攻击力等)
- 添加攻击和防御机制
- 实现战斗动画和特效
- **地图和关卡系统**:
- 实现地图生成和加载
- 添加关卡目标和胜利条件
- 实现地形和障碍物系统
- **资源管理系统**:
- 添加资源采集和生产机制
- 实现资源存储和消耗系统
- **UI界面扩展**:
- 实现背包界面
- 添加角色属性面板
- 实现技能和升级系统界面
- 添加游戏菜单和设置界面
- **多人游戏**:
- 实现本地多人游戏
- 添加网络多人游戏支持
+88
View File
@@ -18,6 +18,10 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite {
this.isSelected = false;
this.selectedIndicator = null;
this.createSelectedIndicator();
// 目标地点显示
this.targetIndicator = null;
this.pathGraphics = null;
}
/**
@@ -35,6 +39,30 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite {
this.selectedIndicator.setScrollFactor(1, 1);
}
/**
* 创建目标地点指示器
*/
createTargetIndicator() {
if (!this.targetIndicator) {
// 创建目标地点指示器(红色圆点)
this.targetIndicator = this.scene.add.circle(0, 0, 8, 0xff0000, 1);
this.targetIndicator.setOrigin(0.5, 0.5);
this.targetIndicator.setScrollFactor(1, 1);
this.targetIndicator.setVisible(false);
}
}
/**
* 创建寻路路径图形
*/
createPathGraphics() {
if (!this.pathGraphics) {
// 创建图形对象用于绘制路径
this.pathGraphics = this.scene.add.graphics();
this.pathGraphics.setScrollFactor(1, 1);
}
}
/**
* 设置单位选中状态
* @param {boolean} isSelected
@@ -44,6 +72,41 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite {
if (this.selectedIndicator) {
this.selectedIndicator.setVisible(isSelected);
}
// 只有选中时才显示目标地点和路径
this.updateTargetVisibility();
}
/**
* 更新目标地点和路径的可见性
*/
updateTargetVisibility() {
if (this.targetIndicator) {
this.targetIndicator.setVisible(this.isSelected && this.target !== null);
}
if (this.pathGraphics) {
this.pathGraphics.clear();
// 只有选中且有目标时才绘制路径
if (this.isSelected && this.target !== null) {
this.drawPath();
}
}
}
/**
* 绘制寻路路径
*/
drawPath() {
if (!this.pathGraphics || !this.target) {
return;
}
this.pathGraphics.clear();
this.pathGraphics.lineStyle(2, 0x00ff00, 0.8);
this.pathGraphics.beginPath();
this.pathGraphics.moveTo(this.x, this.y);
this.pathGraphics.lineTo(this.target.x, this.target.y);
this.pathGraphics.stroke();
}
/**
@@ -60,6 +123,18 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite {
const clampedY = Math.max(worldBounds.y, Math.min(worldBounds.bottom, y));
this.target = { x: clampedX, y: clampedY };
// 创建目标指示器和路径图形(如果不存在)
this.createTargetIndicator();
this.createPathGraphics();
// 更新目标指示器位置
if (this.targetIndicator) {
this.targetIndicator.setPosition(this.target.x, this.target.y);
}
// 更新目标可见性和路径绘制
this.updateTargetVisibility();
}
/**
@@ -68,6 +143,14 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite {
clearTarget() {
this.target = null;
this.setVelocity(0, 0);
// 隐藏目标指示器和清除路径
if (this.targetIndicator) {
this.targetIndicator.setVisible(false);
}
if (this.pathGraphics) {
this.pathGraphics.clear();
}
}
/**
@@ -99,6 +182,11 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite {
if (this.selectedIndicator && this.isSelected) {
this.selectedIndicator.setPosition(this.x, this.y);
}
// 更新路径绘制(单位位置变化时)
if (this.isSelected && this.target !== null) {
this.drawPath();
}
}
updateMovement() {
+80
View File
@@ -0,0 +1,80 @@
// 启动场景:用于选择进入不同的测试场景
import GameScene from './GameScene.js';
export default class BootScene extends Phaser.Scene {
constructor() {
super({ key: 'BootScene' });
}
create() {
// 设置背景色
this.cameras.main.setBackgroundColor('#000000');
// 添加标题文本
this.add.text(this.sys.game.config.width / 2, 100, '测试场景菜单', {
fontSize: '32px',
fill: '#ffffff',
fontFamily: 'Arial'
}).setOrigin(0.5, 0.5);
// 测试场景列表
const testScenes = [
{ key: 'MovementTestScene1', name: '测试场景1:相对移动(键盘控制)' },
{ key: 'MovementTestScene2', name: '测试场景2:目标点移动' },
{ key: 'MovementTestScene3', name: '测试场景3:单位选择与移动命令' },
{ key: 'MovementTestScene4', name: '测试场景4:虚拟摇杆控制' },
{ key: 'MovementTestScene5', name: '测试场景5:移动更新逻辑' },
{ key: 'MovementTestScene6', name: '测试场景6:小地图测试' },
{ key: 'ItemTestScene', name: '测试场景7:物品系统测试' },
{ key: 'GameScene', name: '返回主游戏场景' }
];
// 创建按钮网格
const buttonWidth = 400;
const buttonHeight = 50;
const buttonSpacing = 20;
const startY = 200;
testScenes.forEach((scene, index) => {
// 创建按钮背景
const button = this.add.rectangle(
this.sys.game.config.width / 2,
startY + index * (buttonHeight + buttonSpacing),
buttonWidth,
buttonHeight,
0x333333
).setOrigin(0.5, 0.5);
// 添加按钮文本
const text = this.add.text(
this.sys.game.config.width / 2,
startY + index * (buttonHeight + buttonSpacing),
scene.name,
{
fontSize: '18px',
fill: '#ffffff',
fontFamily: 'Arial'
}
).setOrigin(0.5, 0.5);
// 使按钮可交互
button.setInteractive({
useHandCursor: true
});
// 添加点击事件
button.on('pointerdown', () => {
this.scene.start(scene.key);
});
// 添加悬停效果
button.on('pointerover', () => {
button.setFillStyle(0x555555);
});
button.on('pointerout', () => {
button.setFillStyle(0x333333);
});
});
}
}
+1 -1
View File
@@ -94,7 +94,7 @@ export default class GameScene extends Phaser.Scene {
this.miniMap = new MiniMap(this, {
worldSize: worldSize, // 游戏世界大小
mapSize: 200, // 小地图显示大小
x: this.sys.game.config.width - mapSize - 10, // 小地图在屏幕上的x坐标
x: this.sys.game.config.width - 200 - 10, // 小地图在屏幕上的x坐标
y: 10 // 小地图在屏幕上的y坐标
});
}
+93
View File
@@ -0,0 +1,93 @@
// 测试场景1:相对移动(键盘控制)
import Unit from '../objects/Unit.js';
import { InputSystem } from '../systems/InputSystem.js';
import { SelectionSystem } from '../systems/SelectionSystem.js';
import { CommandSystem } from '../systems/CommandSystem.js';
export default class MovementTestScene1 extends Phaser.Scene {
constructor() {
super({ key: 'MovementTestScene1' });
}
create() {
// 创建玩家纹理
if (!this.textures.exists('player')) {
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#000000ff';
ctx.beginPath();
ctx.arc(16, 16, 14, 0, Math.PI * 2);
ctx.fill();
this.textures.addCanvas('player', canvas);
}
// 物理世界和相机边界
const worldSize = 1000;
this.physics.world.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.centerOn(0, 0);
// 初始化系统 - 只开启键盘移动,禁用框选
this.inputSystem = new InputSystem(this, {
keyboard: true,
mouse: false,
joystick: false,
selection: false
});
this.selectionSystem = new SelectionSystem(this);
this.commandSystem = new CommandSystem(this);
// 创建单位组
this.unitGroup = this.add.group();
// 创建一个测试单位
const unit = new Unit(this, 0, 0, 'player');
this.unitGroup.add(unit);
// 自动选中单位
this.events.emit('select-unit', unit);
// 添加碰撞检测
this.physics.add.collider(this.unitGroup, this.unitGroup);
// 添加场景说明文本
const titleText = this.add.text(10, 10, '测试场景1:相对移动(键盘控制)', {
fontSize: '20px',
fill: '#ffffff'
});
titleText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
titleText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const moveText = this.add.text(10, 40, '使用WASD或方向键移动单位', {
fontSize: '16px',
fill: '#ffffff'
});
moveText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
moveText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const spaceText = this.add.text(10, 70, '按空格键返回菜单', {
fontSize: '16px',
fill: '#ffffff'
});
spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
spaceText.setDepth(10); // 设置深度,确保显示在游戏元素上方
// 空格键切换场景
this.input.keyboard.on('keydown-SPACE', () => {
this.scene.start('BootScene');
});
}
update() {
this.inputSystem.update();
this.unitGroup.getChildren().forEach(unit => {
if (unit.update) {
unit.update();
}
unit.updateMovement();
});
}
}
+93
View File
@@ -0,0 +1,93 @@
// 测试场景2:目标点移动
import Unit from '../objects/Unit.js';
import { InputSystem } from '../systems/InputSystem.js';
import { SelectionSystem } from '../systems/SelectionSystem.js';
import { CommandSystem } from '../systems/CommandSystem.js';
export default class MovementTestScene2 extends Phaser.Scene {
constructor() {
super({ key: 'MovementTestScene2' });
}
create() {
// 创建玩家纹理
if (!this.textures.exists('player')) {
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#000000ff';
ctx.beginPath();
ctx.arc(16, 16, 14, 0, Math.PI * 2);
ctx.fill();
this.textures.addCanvas('player', canvas);
}
// 物理世界和相机边界
const worldSize = 1000;
this.physics.world.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.centerOn(0, 0);
// 初始化系统 - 只开启鼠标移动,禁用框选
this.inputSystem = new InputSystem(this, {
keyboard: false,
mouse: true,
joystick: false,
selection: false
});
this.selectionSystem = new SelectionSystem(this);
this.commandSystem = new CommandSystem(this);
// 创建单位组
this.unitGroup = this.add.group();
// 创建一个测试单位
const unit = new Unit(this, 0, 0, 'player');
this.unitGroup.add(unit);
// 自动选中单位
this.events.emit('select-unit', unit);
// 添加碰撞检测
this.physics.add.collider(this.unitGroup, this.unitGroup);
// 添加场景说明文本
const titleText = this.add.text(10, 10, '测试场景2:目标点移动', {
fontSize: '20px',
fill: '#ffffff'
});
titleText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
titleText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const moveText = this.add.text(10, 40, '点击鼠标右键或点击空地指定移动目标点', {
fontSize: '16px',
fill: '#ffffff'
});
moveText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
moveText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const spaceText = this.add.text(10, 70, '按空格键返回菜单', {
fontSize: '16px',
fill: '#ffffff'
});
spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
spaceText.setDepth(10); // 设置深度,确保显示在游戏元素上方
// 空格键切换场景
this.input.keyboard.on('keydown-SPACE', () => {
this.scene.start('BootScene');
});
}
update() {
this.inputSystem.update();
this.unitGroup.getChildren().forEach(unit => {
if (unit.update) {
unit.update();
}
unit.updateMovement();
});
}
}
+111
View File
@@ -0,0 +1,111 @@
// 测试场景3:单位选择与移动命令
import Unit from '../objects/Unit.js';
import { InputSystem } from '../systems/InputSystem.js';
import { SelectionSystem } from '../systems/SelectionSystem.js';
import { CommandSystem } from '../systems/CommandSystem.js';
export default class MovementTestScene3 extends Phaser.Scene {
constructor() {
super({ key: 'MovementTestScene3' });
}
create() {
// 创建玩家纹理
if (!this.textures.exists('player')) {
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#000000ff';
ctx.beginPath();
ctx.arc(16, 16, 14, 0, Math.PI * 2);
ctx.fill();
this.textures.addCanvas('player', canvas);
}
// 物理世界和相机边界
const worldSize = 1000;
this.physics.world.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.centerOn(0, 0);
// 初始化系统 - 开启鼠标选择、移动和框选功能
this.inputSystem = new InputSystem(this, {
keyboard: false,
mouse: true,
joystick: false,
selection: true
});
this.selectionSystem = new SelectionSystem(this);
this.commandSystem = new CommandSystem(this);
// 创建单位组
this.unitGroup = this.add.group();
// 创建多个测试单位
const unitCount = 5;
let firstUnit = null;
for (let i = 0; i < unitCount; i++) {
const x = -200 + i * 100;
const y = 0;
const unit = new Unit(this, x, y, 'player');
this.unitGroup.add(unit);
if (i === 0) {
firstUnit = unit;
}
}
// 自动选中第一个单位
if (firstUnit) {
this.events.emit('select-unit', firstUnit);
}
// 添加碰撞检测
this.physics.add.collider(this.unitGroup, this.unitGroup);
// 添加场景说明文本
const titleText = this.add.text(10, 10, '测试场景3:单位选择与移动命令', {
fontSize: '20px',
fill: '#ffffff'
});
titleText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
titleText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const selectText = this.add.text(10, 40, '左键点击单选单位,拖拽框选多个单位', {
fontSize: '16px',
fill: '#ffffff'
});
selectText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
selectText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const moveText = this.add.text(10, 70, '右键或点击空地对选中单位下达移动命令', {
fontSize: '16px',
fill: '#ffffff'
});
moveText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
moveText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const spaceText = this.add.text(10, 100, '按空格键返回菜单', {
fontSize: '16px',
fill: '#ffffff'
});
spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
spaceText.setDepth(10); // 设置深度,确保显示在游戏元素上方
// 空格键切换场景
this.input.keyboard.on('keydown-SPACE', () => {
this.scene.start('BootScene');
});
}
update() {
this.inputSystem.update();
this.unitGroup.getChildren().forEach(unit => {
if (unit.update) {
unit.update();
}
unit.updateMovement();
});
}
}
+99
View File
@@ -0,0 +1,99 @@
// 测试场景4:虚拟摇杆控制
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';
export default class MovementTestScene4 extends Phaser.Scene {
constructor() {
super({ key: 'MovementTestScene4' });
}
create() {
// 创建玩家纹理
if (!this.textures.exists('player')) {
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#000000ff';
ctx.beginPath();
ctx.arc(16, 16, 14, 0, Math.PI * 2);
ctx.fill();
this.textures.addCanvas('player', canvas);
}
// 物理世界和相机边界
const worldSize = 1000;
this.physics.world.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.centerOn(0, 0);
// 初始化系统 - 只开启虚拟摇杆移动,禁用框选
this.inputSystem = new InputSystem(this, {
keyboard: false,
mouse: false,
joystick: true,
selection: false
});
this.selectionSystem = new SelectionSystem(this);
this.commandSystem = new CommandSystem(this);
this.virtualJoystick = new VirtualJoystick(this, true);
// 创建单位组
this.unitGroup = this.add.group();
// 创建一个测试单位
const unit = new Unit(this, 0, 0, 'player');
this.unitGroup.add(unit);
// 自动选中单位
this.events.emit('select-unit', unit);
// 添加碰撞检测
this.physics.add.collider(this.unitGroup, this.unitGroup);
// 添加场景说明文本
const titleText = this.add.text(10, 10, '测试场景4:虚拟摇杆控制', {
fontSize: '20px',
fill: '#ffffff'
});
titleText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
titleText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const moveText = this.add.text(10, 40, '使用屏幕右下角的虚拟摇杆控制单位移动', {
fontSize: '16px',
fill: '#ffffff'
});
moveText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
moveText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const spaceText = this.add.text(10, 70, '按空格键返回菜单', {
fontSize: '16px',
fill: '#ffffff'
});
spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
spaceText.setDepth(10); // 设置深度,确保显示在游戏元素上方
// 空格键切换场景
this.input.keyboard.on('keydown-SPACE', () => {
this.scene.start('BootScene');
});
}
update() {
this.inputSystem.update();
this.unitGroup.getChildren().forEach(unit => {
if (unit.update) {
unit.update();
}
unit.updateMovement();
});
if (this.virtualJoystick) {
this.virtualJoystick.update();
}
}
}
+123
View File
@@ -0,0 +1,123 @@
// 测试场景5:移动更新逻辑
import Unit from '../objects/Unit.js';
import { InputSystem } from '../systems/InputSystem.js';
import { SelectionSystem } from '../systems/SelectionSystem.js';
import { CommandSystem } from '../systems/CommandSystem.js';
export default class MovementTestScene5 extends Phaser.Scene {
constructor() {
super({ key: 'MovementTestScene5' });
}
create() {
// 创建玩家纹理
if (!this.textures.exists('player')) {
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#000000ff';
ctx.beginPath();
ctx.arc(16, 16, 14, 0, Math.PI * 2);
ctx.fill();
this.textures.addCanvas('player', canvas);
}
// 物理世界和相机边界
const worldSize = 1000;
this.physics.world.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.centerOn(0, 0);
// 初始化系统 - 这个场景单位自动移动,禁用所有输入和框选
this.inputSystem = new InputSystem(this, {
keyboard: false,
mouse: false,
joystick: false,
selection: false
});
this.selectionSystem = new SelectionSystem(this);
this.commandSystem = new CommandSystem(this);
// 创建单位组
this.unitGroup = this.add.group();
// 创建多个测试单位,分布在不同位置
const unitPositions = [
{ x: -300, y: -300 },
{ x: 300, y: -300 },
{ x: -300, y: 300 },
{ x: 300, y: 300 }
];
// 自动选中第一个单位
let firstUnit = null;
unitPositions.forEach(pos => {
const unit = new Unit(this, pos.x, pos.y, 'player');
// 为每个单位设置不同的目标点,观察它们的移动
const targetX = Math.random() * 400 - 200;
const targetY = Math.random() * 400 - 200;
unit.setTarget(targetX, targetY);
this.unitGroup.add(unit);
// 保存第一个单位的引用
if (!firstUnit) {
firstUnit = unit;
}
});
// 自动选中第一个单位
if (firstUnit) {
this.events.emit('select-unit', firstUnit);
}
// 添加碰撞检测
this.physics.add.collider(this.unitGroup, this.unitGroup);
// 添加场景说明文本
const titleText = this.add.text(10, 10, '测试场景5:移动更新逻辑', {
fontSize: '20px',
fill: '#ffffff'
});
titleText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
titleText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const autoMoveText = this.add.text(10, 40, '观察单位自动向随机目标点移动', {
fontSize: '16px',
fill: '#ffffff'
});
autoMoveText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
autoMoveText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const pathText = this.add.text(10, 70, '单位会自动计算路径并在到达后停止', {
fontSize: '16px',
fill: '#ffffff'
});
pathText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
pathText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const spaceText = this.add.text(10, 100, '按空格键返回菜单', {
fontSize: '16px',
fill: '#ffffff'
});
spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
spaceText.setDepth(10); // 设置深度,确保显示在游戏元素上方
// 空格键切换场景
this.input.keyboard.on('keydown-SPACE', () => {
this.scene.start('BootScene');
});
}
update() {
this.inputSystem.update();
this.unitGroup.getChildren().forEach(unit => {
if (unit.update) {
unit.update();
}
unit.updateMovement();
});
}
}
+143
View File
@@ -0,0 +1,143 @@
// 测试场景6:小地图测试
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 { MiniMap } from '../ui/MiniMap.js';
export default class MovementTestScene6 extends Phaser.Scene {
constructor() {
super({ key: 'MovementTestScene6' });
}
create() {
// 创建玩家纹理
if (!this.textures.exists('player')) {
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#000000ff';
ctx.beginPath();
ctx.arc(16, 16, 14, 0, Math.PI * 2);
ctx.fill();
this.textures.addCanvas('player', canvas);
}
// 物理世界和相机边界
const worldSize = 2000;
this.physics.world.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
this.cameras.main.centerOn(0, 0);
// 初始化系统 - 开启鼠标移动和框选
this.inputSystem = new InputSystem(this, {
keyboard: true,
mouse: true,
joystick: false,
selection: true
});
this.selectionSystem = new SelectionSystem(this);
this.commandSystem = new CommandSystem(this);
// 创建单位组
this.unitGroup = this.add.group();
// 创建多个测试单位,分布在不同位置
for (let i = 0; i < 10; i++) {
const x = Math.random() * 2000 - 1000;
const y = Math.random() * 2000 - 1000;
const unit = new Unit(this, x, y, 'player');
this.unitGroup.add(unit);
// 自动选中第一个单位
if (i === 0) {
this.events.emit('select-unit', unit);
}
}
// 添加碰撞检测
this.physics.add.collider(this.unitGroup, this.unitGroup);
// 创建小地图
this.miniMap = new MiniMap(this, {
worldSize: worldSize,
mapSize: 200,
x: this.sys.game.config.width - 210,
y: 10
});
// 添加场景说明文本
const titleText = this.add.text(10, 10, '测试场景6:小地图测试', {
fontSize: '20px',
fill: '#ffffff'
});
titleText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
titleText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const miniMapText = this.add.text(10, 40, '使用鼠标拖拽小地图或点击小地图快速移动视角', {
fontSize: '16px',
fill: '#ffffff'
});
miniMapText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
miniMapText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const controlsText = this.add.text(10, 70, 'WASD或方向键移动视角,鼠标右键移动单位', {
fontSize: '16px',
fill: '#ffffff'
});
controlsText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
controlsText.setDepth(10); // 设置深度,确保显示在游戏元素上方
const spaceText = this.add.text(10, 100, '按空格键返回菜单', {
fontSize: '16px',
fill: '#ffffff'
});
spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
spaceText.setDepth(10); // 设置深度,确保显示在游戏元素上方
// 空格键切换场景
this.input.keyboard.on('keydown-SPACE', () => {
this.scene.start('BootScene');
});
// 添加相机移动控制
this.cameraSpeed = 2;
this.cameraKeys = this.input.keyboard.addKeys({
up: Phaser.Input.Keyboard.KeyCodes.W,
down: Phaser.Input.Keyboard.KeyCodes.S,
left: Phaser.Input.Keyboard.KeyCodes.A,
right: Phaser.Input.Keyboard.KeyCodes.D
});
}
update() {
this.inputSystem.update();
// 更新相机移动
if (this.cameraKeys.left.isDown) {
this.cameras.main.scrollX -= this.cameraSpeed;
}
if (this.cameraKeys.right.isDown) {
this.cameras.main.scrollX += this.cameraSpeed;
}
if (this.cameraKeys.up.isDown) {
this.cameras.main.scrollY -= this.cameraSpeed;
}
if (this.cameraKeys.down.isDown) {
this.cameras.main.scrollY += this.cameraSpeed;
}
this.unitGroup.getChildren().forEach(unit => {
if (unit.update) {
unit.update();
}
unit.updateMovement();
});
// 更新小地图显示
if (this.miniMap) {
this.miniMap.update();
}
}
}
+8 -2
View File
@@ -26,7 +26,10 @@ export class CommandSystem {
// 遍历所有目标单位,调用它们的moveRelative方法进行相对移动
targets.forEach(unit => {
unit.moveRelative(dir.x, dir.y);
// 确保unit是有效的Unit实例
if (unit && typeof unit.moveRelative === 'function') {
unit.moveRelative(dir.x, dir.y);
}
});
}
@@ -37,7 +40,10 @@ export class CommandSystem {
// 遍历所有目标单位,调用它们的setTarget方法设置移动目标点
targets.forEach(unit => {
unit.setTarget(x, y); // ← 使用单位的新接口方法
// 确保unit是有效的Unit实例
if (unit && typeof unit.setTarget === 'function') {
unit.setTarget(x, y); // ← 使用单位的新接口方法
}
});
}
+16
View File
@@ -16,6 +16,11 @@ export let activeUnit = null;
// 初始值为空数组,表示没有选中任何单位
export let selectedUnits = [];
// 相机锁定状态
// 用于控制相机是否锁定到选中单位
// 初始值为false,表示未锁定
export let cameraLocked = false;
// 设置当前激活单位的函数
// @param {Object} unit - 要设置为激活状态的单位对象
export function setActiveUnit(unit) {
@@ -41,4 +46,15 @@ export function clearSelection() {
activeUnit = null;
// 将选中的单位列表重置为空数组(取消所有选择)
selectedUnits = [];
// 取消相机锁定
setCameraLocked(false);
}
// 设置相机锁定状态的函数
export function setCameraLocked(locked) {
cameraLocked = locked;
// 将锁定状态暴露到window对象上,以便InputSystem访问
if (typeof window !== 'undefined') {
window.cameraLocked = locked;
}
}
+145 -38
View File
@@ -7,7 +7,8 @@ import { clearSelection, setActiveUnit } from './ControlMode.js';
export class InputSystem {
// 构造函数:初始化输入系统
// @param {Object} scene - 游戏场景实例
constructor(scene) {
// @param {Object} config - 输入配置选项
constructor(scene, config = {}) {
// 保存场景引用,用于访问游戏世界和发送事件
this.scene = scene;
@@ -15,6 +16,14 @@ export class InputSystem {
// 初始值为null,表示当前没有进行框选操作
this.selectionStart = null;
// 移动方式开关配置
this.enabledModes = {
keyboard: config.keyboard !== false, // 键盘控制
mouse: config.mouse !== false, // 鼠标点击移动
joystick: config.joystick !== false, // 虚拟摇杆控制
selection: config.selection !== false // 框选功能
};
// 初始化键盘输入
// 获取Phaser的键盘输入管理器
const kb = scene.input.keyboard;
@@ -53,28 +62,31 @@ export class InputSystem {
// 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;
// 只有在键盘模式启用时才处理键盘输入
if (this.enabledModes.keyboard) {
// 创建一个方向对象,用于存储移动方向
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;
// 无论是否有按键按下,都发送移动事件
// 当dir.x和dir.y都为0时,单位会停止移动
this.scene.events.emit('move-relative', dir);
// 无论是否有按键按下,都发送移动事件
// 当dir.x和dir.y都为0时,单位会停止移动
this.scene.events.emit('move-relative', dir);
}
}
// 处理鼠标/触摸按下事件
// @param {Object} pointer - 指针事件对象,包含鼠标/触摸的位置等信息
onPointerDown(pointer) {
// 检查鼠标是否在小地图或摇杆上,如果是则不处理框选
if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer)) {
// 检查鼠标是否在小地图、摇杆或侧边栏上,如果是则不处理
if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer) || this.isMouseOnSidebar(pointer)) {
return;
}
@@ -98,34 +110,60 @@ export class InputSystem {
}
});
// 检查是否点中了某个物品
let clickedItem = null;
// 遍历场景中的所有物品
if (this.scene.itemManager && this.scene.itemManager.getActiveItems) {
this.scene.itemManager.getActiveItems().forEach(item => {
// 获取物品的边界矩形
const bounds = item.getBounds();
// 检查鼠标世界坐标是否在物品的边界内
if (worldX >= bounds.x && worldX <= bounds.right &&
worldY >= bounds.y && worldY <= bounds.bottom) {
// 如果点中了物品,存储该物品
clickedItem = item;
}
});
}
if (clickedUnit) {
// 如果点中了单位,发送选择单位事件
this.scene.events.emit('select-unit', clickedUnit);
} else {
// 如果没有点中单位,开始框选或移动指令
// 存储点击位置,用于判断是否是拖动操作
this.dragStart = { x: worldX, y: worldY, clickedUnit: clickedUnit };
// 不设置selectionStart,避免触发框选
this.selectionStart = null;
} else if (clickedItem) {
// 如果点中了物品,不执行后续操作,避免触发移动
this.selectionStart = null;
this.dragStart = null;
} else if (this.enabledModes.selection && !this.isCameraLocked()) {
// 如果没有点中单位,且框选功能启用,且相机未锁定,开始框选操作
// 存储框选开始时的世界坐标
this.selectionStart = { x: worldX, y: worldY };
// 发送选择开始事件
this.scene.events.emit('selection-start', this.selectionStart);
// 没有点中单位,清除dragStart
this.dragStart = null;
}
} else if (pointer.rightButtonDown()) {
// 如果是右键按下,发送移动到指定点的事件
} else if (pointer.rightButtonDown() && this.enabledModes.mouse) {
// 如果是右键按下且鼠标模式启用,发送移动到指定点的事件
const worldX = pointer.worldX;
const worldY = pointer.worldY;
this.scene.events.emit('move-to-point', worldX, worldY);
// 右键点击,清除dragStart
this.dragStart = null;
}
}
// 处理鼠标/触摸移动事件
// @param {Object} pointer - 指针事件对象
onPointerMove(pointer) {
// 检查鼠标是否在小地图或摇杆上,如果是则不处理框选
if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer)) {
// 检查鼠标是否在小地图、摇杆或侧边栏上,如果是则不处理
if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer) || this.isMouseOnSidebar(pointer)) {
return;
}
// 如果当前正在进行框选操作
if (this.selectionStart) {
// 如果当前正在进行框选操作且框选功能启用
if (this.selectionStart && this.enabledModes.selection) {
// 获取当前鼠标在游戏世界中的坐标
const currentWorldX = pointer.worldX;
const currentWorldY = pointer.worldY;
@@ -143,23 +181,48 @@ export class InputSystem {
Math.abs(currentWorldY - this.selectionStart.y)
);
// 发送选择更新事件,更新框选区域的显示
this.scene.events.emit('selection-update', rect);
// 只有当框选区域足够大时才发送选择更新事件
if (rect.width > 5 || rect.height > 5) {
// 发送选择更新事件,更新框选区域的显示
this.scene.events.emit('selection-start', this.selectionStart);
this.scene.events.emit('selection-update', rect);
}
}
}
// 处理鼠标/触摸释放事件
// @param {Object} pointer - 指针事件对象
onPointerUp(pointer) {
// 检查鼠标是否在小地图或摇杆上,如果是则不处理框选
if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer)) {
// 重置框选起始点
// 检查鼠标是否在小地图、摇杆或侧边栏上,如果是则不处理
if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer) || this.isMouseOnSidebar(pointer)) {
// 重置框选起始点和拖动状态
this.selectionStart = null;
this.dragStart = null;
return;
}
// 如果当前正在进行框选操作
if (this.selectionStart) {
// 处理拖动单位的逻辑
if (this.dragStart) {
// 获取当前鼠标在游戏世界中的坐标
const currentWorldX = pointer.worldX;
const currentWorldY = pointer.worldY;
// 计算拖动距离
const dx = currentWorldX - this.dragStart.x;
const dy = currentWorldY - this.dragStart.y;
const dragDistance = Math.sqrt(dx * dx + dy * dy);
// 如果拖动距离超过阈值(10像素),视为拖动操作
if (dragDistance > 10 && this.enabledModes.mouse) {
// 发送移动到指定点的事件,让单位移动到释放位置
this.scene.events.emit('move-to-point', currentWorldX, currentWorldY);
}
// 重置拖动状态
this.dragStart = null;
}
// 如果当前正在进行框选操作且框选功能启用
else if (this.selectionStart && this.enabledModes.selection) {
// 获取当前鼠标在游戏世界中的坐标
const currentWorldX = pointer.worldX;
const currentWorldY = pointer.worldY;
@@ -173,8 +236,8 @@ export class InputSystem {
);
// 如果框选区域太小(宽度和高度都小于10像素),视为点击地面移动
if (rect.width < 10 && rect.height < 10) {
// 发送移动到指定点的事件
if (rect.width < 10 && rect.height < 10 && this.enabledModes.mouse) {
// 发送移动到指定点的事件(仅在鼠标模式启用时)
this.scene.events.emit('move-to-point', pointer.worldX, pointer.worldY);
} else {
// 否则执行框选操作
@@ -203,11 +266,12 @@ export class InputSystem {
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; // 小地图下边界
// 小地图下边界,包括锁定按钮(按钮高度25,边距5)
const mapBottom = miniMap.y + miniMap.mapSize + 30;
// 检查鼠标坐标是否在小地图边界内
return mouseX >= mapLeft && mouseX <= mapRight &&
@@ -242,4 +306,47 @@ export class InputSystem {
return mouseX >= joystickLeft && mouseX <= joystickRight &&
mouseY >= joystickTop && mouseY <= joystickBottom;
}
/**
* 检查相机是否被锁定到某个角色
* @returns {boolean} - 如果相机被锁定返回true,否则返回false
*/
isCameraLocked() {
// 检查ControlMode模块是否存在且cameraLocked属性已定义
if (typeof window !== 'undefined' && window.cameraLocked !== undefined) {
return window.cameraLocked;
}
// 检查场景中是否有小地图且处于锁定状态
if (this.scene.miniMap && this.scene.miniMap.isLocked) {
return this.scene.miniMap.isLocked;
}
return false;
}
/**
* 检查鼠标是否在侧边栏上
* @param {Phaser.Input.Pointer} pointer - 指针事件对象
* @returns {boolean} - 如果鼠标在侧边栏上返回true,否则返回false
*/
isMouseOnSidebar(pointer) {
// 检查场景中是否有侧边栏
if (!this.scene.sidebar) {
return false;
}
// 获取侧边栏实例
const sidebar = this.scene.sidebar;
// 获取鼠标在屏幕上的坐标
const mouseX = pointer.x;
const mouseY = pointer.y;
// 获取侧边栏的边界
const bounds = sidebar.getBounds();
// 检查鼠标坐标是否在侧边栏边界内
return mouseX >= bounds.x && mouseX <= bounds.right &&
mouseY >= bounds.y && mouseY <= bounds.bottom;
}
}
+354
View File
@@ -0,0 +1,354 @@
// 移动逻辑测试场景
// 本文件将当前移动逻辑拆分为不同的测试场景
// 测试场景1: 相对移动(键盘控制)
// 测试内容:
// 1. 键盘方向键输入检测
// 2. 方向向量归一化
// 3. 移动速度控制
// 4. 停止移动逻辑
function testRelativeMovement() {
console.log("=== 测试场景1: 相对移动(键盘控制) ===");
// 模拟键盘输入
const testCases = [
{ dir: { x: 1, y: 0 }, description: "向右移动" },
{ dir: { x: 0, y: 1 }, description: "向下移动" },
{ dir: { x: -1, y: 0 }, description: "向左移动" },
{ dir: { x: 0, y: -1 }, description: "向上移动" },
{ dir: { x: 1, y: 1 }, description: "向右下对角线移动" },
{ dir: { x: 0, y: 0 }, description: "停止移动" }
];
testCases.forEach(testCase => {
console.log(`测试: ${testCase.description}`);
console.log(`输入方向: (${testCase.dir.x}, ${testCase.dir.y})`);
// 计算归一化向量
const magnitude = Math.sqrt(testCase.dir.x * testCase.dir.x + testCase.dir.y * testCase.dir.y);
let normalizedDir = { x: 0, y: 0 };
if (magnitude > 0) {
normalizedDir = {
x: testCase.dir.x / magnitude,
y: testCase.dir.y / magnitude
};
}
console.log(`归一化方向: (${normalizedDir.x.toFixed(2)}, ${normalizedDir.y.toFixed(2)})`);
// 计算速度向量
const speed = 150;
const velocity = {
x: normalizedDir.x * speed,
y: normalizedDir.y * speed
};
console.log(`速度向量: (${velocity.x.toFixed(2)}, ${velocity.y.toFixed(2)})`);
console.log("------------------------");
});
}
// 测试场景2: 目标点移动
// 测试内容:
// 1. 目标点设置
// 2. 世界边界限制
// 3. 移动路径计算
// 4. 到达目标点检测
function testMoveToPoint() {
console.log("=== 测试场景2: 目标点移动 ===");
// 模拟世界边界
const worldBounds = { x: 0, y: 0, width: 1000, height: 800 };
// 测试用例
const testCases = [
{
unitPos: { x: 100, y: 100 },
targetPos: { x: 500, y: 400 },
description: "正常范围内移动"
},
{
unitPos: { x: 100, y: 100 },
targetPos: { x: 1200, y: 900 },
description: "超出世界边界的目标点"
},
{
unitPos: { x: 500, y: 400 },
targetPos: { x: 505, y: 405 },
description: "接近当前位置的目标点"
}
];
testCases.forEach(testCase => {
console.log(`测试: ${testCase.description}`);
console.log(`单位位置: (${testCase.unitPos.x}, ${testCase.unitPos.y})`);
console.log(`目标位置: (${testCase.targetPos.x}, ${testCase.targetPos.y})`);
// 应用世界边界限制
const clampedTarget = {
x: Math.max(worldBounds.x, Math.min(worldBounds.width, testCase.targetPos.x)),
y: Math.max(worldBounds.y, Math.min(worldBounds.height, testCase.targetPos.y))
};
console.log(`限制后目标: (${clampedTarget.x}, ${clampedTarget.y})`);
// 计算距离和方向
const dx = clampedTarget.x - testCase.unitPos.x;
const dy = clampedTarget.y - testCase.unitPos.y;
const distance = Math.sqrt(dx * dx + dy * dy);
console.log(`距离: ${distance.toFixed(2)}`);
// 检查是否到达目标
const arrived = distance < 5;
console.log(`是否到达目标: ${arrived}`);
if (!arrived) {
// 计算移动方向和速度
const angle = Math.atan2(dy, dx);
const speed = 150;
const velocity = {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed
};
console.log(`移动速度: (${velocity.x.toFixed(2)}, ${velocity.y.toFixed(2)})`);
}
console.log("------------------------");
});
}
// 测试场景3: 单位选择与移动命令
// 测试内容:
// 1. 单个单位选择
// 2. 多个单位选择
// 3. 移动命令分发
function testSelectionAndCommand() {
console.log("=== 测试场景3: 单位选择与移动命令 ===");
// 模拟单位和选择状态
const mockUnits = [
{ id: 1, name: "单位1", moveRelative: () => {}, setTarget: () => {} },
{ id: 2, name: "单位2", moveRelative: () => {}, setTarget: () => {} },
{ id: 3, name: "单位3", moveRelative: () => {}, setTarget: () => {} }
];
// 测试用例
const testCases = [
{
selectedUnits: [],
activeUnit: mockUnits[0],
command: "move-relative",
dir: { x: 1, y: 0 },
description: "无选中单位,使用激活单位"
},
{
selectedUnits: [mockUnits[0], mockUnits[1]],
activeUnit: mockUnits[0],
command: "move-to-point",
target: { x: 500, y: 400 },
description: "多个选中单位,执行目标点移动"
},
{
selectedUnits: [mockUnits[2]],
activeUnit: mockUnits[0],
command: "move-relative",
dir: { x: 0, y: -1 },
description: "单个选中单位,执行相对移动"
}
];
testCases.forEach(testCase => {
console.log(`测试: ${testCase.description}`);
console.log(`激活单位: ${testCase.activeUnit.name}`);
console.log(`选中单位: ${testCase.selectedUnits.map(u => u.name).join(", ")}`);
// 确定命令目标
const targets = testCase.selectedUnits.length > 0 ? testCase.selectedUnits : [testCase.activeUnit];
console.log(`命令目标: ${targets.map(u => u.name).join(", ")}`);
if (testCase.command === "move-relative") {
console.log(`执行相对移动: (${testCase.dir.x}, ${testCase.dir.y})`);
} else if (testCase.command === "move-to-point") {
console.log(`执行目标点移动: (${testCase.target.x}, ${testCase.target.y})`);
}
console.log("------------------------");
});
}
// 测试场景4: 虚拟摇杆控制
// 测试内容:
// 1. 摇杆激活检测
// 2. 摇杆方向计算
// 3. 归一化方向输出
// 4. 停止移动处理
function testVirtualJoystick() {
console.log("=== 测试场景4: 虚拟摇杆控制 ===");
// 模拟摇杆配置
const joystickConfig = { centerX: 100, centerY: 100, radius: 50 };
// 测试用例
const testCases = [
{
pointerPos: { x: 120, y: 100 },
description: "向右轻微拖动"
},
{
pointerPos: { x: 160, y: 100 },
description: "向右拖动超出摇杆范围"
},
{
pointerPos: { x: 135, y: 135 },
description: "向右下对角线拖动"
},
{
pointerPos: { x: 100, y: 100 },
description: "摇杆中心位置(停止)"
}
];
testCases.forEach(testCase => {
console.log(`测试: ${testCase.description}`);
console.log(`摇杆中心: (${joystickConfig.centerX}, ${joystickConfig.centerY})`);
console.log(`指针位置: (${testCase.pointerPos.x}, ${testCase.pointerPos.y})`);
// 计算方向和距离
const dx = testCase.pointerPos.x - joystickConfig.centerX;
const dy = testCase.pointerPos.y - joystickConfig.centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
console.log(`距离: ${distance.toFixed(2)}`);
// 限制在摇杆范围内
let limitedDx = dx;
let limitedDy = dy;
if (distance > joystickConfig.radius) {
const angle = Math.atan2(dy, dx);
limitedDx = Math.cos(angle) * joystickConfig.radius;
limitedDy = Math.sin(angle) * joystickConfig.radius;
console.log(`限制后偏移: (${limitedDx.toFixed(2)}, ${limitedDy.toFixed(2)})`);
}
// 计算归一化方向
const dirX = limitedDx / joystickConfig.radius;
const dirY = limitedDy / joystickConfig.radius;
console.log(`归一化方向: (${dirX.toFixed(2)}, ${dirY.toFixed(2)})`);
console.log("------------------------");
});
}
// 测试场景5: 移动更新逻辑
// 测试内容:
// 1. 每帧移动更新
// 2. 位置更新计算
// 3. 目标到达检测
function testMovementUpdate() {
console.log("=== 测试场景5: 移动更新逻辑 ===");
// 模拟单位和时间增量
const mockUnit = {
x: 100,
y: 100,
target: { x: 500, y: 400 },
setVelocity: (vx, vy) => { mockUnit.velocity = { x: vx, y: vy }; },
setPosition: (x, y) => { mockUnit.x = x; mockUnit.y = y; }
};
const speed = 150;
const deltaTime = 1/60; // 60fps
console.log("初始状态:");
console.log(`单位位置: (${mockUnit.x}, ${mockUnit.y})`);
console.log(`目标位置: (${mockUnit.target.x}, ${mockUnit.target.y})`);
// 模拟几帧的移动更新
for (let i = 0; i < 5; i++) {
console.log(`\n第${i+1}帧更新:`);
const dx = mockUnit.target.x - mockUnit.x;
const dy = mockUnit.target.y - mockUnit.y;
const distance = Math.sqrt(dx * dx + dy * dy);
console.log(`距离目标: ${distance.toFixed(2)}`);
if (distance < 5) {
// 到达目标
mockUnit.setPosition(mockUnit.target.x, mockUnit.target.y);
mockUnit.setVelocity(0, 0);
mockUnit.target = null;
console.log("到达目标,停止移动");
break;
} else {
// 继续移动
const angle = Math.atan2(dy, dx);
const velocity = {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed
};
mockUnit.setVelocity(velocity.x, velocity.y);
// 更新位置
mockUnit.x += velocity.x * deltaTime;
mockUnit.y += velocity.y * deltaTime;
console.log(`速度: (${velocity.x.toFixed(2)}, ${velocity.y.toFixed(2)})`);
console.log(`新位置: (${mockUnit.x.toFixed(2)}, ${mockUnit.y.toFixed(2)})`);
}
}
console.log("\n最终状态:");
console.log(`单位位置: (${mockUnit.x}, ${mockUnit.y})`);
console.log(`目标状态: ${mockUnit.target ? "有目标" : "无目标"}`);
}
// 运行所有测试场景
function runAllTests() {
console.log("开始运行移动逻辑测试场景\n");
testRelativeMovement();
console.log("\n" + "=".repeat(50) + "\n");
testMoveToPoint();
console.log("\n" + "=".repeat(50) + "\n");
testSelectionAndCommand();
console.log("\n" + "=".repeat(50) + "\n");
testVirtualJoystick();
console.log("\n" + "=".repeat(50) + "\n");
testMovementUpdate();
console.log("\n" + "=".repeat(50));
console.log("所有测试场景运行完成");
}
// 导出测试函数
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
testRelativeMovement,
testMoveToPoint,
testSelectionAndCommand,
testVirtualJoystick,
testMovementUpdate,
runAllTests
};
// Node.js环境下自动运行测试
runAllTests();
} else {
// 浏览器环境下自动运行测试
window.movementTests = {
runAllTests,
testRelativeMovement,
testMoveToPoint,
testSelectionAndCommand,
testVirtualJoystick,
testMovementUpdate
};
// 浏览器环境下自动运行测试
runAllTests();
}
+10 -11
View File
@@ -1,6 +1,6 @@
// ui/VirtualJoystick.js
export class VirtualJoystick {
constructor(scene) {
constructor(scene, enabled = true) {
this.scene = scene;
this.isActive = false;
this.base = null;
@@ -9,16 +9,14 @@ export class VirtualJoystick {
this.centerX = 0;
this.centerY = 0;
this.radius = 50;
this.enabled = enabled;
// 仅在触屏设备显示
// if ('ontouchstart' in window) {
// this.create();
// this.bindEvents();
// }
// 在所有设备上都显示摇杆
this.create();
this.bindEvents();
// 仅在启用时创建摇杆
if (this.enabled) {
// 在所有设备上都显示摇杆
this.create();
this.bindEvents();
}
}
create() {
@@ -84,7 +82,8 @@ export class VirtualJoystick {
* 更新方法,用于在游戏循环中持续发送移动指令
*/
update() {
if (this.isActive && this.pointer) {
// 仅在启用时更新
if (this.enabled && this.isActive && this.pointer) {
const dx = this.pointer.x - this.centerX;
const dy = this.pointer.y - this.centerY;