mirror of
http://junepve.top:3000/chicrobot/CINC.git
synced 2026-10-30 00:41:29 +08:00
refactor(scenes) 将测试场景移动到测试场景testScenes目录下
This commit is contained in:
1 parent
dabe9ed2ef
commit
f6874d1ee9
14 files changed
+63
-63
No files matched your search
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
// 角色动画测试场景
|
||||
// 使用Player0和Player1图片作为移动帧
|
||||
|
||||
// 导入系统和UI组件
|
||||
import { InputSystem } from '../../systems/InputSystem.js';
|
||||
import { SelectionSystem } from '../../systems/SelectionSystem.js';
|
||||
import { CommandSystem } from '../../systems/CommandSystem.js';
|
||||
|
||||
// 导入Unit类
|
||||
import Unit from '../../objects/Unit.js';
|
||||
|
||||
export default class CharacterAnimationTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'CharacterAnimationTestScene' });
|
||||
this.tileSize = 16; // 瓦片大小
|
||||
this.player = null; // 玩家角色
|
||||
this.inputSystem = null; // 输入系统
|
||||
this.selectionSystem = null; // 选择系统
|
||||
this.commandSystem = null; // 命令系统
|
||||
this.keys = null; // 键盘按键
|
||||
}
|
||||
|
||||
preload() {
|
||||
// 加载玩家瓦片图片
|
||||
console.log('加载玩家瓦片图片...');
|
||||
|
||||
// 加载Player0的动画帧(假设tile_0000.png到tile_0007.png是Player0的帧)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
this.load.image(`player0-${i}`, `assets/PNG/Players/Tiles/tile_${String(i).padStart(4, '0')}.png`);
|
||||
}
|
||||
|
||||
// 加载Player1的动画帧(假设tile_0008.png到tile_0015.png是Player1的帧)
|
||||
for (let i = 8; i < 16; i++) {
|
||||
this.load.image(`player1-${i-8}`, `assets/PNG/Players/Tiles/tile_${String(i).padStart(4, '0')}.png`);
|
||||
}
|
||||
|
||||
console.log('玩家瓦片图片加载完成');
|
||||
}
|
||||
|
||||
create() {
|
||||
// 物理世界和相机边界
|
||||
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.cameras.main.setBackgroundColor('#222222');
|
||||
|
||||
// 创建地面网格
|
||||
this.createGrid();
|
||||
|
||||
// 创建角色动画
|
||||
this.createCharacterAnimations();
|
||||
|
||||
// 初始化系统 - 开启鼠标移动和选择
|
||||
this.inputSystem = new InputSystem(this, {
|
||||
keyboard: true,
|
||||
mouse: true,
|
||||
selection: true
|
||||
});
|
||||
this.selectionSystem = new SelectionSystem(this);
|
||||
this.commandSystem = new CommandSystem(this);
|
||||
|
||||
// 创建单位组
|
||||
this.unitGroup = this.add.group();
|
||||
|
||||
// 创建玩家角色
|
||||
this.createPlayer();
|
||||
|
||||
// 设置输入控制
|
||||
this.setupInput();
|
||||
|
||||
// 添加碰撞检测
|
||||
this.physics.add.collider(this.unitGroup, this.unitGroup);
|
||||
|
||||
// 显示提示信息
|
||||
this.showInstructions();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建地面网格
|
||||
*/
|
||||
createGrid() {
|
||||
// 创建网格线
|
||||
const gridGraphics = this.add.graphics();
|
||||
gridGraphics.lineStyle(1, 0x444444, 0.5);
|
||||
|
||||
// 绘制垂直线
|
||||
for (let x = 0; x <= 2000; x += this.tileSize) {
|
||||
gridGraphics.moveTo(x, 0);
|
||||
gridGraphics.lineTo(x, 2000);
|
||||
}
|
||||
|
||||
// 绘制水平线
|
||||
for (let y = 0; y <= 2000; y += this.tileSize) {
|
||||
gridGraphics.moveTo(0, y);
|
||||
gridGraphics.lineTo(2000, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建角色动画
|
||||
*/
|
||||
createCharacterAnimations() {
|
||||
console.log('创建角色动画...');
|
||||
|
||||
// 创建Player0的移动动画
|
||||
this.anims.create({
|
||||
key: 'player0-move',
|
||||
frames: [
|
||||
{ key: 'player0-0' },
|
||||
{ key: 'player0-1' },
|
||||
{ key: 'player0-2' },
|
||||
{ key: 'player0-3' },
|
||||
{ key: 'player0-4' },
|
||||
{ key: 'player0-5' },
|
||||
{ key: 'player0-6' },
|
||||
{ key: 'player0-7' }
|
||||
],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
});
|
||||
|
||||
// 创建Player1的移动动画
|
||||
this.anims.create({
|
||||
key: 'player1-move',
|
||||
frames: [
|
||||
{ key: 'player1-0' },
|
||||
{ key: 'player1-1' },
|
||||
{ key: 'player1-2' },
|
||||
{ key: 'player1-3' },
|
||||
{ key: 'player1-4' },
|
||||
{ key: 'player1-5' },
|
||||
{ key: 'player1-6' },
|
||||
{ key: 'player1-7' }
|
||||
],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
});
|
||||
|
||||
console.log('角色动画创建完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建玩家角色
|
||||
*/
|
||||
createPlayer() {
|
||||
console.log('创建玩家角色...');
|
||||
|
||||
// 创建玩家角色使用Unit类
|
||||
this.player = new Unit(this, 0, 0, 'player0-0');
|
||||
this.player.setScale(2); // 放大角色
|
||||
this.player.setBounce(0.2); // 反弹效果
|
||||
|
||||
// 自动选中玩家
|
||||
this.events.emit('select-unit', this.player);
|
||||
|
||||
// 将玩家添加到单位组
|
||||
this.unitGroup.add(this.player);
|
||||
|
||||
console.log('玩家角色创建完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入控制
|
||||
*/
|
||||
setupInput() {
|
||||
// 只保留必要的按键监听,键盘移动由InputSystem处理
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
|
||||
// 切换角色按钮
|
||||
this.createButton(this.sys.game.config.width - 200, 100, '切换角色', () => {
|
||||
this.switchCharacter();
|
||||
});
|
||||
|
||||
// 返回启动场景按钮
|
||||
this.createButton(this.sys.game.config.width - 200, 150, '返回启动场景', () => {
|
||||
this.scene.start('BootScene');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 创建按钮
|
||||
*/
|
||||
createButton(x, y, text, callback) {
|
||||
// 按钮背景
|
||||
const button = this.add.rectangle(x, y, 180, 40, 0x333333);
|
||||
button.setOrigin(0.5, 0.5);
|
||||
button.setScrollFactor(0, 0);
|
||||
button.setDepth(10);
|
||||
button.setInteractive({ useHandCursor: true });
|
||||
|
||||
// 按钮文本
|
||||
const buttonText = this.add.text(x - 70, y - 15, text, {
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
buttonText.setScrollFactor(0, 0);
|
||||
buttonText.setDepth(11);
|
||||
|
||||
// 按钮事件
|
||||
button.on('pointerdown', callback);
|
||||
|
||||
button.on('pointerover', () => {
|
||||
button.setFillStyle(0x555555);
|
||||
});
|
||||
|
||||
button.on('pointerout', () => {
|
||||
button.setFillStyle(0x333333);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换角色
|
||||
*/
|
||||
switchCharacter() {
|
||||
// 根据当前使用的纹理切换角色
|
||||
if (this.player.anims.currentAnim && this.player.anims.currentAnim.key === 'player0-move') {
|
||||
// 切换到Player1
|
||||
this.player.play('player1-move');
|
||||
console.log('切换到Player1');
|
||||
} else {
|
||||
// 切换到Player0
|
||||
this.player.play('player0-move');
|
||||
console.log('切换到Player0');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提示信息
|
||||
*/
|
||||
showInstructions() {
|
||||
// 标题
|
||||
this.add.text(20, 20, '角色动画测试场景', {
|
||||
fontSize: '20px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
}).setScrollFactor(0, 0).setDepth(11);
|
||||
|
||||
// 操作提示
|
||||
const instructions = [
|
||||
'WASD或方向键移动角色',
|
||||
'角色移动时播放动画',
|
||||
'点击"切换角色"按钮切换Player0和Player1',
|
||||
'角色动画使用Player0和Player1的图片作为移动帧',
|
||||
'按空格键返回启动场景'
|
||||
];
|
||||
|
||||
for (let i = 0; i < instructions.length; i++) {
|
||||
this.add.text(20, 50 + i * 25, instructions[i], {
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}).setScrollFactor(0, 0).setDepth(11);
|
||||
}
|
||||
|
||||
// 显示当前角色
|
||||
this.currentCharacterText = this.add.text(20, 200, '当前角色: Player0', {
|
||||
fontSize: '16px',
|
||||
fill: '#00ff00',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
}).setScrollFactor(0, 0).setDepth(11);
|
||||
}
|
||||
|
||||
|
||||
|
||||
update() {
|
||||
// 更新输入系统
|
||||
this.inputSystem.update();
|
||||
|
||||
// 更新所有单位
|
||||
this.unitGroup.getChildren().forEach(unit => {
|
||||
if (unit.update) {
|
||||
unit.update();
|
||||
}
|
||||
unit.updateMovement();
|
||||
|
||||
// 处理角色动画
|
||||
this.updateCharacterAnimation(unit);
|
||||
});
|
||||
|
||||
// 按空格键返回启动场景
|
||||
if (this.cursors.space.isDown) {
|
||||
this.scene.start('BootScene');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色动画
|
||||
* @param {Object} unit - 单位对象
|
||||
*/
|
||||
updateCharacterAnimation(unit) {
|
||||
// 检查单位是否在移动
|
||||
const isMoving = Math.abs(unit.body.velocity.x) > 10 || Math.abs(unit.body.velocity.y) > 10;
|
||||
|
||||
if (isMoving) {
|
||||
// 处理角色朝向
|
||||
if (unit.body.velocity.x < -10) {
|
||||
unit.setFlipX(true); // 向左翻转
|
||||
} else if (unit.body.velocity.x > 10) {
|
||||
unit.setFlipX(false); // 向右不翻转
|
||||
}
|
||||
|
||||
// 播放动画
|
||||
if (!unit.anims.isPlaying) {
|
||||
// 根据当前角色播放对应的移动动画
|
||||
if (this.currentCharacterText.text === '当前角色: Player0') {
|
||||
unit.play('player0-move');
|
||||
} else {
|
||||
unit.play('player1-move');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 停止动画,保持当前帧
|
||||
unit.anims.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,631 @@
|
||||
// 背包系统测试场景
|
||||
|
||||
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 { ItemManager } from '../../items/ItemManager.js';
|
||||
import { Inventory } from '../../items/Inventory.js';
|
||||
import { InventoryUI } from '../../ui/InventoryUI.js';
|
||||
|
||||
export default class InventoryTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'InventoryTestScene' });
|
||||
}
|
||||
|
||||
preload() {
|
||||
// 加载物品数据
|
||||
this.load.json('items', './data/items.json');
|
||||
}
|
||||
|
||||
create() {
|
||||
// 物理世界和相机边界
|
||||
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: true,
|
||||
joystick: false,
|
||||
selection: true
|
||||
});
|
||||
this.selectionSystem = new SelectionSystem(this);
|
||||
this.commandSystem = new CommandSystem(this);
|
||||
|
||||
// 创建单位组
|
||||
this.unitGroup = this.add.group();
|
||||
|
||||
// 创建背景
|
||||
this.createBackground();
|
||||
|
||||
// 创建玩家
|
||||
this.createPlayer();
|
||||
|
||||
// 初始化物品管理器
|
||||
this.itemManager = new ItemManager(this);
|
||||
|
||||
// 初始化背包系统
|
||||
this.initInventory();
|
||||
|
||||
// 创建测试物品
|
||||
this.createTestItems();
|
||||
|
||||
// 设置输入处理
|
||||
this.setupInput();
|
||||
|
||||
// 创建侧边栏UI
|
||||
this.createSidebar();
|
||||
|
||||
// 显示提示信息
|
||||
this.showInstructions();
|
||||
|
||||
// 显示单位状态
|
||||
this.updateUnitStatus(this.player);
|
||||
|
||||
// 显示默认提示
|
||||
this.itemInfoText.setText('未选择任何物品');
|
||||
this.interactionText.setText('点击物品查看详细信息,交互范围50像素');
|
||||
this.generalInfoText.setText('欢迎使用背包系统测试场景');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建背景
|
||||
*/
|
||||
createBackground() {
|
||||
// 创建纯色背景
|
||||
this.add.rectangle(0, 0, this.sys.game.config.width, this.sys.game.config.height, 0x1a1a2e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建玩家
|
||||
*/
|
||||
createPlayer() {
|
||||
// 为玩家创建纯色纹理
|
||||
if (!this.textures.exists('player')) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 32;
|
||||
canvas.height = 32;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#4a90e2';
|
||||
ctx.beginPath();
|
||||
ctx.arc(16, 16, 16, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
this.textures.addCanvas('player', canvas);
|
||||
}
|
||||
|
||||
// 创建玩家单位
|
||||
this.player = new Unit(this, 0, 0, 'player');
|
||||
|
||||
// 设置玩家属性
|
||||
this.player.name = '测试玩家';
|
||||
this.player.health = 100;
|
||||
this.player.nutrition = 100;
|
||||
this.player.hydration = 100;
|
||||
this.player.equipWeapon = function(weapon) {
|
||||
this.weapon = weapon;
|
||||
console.log(`${this.name} 装备了 ${weapon.name}`);
|
||||
};
|
||||
|
||||
// 添加到单位组
|
||||
this.unitGroup.add(this.player);
|
||||
|
||||
// 自动选中玩家
|
||||
this.events.emit('select-unit', this.player);
|
||||
|
||||
// 添加碰撞检测
|
||||
this.physics.add.collider(this.unitGroup, this.unitGroup);
|
||||
|
||||
// 保持相机中心在世界原点,与物品测试场景一致
|
||||
this.cameras.main.centerOn(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化背包系统
|
||||
*/
|
||||
initInventory() {
|
||||
// 创建背包(6x4网格)
|
||||
this.player.inventory = new Inventory(6, 4);
|
||||
|
||||
// 创建背包UI(会自动固定在右侧)
|
||||
this.inventoryUI = new InventoryUI(this, this.player.inventory, 40);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建测试物品
|
||||
*/
|
||||
createTestItems() {
|
||||
// 等待物品数据加载完成
|
||||
this.itemManager.onDataLoaded(() => {
|
||||
// 创建初始物品
|
||||
this.createInitialItems();
|
||||
|
||||
// 绑定物品点击事件
|
||||
this.events.on('item-clicked', (item) => {
|
||||
this.showItemInfo(item);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建初始物品
|
||||
*/
|
||||
createInitialItems() {
|
||||
// 在不同位置创建各种类型的物品
|
||||
const itemTypes = ['food', 'tool', 'medical', 'weapon'];
|
||||
|
||||
for (let i = 0; i < itemTypes.length; i++) {
|
||||
const x = this.player.x + (i - 2) * 200;
|
||||
const y = this.player.y - 200;
|
||||
this.itemManager.createItemByType(itemTypes[i], x, y);
|
||||
}
|
||||
|
||||
// 在场景中随机位置创建更多物品
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const itemIds = ['water_bottle', 'canned_food', 'crowbar', 'first_aid_kit', 'flashlight', 'knife'];
|
||||
const randomItemId = itemIds[Math.floor(Math.random() * itemIds.length)];
|
||||
const x = this.player.x + Phaser.Math.Between(-300, 300);
|
||||
const y = this.player.y + Phaser.Math.Between(-300, 300);
|
||||
|
||||
this.itemManager.createItem(randomItemId, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入处理
|
||||
*/
|
||||
setupInput() {
|
||||
// 键盘输入
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
|
||||
// 背包快捷键(B键)
|
||||
this.input.keyboard.on('keydown-B', () => {
|
||||
this.inventoryUI.toggle();
|
||||
});
|
||||
|
||||
// 注册键盘事件
|
||||
this.input.keyboard.on('keydown-ONE', () => {
|
||||
this.itemManager.createRandomItem();
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-TWO', () => {
|
||||
// 在单位附近创建食物类物品
|
||||
const x = this.player.x + Phaser.Math.Between(-100, 100);
|
||||
const y = this.player.y + Phaser.Math.Between(-100, 100);
|
||||
this.itemManager.createItemByType('food', x, y);
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-THREE', () => {
|
||||
// 在单位附近创建工具类物品
|
||||
const x = this.player.x + Phaser.Math.Between(-100, 100);
|
||||
const y = this.player.y + Phaser.Math.Between(-100, 100);
|
||||
this.itemManager.createItemByType('tool', x, y);
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-FOUR', () => {
|
||||
this.itemManager.clearAllItems();
|
||||
this.itemInfoText.setText('所有物品已清理');
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-SPACE', () => {
|
||||
this.scene.start('BootScene');
|
||||
});
|
||||
|
||||
// 创建交互按钮
|
||||
this.createButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建交互按钮
|
||||
*/
|
||||
createButtons() {
|
||||
const buttonWidth = 200;
|
||||
const buttonHeight = 40;
|
||||
const buttonY = this.sys.game.config.height - 100;
|
||||
|
||||
// 创建随机物品按钮
|
||||
this.createButton(this.sys.game.config.width - buttonWidth - 10, buttonY, '创建随机物品', () => {
|
||||
this.itemManager.createRandomItem();
|
||||
});
|
||||
|
||||
// 创建清理物品按钮
|
||||
this.createButton(this.sys.game.config.width - buttonWidth - 10, buttonY + 50, '清理所有物品', () => {
|
||||
this.itemManager.clearAllItems();
|
||||
this.itemInfoText.setText('所有物品已清理');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建按钮
|
||||
* @param {number} x - 按钮x坐标
|
||||
* @param {number} y - 按钮y坐标
|
||||
* @param {string} text - 按钮文本
|
||||
* @param {function} callback - 点击回调函数
|
||||
*/
|
||||
createButton(x, y, text, callback) {
|
||||
// 创建按钮背景
|
||||
const button = this.add.rectangle(x, y, 200, 40, 0x333333);
|
||||
button.setOrigin(0.5, 0.5);
|
||||
button.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
button.setDepth(15); // 设置深度,确保显示在场景说明文本上方
|
||||
|
||||
// 创建按钮文本
|
||||
const buttonText = this.add.text(x, y, text, {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
buttonText.setOrigin(0.5, 0.5);
|
||||
buttonText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
buttonText.setDepth(16); // 设置深度,确保显示在按钮背景上方
|
||||
|
||||
// 设置按钮交互
|
||||
button.setInteractive({
|
||||
useHandCursor: true
|
||||
});
|
||||
|
||||
// 按钮事件
|
||||
button.on('pointerdown', callback);
|
||||
|
||||
button.on('pointerover', () => {
|
||||
button.setFillStyle(0x555555);
|
||||
});
|
||||
|
||||
button.on('pointerout', () => {
|
||||
button.setFillStyle(0x333333);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示物品信息
|
||||
* @param {Item} item - 物品实例
|
||||
*/
|
||||
showItemInfo(item) {
|
||||
const unit = this.player;
|
||||
const isInRange = item.isInInteractionRange(unit);
|
||||
|
||||
const info = item.getInfo();
|
||||
let infoText = `${info.name}\n`;
|
||||
infoText += `${info.description}\n`;
|
||||
infoText += `类型: ${info.type}\n`;
|
||||
infoText += `重量: ${info.weight}kg\n`;
|
||||
infoText += `体积: ${info.volume}L\n`;
|
||||
infoText += `价值: ${info.value}金币\n`;
|
||||
infoText += `尺寸: ${info.gridSize.width}x${info.gridSize.height}\n`;
|
||||
infoText += `距离: ${Math.round(Math.sqrt(Math.pow(item.x - unit.x, 2) + Math.pow(item.y - unit.y, 2)))}m\n`;
|
||||
|
||||
// 显示物品属性
|
||||
infoText += '属性: ';
|
||||
const properties = [];
|
||||
for (const [key, value] of Object.entries(info.properties)) {
|
||||
properties.push(`${key}: ${value}`);
|
||||
}
|
||||
infoText += properties.join(', ');
|
||||
|
||||
this.itemInfoText.setText(infoText);
|
||||
|
||||
// 显示交互提示
|
||||
if (isInRange) {
|
||||
this.interactionText.setText('按E键拾取物品,按Q键快捷使用');
|
||||
// 注册E键拾取物品
|
||||
this.input.keyboard.once('keydown-E', () => {
|
||||
item.pickup(unit);
|
||||
// 更新背包UI
|
||||
this.inventoryUI.updateUI();
|
||||
this.interactionText.setText('');
|
||||
this.itemInfoText.setText('未选择任何物品');
|
||||
});
|
||||
// 注册Q键快捷使用物品
|
||||
this.input.keyboard.once('keydown-Q', () => {
|
||||
item.use(unit);
|
||||
this.interactionText.setText('');
|
||||
this.itemInfoText.setText('未选择任何物品');
|
||||
});
|
||||
} else {
|
||||
this.interactionText.setText('距离太远,无法交互');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提示信息
|
||||
*/
|
||||
showInstructions() {
|
||||
const titleText = this.add.text(10, 10, '背包系统测试场景', {
|
||||
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 itemText = this.add.text(10, 70, '点击物品查看信息,右键移动到物品位置', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
itemText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
itemText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key1Text = this.add.text(10, 100, '按1键:创建随机物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key1Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key1Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key2Text = this.add.text(10, 130, '按2键:创建食物类物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key2Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key2Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key3Text = this.add.text(10, 160, '按3键:创建工具类物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key3Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key3Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key4Text = this.add.text(10, 190, '按4键:清理所有物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key4Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key4Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const keyIText = this.add.text(10, 220, '按B键:打开/关闭背包', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
keyIText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
keyIText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const spaceText = this.add.text(10, 250, '按空格键返回启动场景', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
spaceText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
// 显示背包操作说明
|
||||
const inventoryText = this.add.text(10, 280, '背包操作:点击物品选择,再次点击取消选择,点击空位移动物品', {
|
||||
fontSize: '14px',
|
||||
fill: '#ffff00'
|
||||
});
|
||||
inventoryText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
inventoryText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建侧边栏UI
|
||||
*/
|
||||
createSidebar() {
|
||||
// 侧边栏宽度
|
||||
const sidebarWidth = 300;
|
||||
const sidebarDepth = 5; // 侧边栏深度值,确保显示在游戏元素上方,但低于场景说明文本
|
||||
|
||||
// 创建侧边栏背景
|
||||
this.sidebar = this.add.rectangle(
|
||||
this.sys.game.config.width - sidebarWidth,
|
||||
0,
|
||||
sidebarWidth,
|
||||
this.sys.game.config.height,
|
||||
0x111111,
|
||||
0.9
|
||||
);
|
||||
this.sidebar.setOrigin(0, 0);
|
||||
this.sidebar.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.sidebar.setDepth(sidebarDepth); // 设置深度,确保显示在游戏元素上方
|
||||
|
||||
// 添加侧边栏边框
|
||||
this.sidebarBorder = this.add.rectangle(
|
||||
this.sys.game.config.width - sidebarWidth,
|
||||
0,
|
||||
sidebarWidth,
|
||||
this.sys.game.config.height,
|
||||
0x444444,
|
||||
0
|
||||
);
|
||||
this.sidebarBorder.setOrigin(0, 0);
|
||||
this.sidebarBorder.setStrokeStyle(2, 0x444444, 1);
|
||||
this.sidebarBorder.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.sidebarBorder.setDepth(sidebarDepth + 1); // 设置深度,确保显示在侧边栏背景上方
|
||||
|
||||
// 侧边栏标题
|
||||
const title = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
20,
|
||||
'游戏信息面板',
|
||||
{
|
||||
fontSize: '20px',
|
||||
fill: '#ffffff',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
title.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
title.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 通用信息区域标题
|
||||
const generalTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
50,
|
||||
'通用信息:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#4CAF50',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
generalTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
generalTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.generalInfoText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
75,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.generalInfoText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.generalInfoText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 角色属性区域标题
|
||||
const characterTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
120,
|
||||
'角色属性:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#2196F3',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
characterTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
characterTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.unitStatusText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
145,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.unitStatusText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.unitStatusText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 交互提示区域标题
|
||||
const interactionTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
220,
|
||||
'交互提示:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#FFEB3B',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
interactionTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
interactionTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.interactionText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
245,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffff00',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.interactionText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.interactionText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 物品详情区域标题
|
||||
const itemTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
350,
|
||||
'物品详情:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#FF9800',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
itemTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
itemTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.itemInfoText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
375,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.itemInfoText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.itemInfoText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单位状态显示
|
||||
* @param {Unit} unit - 单位实例
|
||||
*/
|
||||
updateUnitStatus(unit) {
|
||||
let statusText = `${unit.name} 状态:\n`;
|
||||
statusText += `生命值: ${unit.health}\n`;
|
||||
statusText += `营养值: ${unit.nutrition}\n`;
|
||||
statusText += `水分值: ${unit.hydration}\n`;
|
||||
if (unit.weapon) {
|
||||
statusText += `武器: ${unit.weapon.name}`;
|
||||
}
|
||||
|
||||
this.unitStatusText.setText(statusText);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示消息
|
||||
* @param {string} message - 消息内容
|
||||
*/
|
||||
showMessage(message) {
|
||||
// 创建消息
|
||||
const messageText = this.add.text(
|
||||
this.player.x,
|
||||
this.player.y - 50,
|
||||
message,
|
||||
{
|
||||
font: '20px Arial',
|
||||
fill: '#ffffff',
|
||||
backgroundColor: '#000000',
|
||||
padding: { x: 10, y: 5 }
|
||||
}
|
||||
);
|
||||
messageText.setOrigin(0.5, 0.5);
|
||||
messageText.setDepth(20);
|
||||
messageText.setScrollFactor(1); // 允许消息随相机移动
|
||||
|
||||
// 3秒后自动移除消息
|
||||
this.time.delayedCall(3000, () => {
|
||||
if (messageText) {
|
||||
messageText.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
this.inputSystem.update();
|
||||
|
||||
// 更新所有单位
|
||||
this.unitGroup.getChildren().forEach(unit => {
|
||||
if (unit.update) {
|
||||
unit.update();
|
||||
}
|
||||
unit.updateMovement();
|
||||
// 更新单位状态显示
|
||||
this.updateUnitStatus(unit);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,673 @@
|
||||
// 物品列表菜单测试场景
|
||||
|
||||
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 { ItemManager } from '../../items/ItemManager.js';
|
||||
import { Inventory } from '../../items/Inventory.js';
|
||||
import { InventoryUI } from '../../ui/InventoryUI.js';
|
||||
import { ItemListUI } from '../../ui/ItemListUI.js';
|
||||
|
||||
export default class ItemListTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'ItemListTestScene' });
|
||||
}
|
||||
|
||||
preload() {
|
||||
// 加载物品数据
|
||||
this.load.json('items', './data/items.json');
|
||||
}
|
||||
|
||||
create() {
|
||||
// 物理世界和相机边界
|
||||
const worldSize = 1000;
|
||||
this.physics.world.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
|
||||
this.cameras.main.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
|
||||
|
||||
// 初始化系统
|
||||
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();
|
||||
|
||||
// 创建背景
|
||||
this.createBackground();
|
||||
|
||||
// 创建玩家
|
||||
this.createPlayer();
|
||||
|
||||
// 初始化物品管理器
|
||||
this.itemManager = new ItemManager(this);
|
||||
|
||||
// 初始化背包系统
|
||||
this.initInventory();
|
||||
|
||||
// 创建物品列表UI
|
||||
this.initItemListUI();
|
||||
|
||||
// 创建测试物品
|
||||
this.createTestItems();
|
||||
|
||||
// 设置输入处理
|
||||
this.setupInput();
|
||||
|
||||
// 创建侧边栏UI
|
||||
this.createSidebar();
|
||||
|
||||
// 显示提示信息
|
||||
this.showInstructions();
|
||||
|
||||
// 显示单位状态
|
||||
this.updateUnitStatus(this.player);
|
||||
|
||||
// 显示默认提示
|
||||
this.itemInfoText.setText('未选择任何物品');
|
||||
this.interactionText.setText('按c键打开物品列表菜单');
|
||||
this.generalInfoText.setText('欢迎使用物品列表菜单测试场景');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建背景
|
||||
*/
|
||||
createBackground() {
|
||||
// 创建纯色背景
|
||||
this.add.rectangle(0, 0, this.sys.game.config.width, this.sys.game.config.height, 0x1a1a2e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建玩家
|
||||
*/
|
||||
createPlayer() {
|
||||
// 为玩家创建纯色纹理
|
||||
if (!this.textures.exists('player')) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 32;
|
||||
canvas.height = 32;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#4a90e2';
|
||||
ctx.beginPath();
|
||||
ctx.arc(16, 16, 16, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
this.textures.addCanvas('player', canvas);
|
||||
}
|
||||
|
||||
// 创建玩家单位
|
||||
this.player = new Unit(this, 0, 0, 'player');
|
||||
|
||||
// 设置玩家属性
|
||||
this.player.name = '测试玩家';
|
||||
this.player.health = 100;
|
||||
this.player.nutrition = 100;
|
||||
this.player.hydration = 100;
|
||||
this.player.equipWeapon = function(weapon) {
|
||||
this.weapon = weapon;
|
||||
console.log(`${this.name} 装备了 ${weapon.name}`);
|
||||
};
|
||||
|
||||
// 添加到单位组
|
||||
this.unitGroup.add(this.player);
|
||||
|
||||
// 自动选中玩家
|
||||
this.events.emit('select-unit', this.player);
|
||||
|
||||
// 添加碰撞检测
|
||||
this.physics.add.collider(this.unitGroup, this.unitGroup);
|
||||
|
||||
// 保持相机中心在世界原点,与背包测试场景一致
|
||||
this.cameras.main.centerOn(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化背包系统
|
||||
*/
|
||||
initInventory() {
|
||||
// 创建背包(6x4网格)
|
||||
this.player.inventory = new Inventory(6, 4);
|
||||
|
||||
// 创建背包UI(会自动固定在右侧)
|
||||
this.inventoryUI = new InventoryUI(this, this.player.inventory, 40);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化物品列表UI
|
||||
*/
|
||||
initItemListUI() {
|
||||
// 创建物品列表UI,位于屏幕左下角
|
||||
const x = 10;
|
||||
const y = this.sys.game.config.height - 350;
|
||||
this.itemListUI = new ItemListUI(this, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建测试物品
|
||||
*/
|
||||
createTestItems() {
|
||||
// 等待物品数据加载完成
|
||||
this.itemManager.onDataLoaded(() => {
|
||||
// 创建初始物品
|
||||
this.createInitialItems();
|
||||
|
||||
// 绑定物品点击事件
|
||||
this.events.on('item-clicked', (item) => {
|
||||
this.showItemInfo(item);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建初始物品
|
||||
*/
|
||||
createInitialItems() {
|
||||
// 在不同位置创建各种类型的物品,确保有些在交互范围内,有些在范围外
|
||||
const itemTypes = ['food', 'tool', 'medical', 'weapon'];
|
||||
|
||||
// 在交互范围内创建物品(距离玩家50像素内)
|
||||
for (let i = 0; i < itemTypes.length; i++) {
|
||||
const x = this.player.x + (i - 2) * 40;
|
||||
const y = this.player.y - 40;
|
||||
this.itemManager.createItemByType(itemTypes[i], x, y);
|
||||
}
|
||||
|
||||
// 在交互范围外创建物品(距离玩家100像素外)
|
||||
for (let i = 0; i < itemTypes.length; i++) {
|
||||
const x = this.player.x + (i - 2) * 120;
|
||||
const y = this.player.y - 120;
|
||||
this.itemManager.createItemByType(itemTypes[i], x, y);
|
||||
}
|
||||
|
||||
// 在场景中随机位置创建更多物品
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const itemIds = ['water_bottle', 'canned_food', 'crowbar', 'first_aid_kit', 'flashlight', 'knife'];
|
||||
const randomItemId = itemIds[Math.floor(Math.random() * itemIds.length)];
|
||||
const x = this.player.x + Phaser.Math.Between(-300, 300);
|
||||
const y = this.player.y + Phaser.Math.Between(-300, 300);
|
||||
|
||||
this.itemManager.createItem(randomItemId, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入处理
|
||||
*/
|
||||
setupInput() {
|
||||
// 键盘输入
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
|
||||
// 背包快捷键(B键)
|
||||
this.input.keyboard.on('keydown-B', () => {
|
||||
this.inventoryUI.toggle();
|
||||
});
|
||||
|
||||
// 物品列表菜单快捷键(C键)
|
||||
this.input.keyboard.on('keydown-C', () => {
|
||||
this.itemListUI.toggle();
|
||||
});
|
||||
|
||||
// 注册键盘事件
|
||||
this.input.keyboard.on('keydown-ONE', () => {
|
||||
this.itemManager.createRandomItem();
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-TWO', () => {
|
||||
// 在单位附近创建食物类物品
|
||||
const x = this.player.x + Phaser.Math.Between(-100, 100);
|
||||
const y = this.player.y + Phaser.Math.Between(-100, 100);
|
||||
this.itemManager.createItemByType('food', x, y);
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-THREE', () => {
|
||||
// 在单位附近创建工具类物品
|
||||
const x = this.player.x + Phaser.Math.Between(-100, 100);
|
||||
const y = this.player.y + Phaser.Math.Between(-100, 100);
|
||||
this.itemManager.createItemByType('tool', x, y);
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-FOUR', () => {
|
||||
this.itemManager.clearAllItems();
|
||||
this.itemInfoText.setText('所有物品已清理');
|
||||
// 更新物品列表UI
|
||||
if (this.itemListUI.visible) {
|
||||
this.itemListUI.updateItems(this.player, this.itemManager.getActiveItems());
|
||||
}
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-SPACE', () => {
|
||||
this.scene.start('BootScene');
|
||||
});
|
||||
|
||||
// 创建交互按钮
|
||||
this.createButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建交互按钮
|
||||
*/
|
||||
createButtons() {
|
||||
const buttonWidth = 200;
|
||||
const buttonHeight = 40;
|
||||
const buttonY = this.sys.game.config.height - 100;
|
||||
|
||||
// 创建随机物品按钮
|
||||
this.createButton(this.sys.game.config.width - buttonWidth - 10, buttonY, '创建随机物品', () => {
|
||||
this.itemManager.createRandomItem();
|
||||
});
|
||||
|
||||
// 创建清理物品按钮
|
||||
this.createButton(this.sys.game.config.width - buttonWidth - 10, buttonY + 50, '清理所有物品', () => {
|
||||
this.itemManager.clearAllItems();
|
||||
this.itemInfoText.setText('所有物品已清理');
|
||||
// 更新物品列表UI
|
||||
if (this.itemListUI.visible) {
|
||||
this.itemListUI.updateItems(this.player, this.itemManager.getActiveItems());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建按钮
|
||||
* @param {number} x - 按钮x坐标
|
||||
* @param {number} y - 按钮y坐标
|
||||
* @param {string} text - 按钮文本
|
||||
* @param {function} callback - 点击回调函数
|
||||
*/
|
||||
createButton(x, y, text, callback) {
|
||||
// 创建按钮背景
|
||||
const button = this.add.rectangle(x, y, 200, 40, 0x333333);
|
||||
button.setOrigin(0.5, 0.5);
|
||||
button.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
button.setDepth(15); // 设置深度,确保显示在场景说明文本上方
|
||||
|
||||
// 创建按钮文本
|
||||
const buttonText = this.add.text(x, y, text, {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
buttonText.setOrigin(0.5, 0.5);
|
||||
buttonText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
buttonText.setDepth(16); // 设置深度,确保显示在按钮背景上方
|
||||
|
||||
// 设置按钮交互
|
||||
button.setInteractive({
|
||||
useHandCursor: true
|
||||
});
|
||||
|
||||
// 按钮事件
|
||||
button.on('pointerdown', callback);
|
||||
|
||||
button.on('pointerover', () => {
|
||||
button.setFillStyle(0x555555);
|
||||
});
|
||||
|
||||
button.on('pointerout', () => {
|
||||
button.setFillStyle(0x333333);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示物品信息
|
||||
* @param {Item} item - 物品实例
|
||||
*/
|
||||
showItemInfo(item) {
|
||||
const unit = this.player;
|
||||
const isInRange = item.isInInteractionRange(unit);
|
||||
|
||||
const info = item.getInfo();
|
||||
let infoText = `${info.name}\n`;
|
||||
infoText += `${info.description}\n`;
|
||||
infoText += `类型: ${info.type}\n`;
|
||||
infoText += `重量: ${info.weight}kg\n`;
|
||||
infoText += `体积: ${info.volume}L\n`;
|
||||
infoText += `价值: ${info.value}金币\n`;
|
||||
infoText += `尺寸: ${info.gridSize.width}x${info.gridSize.height}\n`;
|
||||
infoText += `距离: ${Math.round(Math.sqrt(Math.pow(item.x - unit.x, 2) + Math.pow(item.y - unit.y, 2)))}m\n`;
|
||||
|
||||
// 显示物品属性
|
||||
infoText += '属性: ';
|
||||
const properties = [];
|
||||
for (const [key, value] of Object.entries(info.properties)) {
|
||||
properties.push(`${key}: ${value}`);
|
||||
}
|
||||
infoText += properties.join(', ');
|
||||
|
||||
this.itemInfoText.setText(infoText);
|
||||
|
||||
// 显示交互提示
|
||||
if (isInRange) {
|
||||
this.interactionText.setText('按E键拾取物品,按Q键快捷使用');
|
||||
// 注册E键拾取物品
|
||||
this.input.keyboard.once('keydown-E', () => {
|
||||
item.pickup(unit);
|
||||
// 物品拾取后,更新物品列表UI
|
||||
if (this.itemListUI.visible) {
|
||||
this.itemListUI.updateItems(this.player, this.itemManager.getActiveItems());
|
||||
}
|
||||
this.interactionText.setText('');
|
||||
this.itemInfoText.setText('未选择任何物品');
|
||||
});
|
||||
// 注册Q键快捷使用物品
|
||||
this.input.keyboard.once('keydown-Q', () => {
|
||||
item.use(unit);
|
||||
// 更新物品列表UI
|
||||
if (this.itemListUI.visible) {
|
||||
this.itemListUI.updateItems(this.player, this.itemManager.getActiveItems());
|
||||
}
|
||||
this.interactionText.setText('');
|
||||
this.itemInfoText.setText('未选择任何物品');
|
||||
});
|
||||
} else {
|
||||
this.interactionText.setText('距离太远,无法交互');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提示信息
|
||||
*/
|
||||
showInstructions() {
|
||||
const titleText = this.add.text(10, 10, '物品列表菜单测试场景', {
|
||||
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 itemText = this.add.text(10, 70, '点击物品查看详细信息,交互范围50像素', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
itemText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
itemText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key1Text = this.add.text(10, 100, '按1键:创建随机物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key1Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key1Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key2Text = this.add.text(10, 130, '按2键:创建食物类物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key2Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key2Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key3Text = this.add.text(10, 160, '按3键:创建工具类物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key3Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key3Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key4Text = this.add.text(10, 190, '按4键:清理所有物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key4Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key4Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const keyBText = this.add.text(10, 220, '按B键:打开/关闭背包', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
keyBText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
keyBText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const keyCText = this.add.text(10, 250, '按C键:打开/关闭物品列表菜单', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffff00'
|
||||
});
|
||||
keyCText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
keyCText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const spaceText = this.add.text(10, 280, '按空格键返回启动场景', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
spaceText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建侧边栏UI
|
||||
*/
|
||||
createSidebar() {
|
||||
// 侧边栏宽度
|
||||
const sidebarWidth = 300;
|
||||
const sidebarDepth = 5; // 侧边栏深度值,确保显示在游戏元素上方,但低于场景说明文本
|
||||
|
||||
// 创建侧边栏背景
|
||||
this.sidebar = this.add.rectangle(
|
||||
this.sys.game.config.width - sidebarWidth,
|
||||
0,
|
||||
sidebarWidth,
|
||||
this.sys.game.config.height,
|
||||
0x111111,
|
||||
0.9
|
||||
);
|
||||
this.sidebar.setOrigin(0, 0);
|
||||
this.sidebar.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.sidebar.setDepth(sidebarDepth); // 设置深度,确保显示在游戏元素上方
|
||||
|
||||
// 添加侧边栏边框
|
||||
this.sidebarBorder = this.add.rectangle(
|
||||
this.sys.game.config.width - sidebarWidth,
|
||||
0,
|
||||
sidebarWidth,
|
||||
this.sys.game.config.height,
|
||||
0x444444,
|
||||
0
|
||||
);
|
||||
this.sidebarBorder.setOrigin(0, 0);
|
||||
this.sidebarBorder.setStrokeStyle(2, 0x444444, 1);
|
||||
this.sidebarBorder.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.sidebarBorder.setDepth(sidebarDepth + 1); // 设置深度,确保显示在侧边栏背景上方
|
||||
|
||||
// 侧边栏标题
|
||||
const title = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
20,
|
||||
'游戏信息面板',
|
||||
{
|
||||
fontSize: '20px',
|
||||
fill: '#ffffff',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
title.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
title.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 通用信息区域标题
|
||||
const generalTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
50,
|
||||
'通用信息:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#4CAF50',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
generalTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
generalTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.generalInfoText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
75,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.generalInfoText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.generalInfoText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 角色属性区域标题
|
||||
const characterTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
120,
|
||||
'角色属性:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#2196F3',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
characterTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
characterTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.unitStatusText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
145,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.unitStatusText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.unitStatusText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 交互提示区域标题
|
||||
const interactionTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
220,
|
||||
'交互提示:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#FFEB3B',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
interactionTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
interactionTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.interactionText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
245,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffff00',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.interactionText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.interactionText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 物品详情区域标题
|
||||
const itemTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
350,
|
||||
'物品详情:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#FF9800',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
itemTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
itemTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.itemInfoText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
375,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.itemInfoText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.itemInfoText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单位状态显示
|
||||
* @param {Unit} unit - 单位实例
|
||||
*/
|
||||
updateUnitStatus(unit) {
|
||||
let statusText = `${unit.name} 状态:\n`;
|
||||
statusText += `生命值: ${unit.health}\n`;
|
||||
statusText += `营养值: ${unit.nutrition}\n`;
|
||||
statusText += `水分值: ${unit.hydration}\n`;
|
||||
if (unit.weapon) {
|
||||
statusText += `武器: ${unit.weapon.name}`;
|
||||
}
|
||||
|
||||
this.unitStatusText.setText(statusText);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示消息
|
||||
* @param {string} message - 消息内容
|
||||
*/
|
||||
showMessage(message) {
|
||||
// 创建消息
|
||||
const messageText = this.add.text(
|
||||
this.player.x,
|
||||
this.player.y - 50,
|
||||
message,
|
||||
{
|
||||
font: '20px Arial',
|
||||
fill: '#ffffff',
|
||||
backgroundColor: '#000000',
|
||||
padding: { x: 10, y: 5 }
|
||||
}
|
||||
);
|
||||
messageText.setOrigin(0.5, 0.5);
|
||||
messageText.setDepth(20);
|
||||
messageText.setScrollFactor(1); // 允许消息随相机移动
|
||||
|
||||
// 3秒后自动移除消息
|
||||
this.time.delayedCall(3000, () => {
|
||||
if (messageText) {
|
||||
messageText.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
this.inputSystem.update();
|
||||
|
||||
// 更新物品列表UI(如果可见)
|
||||
this.itemListUI.update();
|
||||
|
||||
// 更新所有单位
|
||||
this.unitGroup.getChildren().forEach(unit => {
|
||||
if (unit.update) {
|
||||
unit.update();
|
||||
}
|
||||
unit.updateMovement();
|
||||
// 更新单位状态显示
|
||||
this.updateUnitStatus(unit);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
// 物品系统测试场景
|
||||
|
||||
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 { ItemManager } from '../../items/ItemManager.js';
|
||||
|
||||
export default class ItemTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'ItemTestScene' });
|
||||
}
|
||||
|
||||
create() {
|
||||
// 物理世界和相机边界
|
||||
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: true,
|
||||
joystick: false,
|
||||
selection: true
|
||||
});
|
||||
this.selectionSystem = new SelectionSystem(this);
|
||||
this.commandSystem = new CommandSystem(this);
|
||||
|
||||
// 创建单位组
|
||||
this.unitGroup = this.add.group();
|
||||
|
||||
// 创建玩家纹理
|
||||
if (!this.textures.exists('player')) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 32;
|
||||
canvas.height = 32;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#4a90e2';
|
||||
ctx.beginPath();
|
||||
ctx.arc(16, 16, 16, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
this.textures.addCanvas('player', canvas);
|
||||
}
|
||||
|
||||
// 创建一个测试单位
|
||||
const unit = new Unit(this, 0, 0, 'player');
|
||||
// 为单位添加一些属性,用于测试物品效果
|
||||
unit.name = '测试单位';
|
||||
unit.health = 100;
|
||||
unit.nutrition = 50;
|
||||
unit.hydration = 50;
|
||||
unit.equipWeapon = function(weapon) {
|
||||
this.weapon = weapon;
|
||||
console.log(`${this.name} 装备了 ${weapon.name}`);
|
||||
};
|
||||
this.unitGroup.add(unit);
|
||||
|
||||
// 自动选中单位
|
||||
this.events.emit('select-unit', unit);
|
||||
|
||||
// 添加碰撞检测
|
||||
this.physics.add.collider(this.unitGroup, this.unitGroup);
|
||||
|
||||
// 初始化物品管理器
|
||||
this.itemManager = new ItemManager(this);
|
||||
|
||||
// 当物品数据加载完成后创建初始物品
|
||||
this.itemManager.onDataLoaded(() => {
|
||||
this.createInitialItems();
|
||||
});
|
||||
|
||||
// 创建侧边栏UI(先创建,确保它在最底层)
|
||||
this.createSidebar();
|
||||
|
||||
// 添加场景说明文本
|
||||
const titleText = this.add.text(10, 10, '物品系统测试场景', {
|
||||
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 itemText = this.add.text(10, 70, '点击物品查看信息,右键移动到物品位置', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
itemText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
itemText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key1Text = this.add.text(10, 100, '按1键:创建随机物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key1Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key1Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key2Text = this.add.text(10, 130, '按2键:创建食物类物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key2Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key2Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key3Text = this.add.text(10, 160, '按3键:创建工具类物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key3Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key3Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const key4Text = this.add.text(10, 190, '按4键:清理所有物品', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
key4Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
key4Text.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
const spaceText = this.add.text(10, 220, '按空格键返回启动场景', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
spaceText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
// 显示单位状态
|
||||
this.updateUnitStatus(unit);
|
||||
|
||||
// 显示默认提示
|
||||
this.itemInfoText.setText('未选择任何物品');
|
||||
this.interactionText.setText('点击物品查看详细信息,交互范围50像素');
|
||||
this.generalInfoText.setText('欢迎使用物品系统测试场景');
|
||||
|
||||
// 添加场景提示信息
|
||||
const interactionHintText = this.add.text(10, 250, '点击物品查看详细信息,交互范围50像素', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff'
|
||||
});
|
||||
interactionHintText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
interactionHintText.setDepth(10); // 设置深度,确保显示在侧边栏上方
|
||||
|
||||
// 注册键盘事件
|
||||
this.input.keyboard.on('keydown-ONE', () => {
|
||||
this.itemManager.createRandomItem();
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-TWO', () => {
|
||||
// 在单位附近创建食物类物品
|
||||
const x = unit.x + Phaser.Math.Between(-100, 100);
|
||||
const y = unit.y + Phaser.Math.Between(-100, 100);
|
||||
this.itemManager.createItemByType('food', x, y);
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-THREE', () => {
|
||||
// 在单位附近创建工具类物品
|
||||
const x = unit.x + Phaser.Math.Between(-100, 100);
|
||||
const y = unit.y + Phaser.Math.Between(-100, 100);
|
||||
this.itemManager.createItemByType('tool', x, y);
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-FOUR', () => {
|
||||
this.itemManager.clearAllItems();
|
||||
this.itemInfoText.setText('所有物品已清理');
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-SPACE', () => {
|
||||
this.scene.start('BootScene');
|
||||
});
|
||||
|
||||
// 监听物品点击事件
|
||||
this.events.on('item-clicked', (item) => {
|
||||
this.showItemInfo(item);
|
||||
});
|
||||
|
||||
// 创建一些交互按钮
|
||||
this.createButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建初始物品
|
||||
*/
|
||||
createInitialItems() {
|
||||
// 在不同位置创建各种类型的物品
|
||||
const itemTypes = ['food', 'tool', 'medical', 'weapon'];
|
||||
const unit = this.unitGroup.getFirstAlive();
|
||||
|
||||
for (let i = 0; i < itemTypes.length; i++) {
|
||||
const x = unit.x + (i - 2) * 200;
|
||||
const y = unit.y - 200;
|
||||
this.itemManager.createItemByType(itemTypes[i], x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建交互按钮
|
||||
*/
|
||||
createButtons() {
|
||||
const buttonWidth = 200;
|
||||
const buttonHeight = 40;
|
||||
const buttonY = this.sys.game.config.height - 100;
|
||||
|
||||
// 创建随机物品按钮
|
||||
this.createButton(this.sys.game.config.width - buttonWidth - 10, buttonY, '创建随机物品', () => {
|
||||
this.itemManager.createRandomItem();
|
||||
});
|
||||
|
||||
// 创建清理物品按钮
|
||||
this.createButton(this.sys.game.config.width - buttonWidth - 10, buttonY + 50, '清理所有物品', () => {
|
||||
this.itemManager.clearAllItems();
|
||||
this.itemInfoText.setText('所有物品已清理');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建按钮
|
||||
* @param {number} x - 按钮x坐标
|
||||
* @param {number} y - 按钮y坐标
|
||||
* @param {string} text - 按钮文本
|
||||
* @param {function} callback - 点击回调函数
|
||||
*/
|
||||
createButton(x, y, text, callback) {
|
||||
// 创建按钮背景
|
||||
const button = this.add.rectangle(x, y, 200, 40, 0x333333);
|
||||
button.setOrigin(0.5, 0.5);
|
||||
button.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
button.setDepth(15); // 设置深度,确保显示在场景说明文本上方
|
||||
|
||||
// 创建按钮文本
|
||||
const buttonText = this.add.text(x, y, text, {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
buttonText.setOrigin(0.5, 0.5);
|
||||
buttonText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
buttonText.setDepth(16); // 设置深度,确保显示在按钮背景上方
|
||||
|
||||
// 设置按钮交互
|
||||
button.setInteractive({
|
||||
useHandCursor: true
|
||||
});
|
||||
|
||||
// 按钮事件
|
||||
button.on('pointerdown', callback);
|
||||
|
||||
button.on('pointerover', () => {
|
||||
button.setFillStyle(0x555555);
|
||||
});
|
||||
|
||||
button.on('pointerout', () => {
|
||||
button.setFillStyle(0x333333);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示物品信息
|
||||
* @param {Item} item - 物品实例
|
||||
*/
|
||||
showItemInfo(item) {
|
||||
const unit = this.unitGroup.getFirstAlive();
|
||||
const isInRange = item.isInInteractionRange(unit);
|
||||
|
||||
const info = item.getInfo();
|
||||
let infoText = `${info.name}\n`;
|
||||
infoText += `${info.description}\n`;
|
||||
infoText += `类型: ${info.type}\n`;
|
||||
infoText += `重量: ${info.weight}kg\n`;
|
||||
infoText += `体积: ${info.volume}L\n`;
|
||||
infoText += `价值: ${info.value}金币\n`;
|
||||
infoText += `距离: ${Math.round(Math.sqrt(Math.pow(item.x - unit.x, 2) + Math.pow(item.y - unit.y, 2)))}m\n`;
|
||||
|
||||
// 显示物品属性
|
||||
infoText += '属性: ';
|
||||
const properties = [];
|
||||
for (const [key, value] of Object.entries(info.properties)) {
|
||||
properties.push(`${key}: ${value}`);
|
||||
}
|
||||
infoText += properties.join(', ');
|
||||
|
||||
this.itemInfoText.setText(infoText);
|
||||
|
||||
// 显示交互提示
|
||||
if (isInRange) {
|
||||
this.interactionText.setText('按E键拾取物品,按Q键快捷使用');
|
||||
// 注册E键拾取物品
|
||||
this.input.keyboard.once('keydown-E', () => {
|
||||
item.pickup(unit);
|
||||
this.interactionText.setText('');
|
||||
this.itemInfoText.setText('未选择任何物品');
|
||||
});
|
||||
// 注册Q键快捷使用物品
|
||||
this.input.keyboard.once('keydown-Q', () => {
|
||||
item.use(unit);
|
||||
this.interactionText.setText('');
|
||||
this.itemInfoText.setText('未选择任何物品');
|
||||
});
|
||||
} else {
|
||||
this.interactionText.setText('距离太远,无法交互');
|
||||
}
|
||||
|
||||
// 移除5秒后自动清除的逻辑,让信息保持显示
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建侧边栏UI
|
||||
*/
|
||||
createSidebar() {
|
||||
// 侧边栏宽度
|
||||
const sidebarWidth = 300;
|
||||
const sidebarDepth = 5; // 侧边栏深度值,确保显示在游戏元素上方,但低于场景说明文本
|
||||
|
||||
// 创建侧边栏背景
|
||||
this.sidebar = this.add.rectangle(
|
||||
this.sys.game.config.width - sidebarWidth,
|
||||
0,
|
||||
sidebarWidth,
|
||||
this.sys.game.config.height,
|
||||
0x111111,
|
||||
0.9
|
||||
);
|
||||
this.sidebar.setOrigin(0, 0);
|
||||
this.sidebar.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.sidebar.setDepth(sidebarDepth); // 设置深度,确保显示在游戏元素上方
|
||||
|
||||
// 添加侧边栏边框
|
||||
this.sidebarBorder = this.add.rectangle(
|
||||
this.sys.game.config.width - sidebarWidth,
|
||||
0,
|
||||
sidebarWidth,
|
||||
this.sys.game.config.height,
|
||||
0x444444,
|
||||
0
|
||||
);
|
||||
this.sidebarBorder.setOrigin(0, 0);
|
||||
this.sidebarBorder.setStrokeStyle(2, 0x444444, 1);
|
||||
this.sidebarBorder.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.sidebarBorder.setDepth(sidebarDepth + 1); // 设置深度,确保显示在侧边栏背景上方
|
||||
|
||||
// 侧边栏标题
|
||||
const title = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
20,
|
||||
'游戏信息面板',
|
||||
{
|
||||
fontSize: '20px',
|
||||
fill: '#ffffff',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
title.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
title.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 通用信息区域标题
|
||||
const generalTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
50,
|
||||
'通用信息:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#4CAF50',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
generalTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
generalTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.generalInfoText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
75,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.generalInfoText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.generalInfoText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 角色属性区域标题
|
||||
const characterTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
120,
|
||||
'角色属性:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#2196F3',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
characterTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
characterTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.unitStatusText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
145,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.unitStatusText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.unitStatusText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 交互提示区域标题
|
||||
const interactionTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
220,
|
||||
'交互提示:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#FFEB3B',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
interactionTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
interactionTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.interactionText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
245,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffff00',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.interactionText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.interactionText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
// 物品详情区域标题
|
||||
const itemTitle = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
350,
|
||||
'物品详情:',
|
||||
{
|
||||
fontSize: '16px',
|
||||
fill: '#FF9800',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
);
|
||||
itemTitle.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
itemTitle.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
|
||||
this.itemInfoText = this.add.text(
|
||||
this.sys.game.config.width - sidebarWidth + 20,
|
||||
375,
|
||||
'',
|
||||
{
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
wordWrap: {
|
||||
width: sidebarWidth - 40
|
||||
}
|
||||
}
|
||||
);
|
||||
this.itemInfoText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动
|
||||
this.itemInfoText.setDepth(sidebarDepth + 2); // 设置深度,确保显示在侧边栏边框上方
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单位状态显示
|
||||
* @param {Unit} unit - 单位实例
|
||||
*/
|
||||
updateUnitStatus(unit) {
|
||||
let statusText = `${unit.name} 状态:\n`;
|
||||
statusText += `生命值: ${unit.health}\n`;
|
||||
statusText += `营养值: ${unit.nutrition}\n`;
|
||||
statusText += `水分值: ${unit.hydration}\n`;
|
||||
if (unit.weapon) {
|
||||
statusText += `武器: ${unit.weapon.name}`;
|
||||
}
|
||||
|
||||
this.unitStatusText.setText(statusText);
|
||||
}
|
||||
|
||||
update() {
|
||||
this.inputSystem.update();
|
||||
|
||||
// 更新所有单位
|
||||
this.unitGroup.getChildren().forEach(unit => {
|
||||
if (unit.update) {
|
||||
unit.update();
|
||||
}
|
||||
unit.updateMovement();
|
||||
// 更新单位状态显示
|
||||
this.updateUnitStatus(unit);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// 测试场景:虚拟摇杆控制
|
||||
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 JoystickMovementTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'JoystickMovementTestScene' });
|
||||
}
|
||||
|
||||
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, '测试场景:虚拟摇杆控制', {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// 测试场景:相对移动(键盘控制)
|
||||
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 KeyboardMovementTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'KeyboardMovementTestScene' });
|
||||
}
|
||||
|
||||
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, '测试场景:相对移动(键盘控制)', {
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,922 @@
|
||||
// 地图编辑测试场景
|
||||
// 防御性编程原则:
|
||||
// 1. 始终检查对象和方法是否存在
|
||||
// 2. 检查参数类型和范围
|
||||
// 3. 使用 try-catch 块捕获可能的异常
|
||||
// 4. 为所有容器和对象使用明确的命名
|
||||
// 5. 避免使用相同的键名,防止冲突
|
||||
// 6. 始终检查纹理是否存在
|
||||
// 7. 在使用方法前检查方法是否存在
|
||||
// 8. 添加详细的日志和错误信息
|
||||
// 9. 为复杂逻辑添加注释
|
||||
// 10. 使用类型检查和断言
|
||||
|
||||
export default class MapEditSinglePicTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'MapEditSinglePicTestScene' });
|
||||
this.tileSize = 16; // 地图瓦片大小
|
||||
this.selectedTile = null; // 当前选中的瓦片
|
||||
this.mapLayer = null; // 地图层
|
||||
this.mapWidth = 50; // 地图宽度(瓦片数量)
|
||||
this.mapHeight = 50; // 地图高度(瓦片数量)
|
||||
this.isDragging = false; // 是否正在拖拽地图
|
||||
this.lastDragPosition = { x: 0, y: 0 }; // 上次拖拽位置
|
||||
this.zoomLevel = 1; // 缩放级别
|
||||
// 瓦片图相关属性
|
||||
this.tilemapKey = ''; // 单张瓦片图的键名
|
||||
this.tileCount = 234; // 总瓦片数
|
||||
this.tileImageSize = 16; // 瓦片在图片中的实际大小
|
||||
this.tileSpacing = 0; // 瓦片间距
|
||||
this.tileColumns = 18; // 瓦片图列数
|
||||
this.tileRows = 13; // 瓦片图行数
|
||||
}
|
||||
|
||||
preload() {
|
||||
// 加载瓦片资源
|
||||
this.loadTileAssets();
|
||||
|
||||
// 显示加载进度
|
||||
this.createLoadingUI();
|
||||
}
|
||||
|
||||
create() {
|
||||
console.log('开始创建地图编辑场景');
|
||||
// 物理世界和相机边界
|
||||
const worldSize = this.mapWidth * this.tileSize;
|
||||
this.physics.world.setBounds(0, 0, worldSize, worldSize);
|
||||
this.cameras.main.setBounds(0, 0, worldSize, worldSize);
|
||||
|
||||
// 设置背景色
|
||||
this.cameras.main.setBackgroundColor('#222222');
|
||||
|
||||
// 修复:移除可能有问题的defaultFilterMode设置,使用资源加载完成后设置过滤模式
|
||||
|
||||
// 创建地图编辑区域
|
||||
console.log('创建地图编辑区域');
|
||||
this.createMapArea();
|
||||
|
||||
// 创建瓦片选择区域
|
||||
console.log('创建瓦片选择区域');
|
||||
this.createTileSelectionArea();
|
||||
|
||||
// 检查资源是否已加载
|
||||
if (this.assetsLoaded) {
|
||||
console.log('资源已加载,调用updateTileSelection');
|
||||
this.updateTileSelection();
|
||||
} else {
|
||||
console.log('资源尚未加载,等待加载完成');
|
||||
}
|
||||
|
||||
// 创建控制面板
|
||||
console.log('创建控制面板');
|
||||
this.createControlPanel();
|
||||
|
||||
// 设置输入处理
|
||||
console.log('设置输入处理');
|
||||
this.setupInput();
|
||||
|
||||
// 显示提示信息
|
||||
console.log('显示提示信息');
|
||||
this.showInstructions();
|
||||
|
||||
console.log('地图编辑场景创建完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载瓦片资源
|
||||
*/
|
||||
loadTileAssets() {
|
||||
console.log('开始加载瓦片资源');
|
||||
|
||||
// 只加载单张瓦片图,替代多张瓦片资源
|
||||
const tilemapKey = 'mapedit-tilemap';
|
||||
const tilemapPath = 'assets/PNG/Tiles/Tilemap/tilemap_packed.png';
|
||||
console.log(`加载瓦片图: ${tilemapKey} -> ${tilemapPath}`);
|
||||
this.load.image(tilemapKey, tilemapPath);
|
||||
|
||||
// 加载进度回调
|
||||
this.load.on('progress', (progress) => {
|
||||
console.log(`资源加载进度: ${Math.round(progress * 100)}%`);
|
||||
});
|
||||
|
||||
// 文件加载完成回调
|
||||
this.load.on('filecomplete', (key, type, data) => {
|
||||
console.log(`文件加载完成: ${key} (${type})`);
|
||||
});
|
||||
|
||||
// 加载完成回调
|
||||
this.load.on('complete', () => {
|
||||
console.log('所有资源加载完成!');
|
||||
this.assetsLoaded = true;
|
||||
this.loadingText.setText('资源加载完成!');
|
||||
|
||||
// 设置瓦片图的过滤模式为NEAREST
|
||||
if (this.textures.exists(tilemapKey)) {
|
||||
const texture = this.textures.get(tilemapKey);
|
||||
texture.setFilter(Phaser.Textures.FilterMode.NEAREST);
|
||||
console.log(`设置纹理 ${tilemapKey} 的过滤模式为NEAREST`);
|
||||
}
|
||||
|
||||
// 初始化瓦片集合 - 只使用单张瓦片图键名
|
||||
this.tileCount = 234; // 根据Tilesheet.txt,总瓦片数为18x13=234
|
||||
this.tilemapKey = tilemapKey;
|
||||
// 存储瓦片的位置信息,用于后续切割
|
||||
this.tileImageSize = 16; // 瓦片大小16x16px
|
||||
this.tileSpacing = 0; // 瓦片间距0px
|
||||
this.tileColumns = 18; // 18列
|
||||
this.tileRows = 13; // 13行
|
||||
|
||||
console.log(`瓦片图初始化完成,共 ${this.tileCount} 个瓦片`);
|
||||
// 只有在tileSelectionContainer初始化后才调用updateTileSelection
|
||||
if (this.tileSelectionContainer) {
|
||||
console.log('tileSelectionContainer已初始化,调用updateTileSelection');
|
||||
this.updateTileSelection();
|
||||
} else {
|
||||
console.log('tileSelectionContainer尚未初始化,等待create方法调用');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建加载UI
|
||||
*/
|
||||
createLoadingUI() {
|
||||
// 加载文本
|
||||
this.loadingText = this.add.text(10, 10, '加载瓦片资源...', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
this.loadingText.setScrollFactor(0, 0);
|
||||
this.loadingText.setDepth(100);
|
||||
|
||||
// 加载进度条
|
||||
this.progressBar = this.add.rectangle(10, 40, 300, 20, 0x555555);
|
||||
this.progressBar.setOrigin(0, 0);
|
||||
this.progressBar.setScrollFactor(0, 0);
|
||||
this.progressBar.setDepth(100);
|
||||
|
||||
this.progressFill = this.add.rectangle(10, 40, 0, 20, 0x00ff00);
|
||||
this.progressFill.setOrigin(0, 0);
|
||||
this.progressFill.setScrollFactor(0, 0);
|
||||
this.progressFill.setDepth(100);
|
||||
|
||||
// 更新进度
|
||||
this.load.on('progress', (progress) => {
|
||||
this.progressFill.width = progress * 300;
|
||||
this.loadingText.setText(`加载瓦片资源... ${Math.round(progress * 100)}%`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建地图编辑区域
|
||||
*/
|
||||
createMapArea() {
|
||||
// 创建地图容器
|
||||
this.mapContainer = this.add.container(0, 0);
|
||||
this.mapContainer.setDepth(1);
|
||||
|
||||
// 创建背景网格
|
||||
this.createGrid();
|
||||
|
||||
// 创建地图层(用于放置瓦片)
|
||||
this.mapLayer = this.add.container(0, 0);
|
||||
this.mapContainer.add(this.mapLayer);
|
||||
|
||||
// 初始化空地图
|
||||
this.mapData = [];
|
||||
for (let y = 0; y < this.mapHeight; y++) {
|
||||
this.mapData[y] = [];
|
||||
for (let x = 0; x < this.mapWidth; x++) {
|
||||
this.mapData[y][x] = -1; // -1表示空瓦片
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建背景网格
|
||||
*/
|
||||
createGrid() {
|
||||
// 创建网格线
|
||||
const gridGraphics = this.add.graphics();
|
||||
gridGraphics.lineStyle(1, 0x444444, 0.5);
|
||||
|
||||
// 绘制垂直线
|
||||
for (let x = 0; x <= this.mapWidth; x++) {
|
||||
gridGraphics.moveTo(x * this.tileSize, 0);
|
||||
gridGraphics.lineTo(x * this.tileSize, this.mapHeight * this.tileSize);
|
||||
}
|
||||
|
||||
// 绘制水平线
|
||||
for (let y = 0; y <= this.mapHeight; y++) {
|
||||
gridGraphics.moveTo(0, y * this.tileSize);
|
||||
gridGraphics.lineTo(this.mapWidth * this.tileSize, y * this.tileSize);
|
||||
}
|
||||
|
||||
this.mapContainer.add(gridGraphics);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建瓦片选择区域
|
||||
*/
|
||||
createTileSelectionArea() {
|
||||
// 瓦片选择区域背景 - 使用固定大小,足够容纳完整瓦片图
|
||||
const tilePanel = this.add.rectangle(10, 70, 300, 250, 0x111111, 0.8);
|
||||
tilePanel.setOrigin(0, 0);
|
||||
tilePanel.setScrollFactor(0, 0);
|
||||
tilePanel.setDepth(10);
|
||||
|
||||
// 瓦片选择区域标题
|
||||
this.add.text(20, 80, '瓦片选择', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
}).setScrollFactor(0, 0).setDepth(11);
|
||||
|
||||
// 瓦片选择容器 - 注意:这是一个Container对象,可以使用removeAll方法
|
||||
this.tileSelectionContainer = this.add.container(20, 110);
|
||||
this.tileSelectionContainer.setScrollFactor(0, 0);
|
||||
this.tileSelectionContainer.setDepth(11);
|
||||
|
||||
// 移除网格,不再需要
|
||||
// this.tileSelectionGrid = this.add.grid(0, 0, fullWidth, fullHeight, tileWidth, tileHeight, 0x333333, 0.5, 0x444444, 0.5);
|
||||
// this.tileSelectionContainer.add(this.tileSelectionGrid);
|
||||
|
||||
// 当前选中瓦片显示 - 改为使用Container容器,而不是Rectangle,这样可以使用removeAll方法
|
||||
this.selectedTileContainer = this.add.container(20, 340);
|
||||
this.selectedTileContainer.setScrollFactor(0, 0);
|
||||
this.selectedTileContainer.setDepth(11);
|
||||
|
||||
// 添加选中瓦片背景
|
||||
const selectedTileBg = this.add.rectangle(0, 0, 16, 16, 0x333333);
|
||||
selectedTileBg.setOrigin(0, 0);
|
||||
selectedTileBg.setDepth(10);
|
||||
this.selectedTileContainer.add(selectedTileBg);
|
||||
|
||||
this.add.text(20, 320, '当前选中瓦片:', {
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}).setScrollFactor(0, 0).setDepth(11);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建控制面板
|
||||
*/
|
||||
createControlPanel() {
|
||||
// 控制面板背景
|
||||
const controlPanel = this.add.rectangle(this.sys.game.config.width - 210, 10, 200, 150, 0x111111, 0.8);
|
||||
controlPanel.setOrigin(0, 0);
|
||||
controlPanel.setScrollFactor(0, 0);
|
||||
controlPanel.setDepth(10);
|
||||
|
||||
// 控制面板标题
|
||||
this.add.text(this.sys.game.config.width - 200, 20, '控制面板', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
}).setScrollFactor(0, 0).setDepth(11);
|
||||
|
||||
// 创建按钮
|
||||
this.createButton(this.sys.game.config.width - 200, 50, 180, 30, '清空地图', () => {
|
||||
this.clearMap();
|
||||
});
|
||||
|
||||
this.createButton(this.sys.game.config.width - 200, 90, 180, 30, '保存地图', () => {
|
||||
this.saveMap();
|
||||
});
|
||||
|
||||
this.createButton(this.sys.game.config.width - 200, 130, 180, 30, '加载地图', () => {
|
||||
this.loadMap();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建按钮
|
||||
*/
|
||||
createButton(x, y, width, height, text, callback) {
|
||||
// 按钮背景
|
||||
const button = this.add.rectangle(x, y, width, height, 0x333333);
|
||||
button.setOrigin(0, 0);
|
||||
button.setScrollFactor(0, 0);
|
||||
button.setDepth(10);
|
||||
button.setInteractive({ useHandCursor: true });
|
||||
|
||||
// 按钮文本
|
||||
const buttonText = this.add.text(x + 10, y + 5, text, {
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
buttonText.setScrollFactor(0, 0);
|
||||
buttonText.setDepth(11);
|
||||
|
||||
// 按钮事件
|
||||
button.on('pointerdown', callback);
|
||||
|
||||
button.on('pointerover', () => {
|
||||
button.setFillStyle(0x555555);
|
||||
});
|
||||
|
||||
button.on('pointerout', () => {
|
||||
button.setFillStyle(0x333333);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新瓦片选择显示
|
||||
*/
|
||||
updateTileSelection() {
|
||||
console.log('开始更新瓦片选择显示');
|
||||
console.log(`当前瓦片数量: ${this.tileCount}`);
|
||||
|
||||
// 清空当前瓦片选择
|
||||
console.log('清空当前瓦片选择');
|
||||
this.tileSelectionContainer.removeAll(true);
|
||||
|
||||
try {
|
||||
// 检查纹理是否存在
|
||||
if (this.textures.exists(this.tilemapKey)) {
|
||||
console.log(`纹理 ${this.tilemapKey} 存在`);
|
||||
|
||||
// 获取瓦片图的实际尺寸
|
||||
const texture = this.textures.get(this.tilemapKey);
|
||||
const frame = texture.get();
|
||||
const actualWidth = frame.width;
|
||||
const actualHeight = frame.height;
|
||||
console.log(`瓦片图实际尺寸: ${actualWidth}x${actualHeight}`);
|
||||
|
||||
// 直接添加完整瓦片图,不裁剪,不缩放
|
||||
const fullTilemap = this.add.image(0, 0, this.tilemapKey);
|
||||
fullTilemap.setOrigin(0, 0);
|
||||
fullTilemap.setScale(1);
|
||||
this.tileSelectionContainer.add(fullTilemap);
|
||||
|
||||
// 根据用户提供的精确信息:图片288*208,瓦片16*16,18*13排列
|
||||
const tileWidth = 16; // 瓦片大小16x16px
|
||||
const tileHeight = 16;
|
||||
const spacing = 0; // 瓦片之间没有间距(288/18=16,208/13=16)
|
||||
const columns = 18; // 18列
|
||||
const rows = 13; // 13行
|
||||
|
||||
console.log(`瓦片配置:大小${tileWidth}x${tileHeight},间距${spacing},${columns}x${rows}排列`);
|
||||
|
||||
// 为每个瓦片位置添加透明的交互式矩形,用于悬停高亮和点击选择
|
||||
for (let i = 0; i < this.tileCount; i++) {
|
||||
const col = i % columns;
|
||||
const row = Math.floor(i / columns);
|
||||
|
||||
// 精确计算瓦片在实际图片中的位置(无间距)
|
||||
const tileX = col * tileWidth;
|
||||
const tileY = row * tileHeight;
|
||||
|
||||
console.log(`瓦片 ${i} 索引 -> 行列: (${col}, ${row}) -> 位置: (${tileX}, ${tileY}), 大小: ${tileWidth}x${tileHeight}`);
|
||||
|
||||
// 创建透明的交互式矩形,精确对齐瓦片
|
||||
// 使用add.zone创建透明区域,专门用于交互
|
||||
const tileZone = this.add.zone(tileX, tileY, tileWidth, tileHeight);
|
||||
tileZone.setOrigin(0, 0);
|
||||
tileZone.setInteractive({ useHandCursor: true });
|
||||
this.tileSelectionContainer.add(tileZone);
|
||||
|
||||
// 瓦片点击事件
|
||||
tileZone.on('pointerdown', () => {
|
||||
console.log(`点击了瓦片 ${i},行列: (${col}, ${row})`);
|
||||
this.selectTile(i);
|
||||
});
|
||||
|
||||
// 悬停高亮效果
|
||||
tileZone.on('pointerover', () => {
|
||||
// 创建临时高亮矩形
|
||||
if (!this.highlightRect) {
|
||||
this.highlightRect = this.add.rectangle(tileX, tileY, tileWidth, tileHeight, 0xffffff, 0.3);
|
||||
this.highlightRect.setOrigin(0, 0);
|
||||
this.tileSelectionContainer.add(this.highlightRect);
|
||||
} else {
|
||||
this.highlightRect.setPosition(tileX, tileY);
|
||||
}
|
||||
});
|
||||
|
||||
tileZone.on('pointerout', () => {
|
||||
// 移除高亮效果
|
||||
if (this.highlightRect) {
|
||||
this.highlightRect.destroy();
|
||||
this.highlightRect = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('瓦片选择区域已更新,直接显示完整瓦片图');
|
||||
} else {
|
||||
console.error(`纹理 ${this.tilemapKey} 不存在!`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新瓦片选择显示失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择瓦片
|
||||
* @param {number} tileIndex - 瓦片索引
|
||||
*/
|
||||
selectTile(tileIndex) {
|
||||
// 防御性编程:检查参数类型
|
||||
if (typeof tileIndex !== 'number') {
|
||||
console.error('selectTile: tileIndex 必须是数字类型');
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedTile = tileIndex;
|
||||
console.log(`选择瓦片索引: ${tileIndex}`);
|
||||
|
||||
// 更新选中瓦片显示
|
||||
// 注意:使用Container对象的removeAll方法,而不是Rectangle对象
|
||||
try {
|
||||
// 防御性编程:检查容器是否存在
|
||||
if (this.selectedTileContainer && typeof this.selectedTileContainer.removeAll === 'function') {
|
||||
this.selectedTileContainer.removeAll(true);
|
||||
} else {
|
||||
console.error('selectTile: selectedTileContainer 不存在或没有 removeAll 方法');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('selectTile: 清空选中瓦片容器失败:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedTile !== null && this.selectedTile !== -1) {
|
||||
try {
|
||||
// 防御性编程:检查瓦片索引是否在有效范围内
|
||||
if (this.selectedTile >= 0 && this.selectedTile < this.tileCount) {
|
||||
// 防御性编程:检查纹理是否存在
|
||||
if (this.textures.exists(this.tilemapKey)) {
|
||||
// 根据用户提供的精确信息:瓦片16*16,无间距,18*13排列
|
||||
const tileWidth = 16;
|
||||
const tileHeight = 16;
|
||||
const spacing = 0;
|
||||
const columns = 18;
|
||||
|
||||
// 计算瓦片在大图中的位置
|
||||
const col = this.selectedTile % columns;
|
||||
const row = Math.floor(this.selectedTile / columns);
|
||||
|
||||
// 计算裁剪区域 - 正确使用图块对应的左上角像素位置
|
||||
const cropWidth = tileWidth;
|
||||
const cropHeight = tileHeight;
|
||||
|
||||
// 计算图块在大图中的精确左上角像素位置
|
||||
const frameX = col * tileWidth;
|
||||
const frameY = row * tileHeight;
|
||||
|
||||
// 创建一个新的image,专门用于显示选中的瓦片
|
||||
const tile = this.add.image(0, 0, this.tilemapKey);
|
||||
tile.setOrigin(0, 0);
|
||||
|
||||
// 先设置裁剪区域,使用图块在大图中的精确位置作为起始点
|
||||
tile.setCrop(frameX, frameY, cropWidth, cropHeight);
|
||||
|
||||
// 调整瓦片位置,使其相对于容器(0,0)位置显示,而不是原图像位置
|
||||
tile.setPosition(-frameX*2, -frameY*2);
|
||||
|
||||
// 然后设置缩放,放大显示
|
||||
tile.setScale(2);
|
||||
|
||||
this.selectedTileContainer.add(tile);
|
||||
|
||||
// 添加选中边框,匹配放大后的瓦片大小
|
||||
const border = this.add.rectangle(0, 0, 64, 64, 0x00ff00, 0);
|
||||
border.setOrigin(0, 0);
|
||||
border.setStrokeStyle(2, 0x00ff00, 1);
|
||||
this.selectedTileContainer.add(border);
|
||||
} else {
|
||||
console.error(`selectTile: 纹理 ${this.tilemapKey} 不存在`);
|
||||
}
|
||||
} else {
|
||||
console.error(`selectTile: 瓦片索引 ${this.selectedTile} 超出范围 (0-${this.tileCount-1})`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('selectTile: 显示选中瓦片失败:', error);
|
||||
}
|
||||
} else if (this.selectedTile === -1) {
|
||||
// 显示删除模式
|
||||
try {
|
||||
const deleteText = this.add.text(0, 20, '删除模式', {
|
||||
fontSize: '14px',
|
||||
fill: '#ff0000',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
});
|
||||
deleteText.setOrigin(0, 0);
|
||||
this.selectedTileContainer.add(deleteText);
|
||||
} catch (error) {
|
||||
console.error('selectTile: 显示删除模式失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入处理
|
||||
*/
|
||||
setupInput() {
|
||||
// 初始化拖动绘制相关变量
|
||||
this.isDrawing = false;
|
||||
this.lastDrawnTile = null;
|
||||
|
||||
// 鼠标输入
|
||||
this.input.on('pointerdown', (pointer) => {
|
||||
this.handleMouseDown(pointer);
|
||||
// 开始绘制
|
||||
this.isDrawing = true;
|
||||
this.lastDrawnTile = null;
|
||||
});
|
||||
|
||||
this.input.on('pointerup', (pointer) => {
|
||||
this.handleMouseUp(pointer);
|
||||
// 停止绘制
|
||||
this.isDrawing = false;
|
||||
this.lastDrawnTile = null;
|
||||
});
|
||||
|
||||
this.input.on('pointermove', (pointer) => {
|
||||
this.handleMouseMove(pointer);
|
||||
// 拖动绘制:如果鼠标按下且不是在拖拽地图,则绘制瓦片
|
||||
if (this.isDrawing && !this.isDragging) {
|
||||
this.placeTile(pointer);
|
||||
}
|
||||
});
|
||||
|
||||
// 滚轮缩放
|
||||
this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => {
|
||||
this.handleWheel(deltaY);
|
||||
});
|
||||
|
||||
// 键盘输入
|
||||
this.setupKeyboardInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置键盘输入
|
||||
*/
|
||||
setupKeyboardInput() {
|
||||
// 空格键返回启动场景
|
||||
this.input.keyboard.on('keydown-SPACE', () => {
|
||||
this.scene.start('BootScene');
|
||||
});
|
||||
|
||||
// 数字键1-9选择瓦片
|
||||
for (let i = 0; i < 9; i++) {
|
||||
this.input.keyboard.on(`keydown-${i + 1}`, () => {
|
||||
if (i < this.tileSet.length) {
|
||||
this.selectTile(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// D键删除瓦片
|
||||
this.input.keyboard.on('keydown-D', () => {
|
||||
this.selectedTile = -1; // -1表示删除模式
|
||||
});
|
||||
|
||||
// C键清空地图
|
||||
this.input.keyboard.on('keydown-C', () => {
|
||||
this.clearMap();
|
||||
});
|
||||
|
||||
// S键保存地图
|
||||
this.input.keyboard.on('keydown-S', () => {
|
||||
this.saveMap();
|
||||
});
|
||||
|
||||
// L键加载地图
|
||||
this.input.keyboard.on('keydown-L', () => {
|
||||
this.loadMap();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标按下事件
|
||||
*/
|
||||
handleMouseDown(pointer) {
|
||||
// 检查是否点击了瓦片选择区域
|
||||
const tileSelectionRect = new Phaser.Geom.Rectangle(20, 110, 200, 360);
|
||||
if (Phaser.Geom.Rectangle.Contains(tileSelectionRect, pointer.x, pointer.y)) {
|
||||
return; // 点击了瓦片选择区域,不处理地图交互
|
||||
}
|
||||
|
||||
// 检查是否点击了控制面板
|
||||
const controlPanelRect = new Phaser.Geom.Rectangle(this.sys.game.config.width - 210, 10, 200, 150);
|
||||
if (Phaser.Geom.Rectangle.Contains(controlPanelRect, pointer.x, pointer.y)) {
|
||||
return; // 点击了控制面板,不处理地图交互
|
||||
}
|
||||
|
||||
// 检查是否点击了当前选中瓦片显示区域
|
||||
const selectedTileRect = new Phaser.Geom.Rectangle(20, 490, 64, 64);
|
||||
if (Phaser.Geom.Rectangle.Contains(selectedTileRect, pointer.x, pointer.y)) {
|
||||
return; // 点击了选中瓦片显示区域,不处理地图交互
|
||||
}
|
||||
|
||||
// 如果点击了地图区域
|
||||
if (this.selectedTile !== null) {
|
||||
// 放置或删除瓦片
|
||||
this.placeTile(pointer);
|
||||
} else {
|
||||
// 开始拖拽地图
|
||||
this.isDragging = true;
|
||||
this.lastDragPosition.x = pointer.x;
|
||||
this.lastDragPosition.y = pointer.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标释放事件
|
||||
*/
|
||||
handleMouseUp(pointer) {
|
||||
this.isDragging = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标移动事件
|
||||
*/
|
||||
handleMouseMove(pointer) {
|
||||
if (this.isDragging) {
|
||||
// 计算拖拽距离
|
||||
const deltaX = pointer.x - this.lastDragPosition.x;
|
||||
const deltaY = pointer.y - this.lastDragPosition.y;
|
||||
|
||||
// 移动相机
|
||||
this.cameras.main.scrollX -= deltaX / this.zoomLevel;
|
||||
this.cameras.main.scrollY -= deltaY / this.zoomLevel;
|
||||
|
||||
// 更新上次拖拽位置
|
||||
this.lastDragPosition.x = pointer.x;
|
||||
this.lastDragPosition.y = pointer.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理滚轮事件
|
||||
*/
|
||||
handleWheel(deltaY) {
|
||||
// 放大
|
||||
if (deltaY < 0 && this.zoomLevel < 2) {
|
||||
this.zoomLevel += 0.1;
|
||||
}
|
||||
// 缩小
|
||||
else if (deltaY > 0 && this.zoomLevel > 0.5) {
|
||||
this.zoomLevel -= 0.1;
|
||||
}
|
||||
|
||||
// 更新相机缩放
|
||||
this.cameras.main.setZoom(this.zoomLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 放置瓦片
|
||||
*/
|
||||
placeTile(pointer) {
|
||||
// 将屏幕坐标转换为世界坐标 - 确保与鼠标位置完全匹配
|
||||
const worldPoint = this.cameras.main.getWorldPoint(pointer.x, pointer.y);
|
||||
|
||||
// 计算瓦片坐标 - 使用地图的瓦片大小32px
|
||||
const mapTileSize = 32;
|
||||
|
||||
// 确保鼠标点击位置与绘制位置完全一致
|
||||
// 使用Math.floor确保点击瓦片的哪个位置都绘制到对应瓦片
|
||||
const tileX = Math.floor(worldPoint.x / mapTileSize);
|
||||
const tileY = Math.floor(worldPoint.y / mapTileSize);
|
||||
|
||||
// 检查瓦片坐标是否在地图范围内
|
||||
if (tileX < 0 || tileX >= this.mapWidth || tileY < 0 || tileY >= this.mapHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已经绘制过这个位置,避免重复绘制
|
||||
if (this.lastDrawnTile && this.lastDrawnTile.x === tileX && this.lastDrawnTile.y === tileY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 删除瓦片模式
|
||||
if (this.selectedTile === -1) {
|
||||
this.removeTile(tileX, tileY);
|
||||
} else {
|
||||
// 放置瓦片 - 使用单张瓦片图
|
||||
|
||||
// 检查该位置是否已有瓦片
|
||||
const existingTile = this.getTileAt(tileX, tileY);
|
||||
if (existingTile) {
|
||||
// 删除现有瓦片
|
||||
existingTile.destroy();
|
||||
}
|
||||
|
||||
// 计算精确的绘制位置 - 确保与鼠标点击的瓦片完全对应
|
||||
const drawX = tileX * mapTileSize;
|
||||
const drawY = tileY * mapTileSize;
|
||||
|
||||
console.log(`鼠标位置: (${pointer.x}, ${pointer.y}) -> 世界坐标: (${worldPoint.x}, ${worldPoint.y}) -> 瓦片坐标: (${tileX}, ${tileY}) -> 绘制位置: (${drawX}, ${drawY})`);
|
||||
|
||||
// 创建新瓦片 - 精确放置在计算出的位置
|
||||
try {
|
||||
// 根据用户提供的精确信息:瓦片16*16,无间距,18*13排列
|
||||
const tileWidth = 16;
|
||||
const tileHeight = 16;
|
||||
const columns = 18;
|
||||
|
||||
// 计算瓦片在大图中的位置
|
||||
const col = this.selectedTile % columns;
|
||||
const row = Math.floor(this.selectedTile / columns);
|
||||
|
||||
// 计算裁剪区域 - 正确使用图块对应的左上角像素位置
|
||||
const cropX = col * tileWidth; // 图块在大图中的x坐标
|
||||
const cropY = row * tileHeight; // 图块在大图中的y坐标
|
||||
const cropWidth = tileWidth;
|
||||
const cropHeight = tileHeight;
|
||||
|
||||
console.log(`选中瓦片索引: ${this.selectedTile} -> 行列: (${col}, ${row}) -> 裁剪区域: (${cropX}, ${cropY}, ${cropWidth}, ${cropHeight})`);
|
||||
|
||||
// 创建image并设置初始位置
|
||||
const tile = this.add.image(drawX, drawY, this.tilemapKey);
|
||||
tile.setOrigin(0, 0);
|
||||
|
||||
// 先设置裁剪区域,使用图块在大图中的精确位置
|
||||
tile.setCrop(cropX, cropY, cropWidth, cropHeight);
|
||||
|
||||
// 调整瓦片位置,使其相对于目标位置(0,0)显示,而不是原图像位置
|
||||
tile.setPosition(drawX - cropX*2, drawY - cropY*2);
|
||||
|
||||
// 然后设置缩放,确保瓦片填满地图网格
|
||||
const scaleFactor = mapTileSize / cropWidth;
|
||||
tile.setScale(scaleFactor);
|
||||
|
||||
console.log(`绘制位置: (${drawX}, ${drawY}), 缩放因子: ${scaleFactor}`);
|
||||
|
||||
tile.setDepth(2);
|
||||
this.mapLayer.add(tile);
|
||||
|
||||
// 更新地图数据
|
||||
this.mapData[tileY][tileX] = this.selectedTile;
|
||||
} catch (error) {
|
||||
console.error('放置瓦片失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 记录最后绘制的瓦片位置
|
||||
this.lastDrawnTile = { x: tileX, y: tileY };
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除瓦片
|
||||
*/
|
||||
removeTile(tileX, tileY) {
|
||||
// 检查该位置是否已有瓦片
|
||||
const existingTile = this.getTileAt(tileX, tileY);
|
||||
if (existingTile) {
|
||||
// 删除现有瓦片
|
||||
existingTile.destroy();
|
||||
|
||||
// 更新地图数据
|
||||
this.mapData[tileY][tileX] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定位置的瓦片
|
||||
*/
|
||||
getTileAt(tileX, tileY) {
|
||||
const children = this.mapLayer.getAll();
|
||||
for (const child of children) {
|
||||
const childTileX = Math.floor(child.x / this.tileSize);
|
||||
const childTileY = Math.floor(child.y / this.tileSize);
|
||||
if (childTileX === tileX && childTileY === tileY) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空地图
|
||||
*/
|
||||
clearMap() {
|
||||
// 删除所有瓦片
|
||||
this.mapLayer.removeAll(true);
|
||||
|
||||
// 重置地图数据
|
||||
for (let y = 0; y < this.mapHeight; y++) {
|
||||
for (let x = 0; x < this.mapWidth; x++) {
|
||||
this.mapData[y][x] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('地图已清空');
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存地图
|
||||
*/
|
||||
saveMap() {
|
||||
// 将地图数据转换为JSON
|
||||
const mapJson = JSON.stringify(this.mapData);
|
||||
|
||||
// 创建下载链接
|
||||
const blob = new Blob([mapJson], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'map.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
console.log('地图已保存');
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载地图
|
||||
*/
|
||||
loadMap() {
|
||||
// 创建文件选择器
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
try {
|
||||
const mapData = JSON.parse(event.target.result);
|
||||
this.loadMapData(mapData);
|
||||
} catch (error) {
|
||||
console.error('加载地图失败:', error);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载地图数据
|
||||
*/
|
||||
loadMapData(mapData) {
|
||||
// 清空当前地图
|
||||
this.clearMap();
|
||||
|
||||
// 更新地图数据
|
||||
this.mapData = mapData;
|
||||
|
||||
// 重新绘制地图
|
||||
for (let y = 0; y < this.mapHeight; y++) {
|
||||
for (let x = 0; x < this.mapWidth; x++) {
|
||||
const tileIndex = this.mapData[y][x];
|
||||
if (tileIndex !== -1 && tileIndex < this.tileSet.length) {
|
||||
const tileKey = this.tileSet[tileIndex];
|
||||
try {
|
||||
const tile = this.add.image(x * this.tileSize, y * this.tileSize, tileKey);
|
||||
tile.setOrigin(0, 0);
|
||||
tile.setDepth(2);
|
||||
this.mapLayer.add(tile);
|
||||
} catch (error) {
|
||||
console.error(`加载地图瓦片(${x},${y})失败:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('地图已加载');
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提示信息
|
||||
*/
|
||||
showInstructions() {
|
||||
const instructions = [
|
||||
'地图编辑测试场景',
|
||||
'点击瓦片选择区域选择瓦片',
|
||||
'在地图上点击放置瓦片',
|
||||
'选择瓦片后按D键进入删除模式',
|
||||
'鼠标拖拽移动地图',
|
||||
'鼠标滚轮缩放地图',
|
||||
'按C键清空地图',
|
||||
'按S键保存地图',
|
||||
'按L键加载地图',
|
||||
'按空格键返回启动场景'
|
||||
];
|
||||
|
||||
for (let i = 0; i < instructions.length; i++) {
|
||||
this.add.text(this.sys.game.config.width - 300, 180 + i * 20, instructions[i], {
|
||||
fontSize: '12px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}).setScrollFactor(0, 0).setDepth(11);
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
// 每帧更新
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,829 @@
|
||||
export default class MapEditTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'MapEditTestScene' });
|
||||
this.selectedTile = null; // 当前选中的瓦片
|
||||
this.mapLayer = null; // 地图层
|
||||
|
||||
this.mapWidth = 50; // 地图宽度(瓦片数量)
|
||||
this.mapHeight = 50; // 地图高度(瓦片数量)
|
||||
|
||||
this.tileSize = 16; // 地图瓦片大小
|
||||
this.tileSet = []; // 存储所有瓦片资源的键名
|
||||
this.tileCount = 234; // 总瓦片数(从tile_0000到tile_0233)
|
||||
|
||||
// 地形类型配置
|
||||
this.terrainTypes = {
|
||||
0: { name: '平原', color: 0x4CAF50, passable: true, speed: 1.0 },
|
||||
1: { name: '森林', color: 0x2E7D32, passable: true, speed: 0.7 },
|
||||
2: { name: '山地', color: 0x795548, passable: false, speed: 0.0 },
|
||||
3: { name: '水域', color: 0x2196F3, passable: false, speed: 0.0 },
|
||||
4: { name: '道路', color: 0x9E9E9E, passable: true, speed: 1.5 }
|
||||
};
|
||||
this.selectedTerrain = 0; // 当前选中的地形类型
|
||||
this.isTerrainMode = false; // 是否处于地形编辑模式
|
||||
}
|
||||
|
||||
preload() {
|
||||
// 加载资源
|
||||
// 加载瓦片资源
|
||||
this.loadTileAssets();
|
||||
// 显示加载进度
|
||||
this.createLoadingUI();
|
||||
// 加载完成后移除加载UI
|
||||
this.load.on('complete', () => {
|
||||
this.loadingText.destroy();
|
||||
this.progressBar.destroy();
|
||||
this.progressFill.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
// 设置输入处理
|
||||
console.log('设置输入处理');
|
||||
this.setupInput();
|
||||
|
||||
// 创建场景
|
||||
// 物理世界和相机边界
|
||||
const worldSize = this.mapWidth * this.tileSize;
|
||||
this.physics.world.setBounds(0, 0, worldSize, worldSize);
|
||||
this.cameras.main.setBounds(0, 0, worldSize, worldSize);
|
||||
// 设置背景色
|
||||
this.cameras.main.setBackgroundColor('#222222');
|
||||
|
||||
// 创建地图编辑区域
|
||||
console.log('创建地图编辑区域');
|
||||
this.createMapArea();
|
||||
|
||||
// 创建瓦片选择区域
|
||||
console.log('创建瓦片选择区域');
|
||||
this.createTileSelectionArea();
|
||||
|
||||
// 检查资源是否已加载
|
||||
if (this.assetsLoaded) {
|
||||
console.log('资源已加载,调用updateTileSelection');
|
||||
this.updateTileSelection();
|
||||
} else {
|
||||
console.log('资源尚未加载,等待加载完成');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
loadTileAssets() {
|
||||
console.log('开始加载瓦片资源');
|
||||
|
||||
// 加载Tiles目录下的多个瓦片图(从tile_0000到tile_0233)
|
||||
this.tileSet = [];
|
||||
const tileBasePath = 'assets/PNG/Tiles/Tiles/';
|
||||
|
||||
for (let i = 0; i < this.tileCount; i++) {
|
||||
const tileIndex = String(i).padStart(4, '0');
|
||||
const tileKey = `tile_${tileIndex}`;
|
||||
const tilePath = `${tileBasePath}${tileKey}.png`;
|
||||
|
||||
console.log(`加载瓦片: ${tileKey} -> ${tilePath}`);
|
||||
this.load.image(tileKey, tilePath);
|
||||
this.tileSet.push(tileKey);
|
||||
}
|
||||
|
||||
// 加载进度回调
|
||||
this.load.on('progress', (progress) => {
|
||||
console.log(`资源加载进度: ${Math.round(progress * 100)}%`);
|
||||
});
|
||||
|
||||
// 文件加载完成回调
|
||||
this.load.on('filecomplete', (key, type, data) => {
|
||||
console.log(`文件加载完成: ${key} (${type})`);
|
||||
});
|
||||
|
||||
// 加载完成回调
|
||||
this.load.on('complete', () => {
|
||||
console.log('所有资源加载完成!');
|
||||
this.assetsLoaded = true;
|
||||
this.loadingText.setText('资源加载完成!');
|
||||
|
||||
// 设置所有瓦片图的过滤模式为NEAREST
|
||||
for (const tileKey of this.tileSet) {
|
||||
if (this.textures.exists(tileKey)) {
|
||||
const texture = this.textures.get(tileKey);
|
||||
texture.setFilter(Phaser.Textures.FilterMode.NEAREST);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`瓦片图初始化完成,共 ${this.tileCount} 个瓦片`);
|
||||
// 只有在tileSelectionContainer初始化后才调用updateTileSelection
|
||||
if (this.tileSelectionContainer) {
|
||||
console.log('tileSelectionContainer已初始化,调用updateTileSelection');
|
||||
this.updateTileSelection();
|
||||
} else {
|
||||
console.log('tileSelectionContainer尚未初始化,等待create方法调用');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createLoadingUI() {
|
||||
// 加载文本
|
||||
this.loadingText = this.add.text(10, 10, '加载瓦片资源...', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
this.loadingText.setScrollFactor(0, 0);
|
||||
this.loadingText.setDepth(100);
|
||||
|
||||
// 加载进度条
|
||||
this.progressBar = this.add.rectangle(10, 40, 300, 20, 0x555555);
|
||||
this.progressBar.setOrigin(0, 0);
|
||||
this.progressBar.setScrollFactor(0, 0);
|
||||
this.progressBar.setDepth(100);
|
||||
|
||||
this.progressFill = this.add.rectangle(10, 40, 0, 20, 0x00ff00);
|
||||
this.progressFill.setOrigin(0, 0);
|
||||
this.progressFill.setScrollFactor(0, 0);
|
||||
this.progressFill.setDepth(100);
|
||||
|
||||
// 更新进度
|
||||
this.load.on('progress', (progress) => {
|
||||
this.progressFill.width = progress * 300;
|
||||
this.loadingText.setText(`加载瓦片资源... ${Math.round(progress * 100)}%`);
|
||||
});
|
||||
}
|
||||
|
||||
updateTileSelection() {
|
||||
console.log('开始更新瓦片选择显示');
|
||||
console.log(`当前瓦片数量: ${this.tileCount}`);
|
||||
|
||||
// 清空当前瓦片选择
|
||||
console.log('清空当前瓦片选择');
|
||||
this.tileSelectionContainer.removeAll(true);
|
||||
|
||||
try {
|
||||
// 定义瓦片排列方式(18列13行,与原来的单张瓦片图保持一致)
|
||||
const tileColumns = 18;
|
||||
const tileRows = 13;
|
||||
|
||||
// 为每个瓦片添加图像和交互区域
|
||||
for (let i = 0; i < this.tileCount; i++) {
|
||||
const tileKey = this.tileSet[i];
|
||||
|
||||
// 检查纹理是否存在
|
||||
if (this.textures.exists(tileKey)) {
|
||||
const col = i % tileColumns;
|
||||
const row = Math.floor(i / tileColumns);
|
||||
|
||||
// 计算瓦片位置
|
||||
const tileX = col * this.tileSize;
|
||||
const tileY = row * this.tileSize;
|
||||
|
||||
console.log(`瓦片 ${i} (${tileKey}) -> 行列: (${col}, ${row}) -> 位置: (${tileX}, ${tileY})`);
|
||||
|
||||
// 添加瓦片图像
|
||||
const tileImage = this.add.image(tileX, tileY, tileKey);
|
||||
tileImage.setOrigin(0, 0);
|
||||
tileImage.setScale(1);
|
||||
this.tileSelectionContainer.add(tileImage);
|
||||
|
||||
// 创建透明的交互式矩形,用于悬停高亮和点击选择
|
||||
const tileZone = this.add.zone(tileX, tileY, this.tileSize, this.tileSize);
|
||||
tileZone.setOrigin(0, 0);
|
||||
tileZone.setInteractive({ useHandCursor: true });
|
||||
this.tileSelectionContainer.add(tileZone);
|
||||
|
||||
// 瓦片点击事件
|
||||
tileZone.on('pointerdown', () => {
|
||||
console.log(`点击了瓦片 ${i} (${tileKey}),行列: (${col}, ${row})`);
|
||||
this.selectTile(i);
|
||||
});
|
||||
|
||||
// 悬停高亮效果
|
||||
tileZone.on('pointerover', () => {
|
||||
// 创建临时高亮矩形
|
||||
if (!this.highlightRect) {
|
||||
this.highlightRect = this.add.rectangle(tileX, tileY, this.tileSize, this.tileSize, 0xffffff, 0.3);
|
||||
this.highlightRect.setOrigin(0, 0);
|
||||
this.highlightRect.setDepth(12);
|
||||
this.tileSelectionContainer.add(this.highlightRect);
|
||||
} else {
|
||||
this.highlightRect.setPosition(tileX, tileY);
|
||||
}
|
||||
});
|
||||
|
||||
tileZone.on('pointerout', () => {
|
||||
// 移除高亮效果
|
||||
if (this.highlightRect) {
|
||||
this.highlightRect.destroy();
|
||||
this.highlightRect = null;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error(`纹理 ${tileKey} 不存在!`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('瓦片选择区域已更新,显示所有独立瓦片');
|
||||
} catch (error) {
|
||||
console.error('更新瓦片选择显示失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setupInput() {
|
||||
// 初始化拖动绘制相关变量
|
||||
this.isDrawing = false;
|
||||
this.lastDrawnTile = null;
|
||||
this.isDragging = false;
|
||||
this.lastDragPosition = { x: 0, y: 0 };
|
||||
|
||||
// 鼠标输入
|
||||
this.input.on('pointerdown', (pointer) => {
|
||||
this.handleMouseDown(pointer);
|
||||
// 开始绘制
|
||||
this.isDrawing = true;
|
||||
this.lastDrawnTile = null;
|
||||
});
|
||||
this.input.on('pointerup', (pointer) => {
|
||||
this.handleMouseUp(pointer);
|
||||
// 停止绘制
|
||||
this.isDrawing = false;
|
||||
this.lastDrawnTile = null;
|
||||
});
|
||||
this.input.on('pointermove', (pointer) => {
|
||||
this.handleMouseMove(pointer);
|
||||
// 拖动绘制:如果鼠标按下且不是在拖拽地图,则绘制瓦片
|
||||
if (this.isDrawing && !this.isDragging) {
|
||||
this.placeTile(pointer);
|
||||
}
|
||||
});
|
||||
|
||||
// 键盘输入
|
||||
// 空格键返回启动场景
|
||||
this.input.keyboard.on('keydown-SPACE', () => {
|
||||
this.scene.start('BootScene');
|
||||
});
|
||||
|
||||
// D键进入删除模式
|
||||
this.input.keyboard.on('keydown-D', () => {
|
||||
this.selectedTile = -1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标按下事件
|
||||
*/
|
||||
handleMouseDown(pointer) {
|
||||
// 检查是否点击了瓦片选择区域
|
||||
const tileSelectionRect = new Phaser.Geom.Rectangle(10, 26, 288, 208);
|
||||
if (Phaser.Geom.Rectangle.Contains(tileSelectionRect, pointer.x, pointer.y)) {
|
||||
return; // 点击了瓦片选择区域,不处理地图交互
|
||||
}
|
||||
|
||||
// 如果点击了地图区域
|
||||
if (this.selectedTile !== null) {
|
||||
// 放置或删除瓦片
|
||||
this.placeTile(pointer);
|
||||
} else {
|
||||
// 开始拖拽地图
|
||||
this.isDragging = true;
|
||||
this.lastDragPosition.x = pointer.x;
|
||||
this.lastDragPosition.y = pointer.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标释放事件
|
||||
*/
|
||||
handleMouseUp(pointer) {
|
||||
this.isDragging = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标移动事件
|
||||
*/
|
||||
handleMouseMove(pointer) {
|
||||
if (this.isDragging) {
|
||||
// 计算拖拽距离
|
||||
const deltaX = pointer.x - this.lastDragPosition.x;
|
||||
const deltaY = pointer.y - this.lastDragPosition.y;
|
||||
|
||||
// 移动相机
|
||||
this.cameras.main.scrollX -= deltaX;
|
||||
this.cameras.main.scrollY -= deltaY;
|
||||
|
||||
// 更新上次拖拽位置
|
||||
this.lastDragPosition.x = pointer.x;
|
||||
this.lastDragPosition.y = pointer.y;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
createMapArea() {
|
||||
// 创建地图容器
|
||||
this.mapContainer = this.add.container(0, 0);
|
||||
this.mapContainer.setDepth(1);
|
||||
|
||||
// 创建背景网格
|
||||
this.createGrid();
|
||||
|
||||
// 创建地图层(用于放置瓦片)
|
||||
this.mapLayer = this.add.container(0, 0);
|
||||
this.mapContainer.add(this.mapLayer);
|
||||
|
||||
// 初始化空地图,数据结构改为包含瓦片索引和地形ID的对象
|
||||
this.mapData = [];
|
||||
for (let y = 0; y < this.mapHeight; y++) {
|
||||
this.mapData[y] = [];
|
||||
for (let x = 0; x < this.mapWidth; x++) {
|
||||
this.mapData[y][x] = { tileIndex: -1, terrainId: 0 }; // tileIndex: -1表示空瓦片,terrainId: 0表示默认地形
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//创建背景网格
|
||||
createGrid() {
|
||||
// 创建网格线
|
||||
const gridGraphics = this.add.graphics();
|
||||
gridGraphics.lineStyle(1, 0x444444, 0.5);
|
||||
|
||||
// 绘制垂直线
|
||||
for (let x = 0; x <= this.mapWidth; x++) {
|
||||
gridGraphics.moveTo(x * this.tileSize, 0);
|
||||
gridGraphics.lineTo(x * this.tileSize, this.mapHeight * this.tileSize);
|
||||
}
|
||||
|
||||
// 绘制水平线
|
||||
for (let y = 0; y <= this.mapHeight; y++) {
|
||||
gridGraphics.moveTo(0, y * this.tileSize);
|
||||
gridGraphics.lineTo(this.mapWidth * this.tileSize, y * this.tileSize);
|
||||
}
|
||||
|
||||
this.mapContainer.add(gridGraphics);
|
||||
}
|
||||
|
||||
createTileSelectionArea() {
|
||||
// 瓦片选择区域位置
|
||||
const tilePanelX = 10;
|
||||
const tilePanelY = 10;
|
||||
|
||||
// 定义瓦片排列方式(18列13行,与原来的单张瓦片图保持一致)
|
||||
const tileColumns = 18;
|
||||
const tileRows = 13;
|
||||
|
||||
// 瓦片选择区域标题
|
||||
this.add.text(tilePanelX, tilePanelY, '瓦片选择', {
|
||||
fontSize: this.tileSize+'px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
}).setScrollFactor(0, 0).setDepth(11);
|
||||
|
||||
// 瓦片选择区域背景
|
||||
const tilePanel = this.add.rectangle(tilePanelX, tilePanelY+this.tileSize, tileColumns*this.tileSize, tileRows*this.tileSize, 0x111111, 0.8);
|
||||
tilePanel.setOrigin(0, 0);
|
||||
tilePanel.setScrollFactor(0, 0);
|
||||
tilePanel.setDepth(10);
|
||||
|
||||
|
||||
// 瓦片选择容器 - 注意:这是一个Container对象,可以使用removeAll方法
|
||||
this.tileSelectionContainer = this.add.container(tilePanelX, tilePanelY+this.tileSize);
|
||||
this.tileSelectionContainer.setScrollFactor(0, 0);
|
||||
this.tileSelectionContainer.setDepth(11);
|
||||
|
||||
// 当前选中瓦片显示 - 改为使用Container容器,而不是Rectangle,这样可以使用removeAll方法
|
||||
this.selectedTileContainer = this.add.container(tilePanelX, tilePanelY+(tileRows+1)*this.tileSize+16);
|
||||
this.selectedTileContainer.setScrollFactor(0, 0);
|
||||
this.selectedTileContainer.setDepth(11);
|
||||
|
||||
// 添加选中瓦片背景
|
||||
const selectedTileBg = this.add.rectangle(0, 0, 32, 32, 0x333333);
|
||||
selectedTileBg.setOrigin(0, 0);
|
||||
selectedTileBg.setDepth(10);
|
||||
this.selectedTileContainer.add(selectedTileBg);
|
||||
|
||||
this.add.text(tilePanelX, tilePanelY+(tileRows+1)*this.tileSize, '当前选中瓦片:', {
|
||||
fontSize: this.tileSize+'px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}).setScrollFactor(0, 0).setDepth(11);
|
||||
|
||||
// 地形选择区域
|
||||
const terrainPanelY = tilePanelY+(tileRows+1)*this.tileSize+64;
|
||||
|
||||
// 地形选择标题
|
||||
this.add.text(tilePanelX, terrainPanelY, '地形类型', {
|
||||
fontSize: this.tileSize+'px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
}).setScrollFactor(0, 0).setDepth(11);
|
||||
|
||||
// 地形选择容器
|
||||
this.terrainSelectionContainer = this.add.container(tilePanelX, terrainPanelY+this.tileSize);
|
||||
this.terrainSelectionContainer.setScrollFactor(0, 0);
|
||||
this.terrainSelectionContainer.setDepth(11);
|
||||
|
||||
// 添加地形选择按钮
|
||||
let terrainX = 0;
|
||||
let terrainY = 0;
|
||||
const terrainButtonSize = 24;
|
||||
const terrainSpacing = 8;
|
||||
|
||||
for (const [id, terrain] of Object.entries(this.terrainTypes)) {
|
||||
// 地形按钮背景
|
||||
const terrainButton = this.add.rectangle(terrainX, terrainY, terrainButtonSize, terrainButtonSize, terrain.color, 0.8);
|
||||
terrainButton.setOrigin(0, 0);
|
||||
terrainButton.setInteractive({ useHandCursor: true });
|
||||
terrainButton.setData('terrainId', parseInt(id));
|
||||
this.terrainSelectionContainer.add(terrainButton);
|
||||
|
||||
// 地形按钮文本
|
||||
this.terrainSelectionContainer.add(this.add.text(
|
||||
terrainX + 4,
|
||||
terrainY + 2,
|
||||
id,
|
||||
{
|
||||
fontSize: '12px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
).setOrigin(0, 0));
|
||||
|
||||
// 地形按钮点击事件
|
||||
terrainButton.on('pointerdown', () => {
|
||||
this.selectTerrain(terrainButton.getData('terrainId'));
|
||||
});
|
||||
|
||||
// 更新位置
|
||||
terrainX += terrainButtonSize + terrainSpacing;
|
||||
if (terrainX >= 200) {
|
||||
terrainX = 0;
|
||||
terrainY += terrainButtonSize + terrainSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
// 当前选中地形显示
|
||||
this.selectedTerrainText = this.add.text(
|
||||
tilePanelX,
|
||||
terrainPanelY+this.tileSize+terrainY+20,
|
||||
`当前地形: ${this.terrainTypes[this.selectedTerrain].name}`,
|
||||
{
|
||||
fontSize: '12px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}
|
||||
).setScrollFactor(0, 0).setDepth(11);
|
||||
|
||||
// 地形模式切换按钮
|
||||
const terrainModeY = terrainPanelY+this.tileSize+terrainY+40;
|
||||
this.terrainModeButton = this.add.rectangle(tilePanelX, terrainModeY, 120, 24, 0x333333, 0.8);
|
||||
this.terrainModeButton.setOrigin(0, 0);
|
||||
this.terrainModeButton.setInteractive({ useHandCursor: true });
|
||||
this.terrainModeButton.setDepth(11);
|
||||
|
||||
// 地形模式按钮文本
|
||||
this.terrainModeText = this.add.text(
|
||||
tilePanelX + 10,
|
||||
terrainModeY + 4,
|
||||
'切换到地形模式',
|
||||
{
|
||||
fontSize: '12px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}
|
||||
).setScrollFactor(0, 0).setDepth(12);
|
||||
|
||||
// 地形模式切换事件
|
||||
this.terrainModeButton.on('pointerdown', () => {
|
||||
this.toggleTerrainMode();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择地形
|
||||
*/
|
||||
selectTerrain(terrainId) {
|
||||
this.selectedTerrain = terrainId;
|
||||
this.selectedTerrainText.setText(`当前地形: ${this.terrainTypes[this.selectedTerrain].name}`);
|
||||
console.log(`选择了地形: ${this.terrainTypes[this.selectedTerrain].name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换地形模式
|
||||
*/
|
||||
toggleTerrainMode() {
|
||||
this.isTerrainMode = !this.isTerrainMode;
|
||||
|
||||
if (this.isTerrainMode) {
|
||||
this.terrainModeButton.setFillStyle(0x00ff00, 0.8);
|
||||
this.terrainModeText.setText('地形模式: 开启');
|
||||
this.terrainModeText.setFill('#00ff00');
|
||||
} else {
|
||||
this.terrainModeButton.setFillStyle(0x333333, 0.8);
|
||||
this.terrainModeText.setText('切换到地形模式');
|
||||
this.terrainModeText.setFill('#ffffff');
|
||||
}
|
||||
|
||||
console.log(`地形模式已${this.isTerrainMode ? '开启' : '关闭'}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择瓦片
|
||||
* @param {number} tileIndex - 瓦片索引
|
||||
*/
|
||||
selectTile(tileIndex) {
|
||||
// 防御性编程:检查参数类型
|
||||
if (typeof tileIndex !== 'number') {
|
||||
console.error('selectTile: tileIndex 必须是数字类型');
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedTile = tileIndex;
|
||||
console.log(`选择瓦片索引: ${tileIndex}`);
|
||||
|
||||
// 更新选中瓦片显示
|
||||
// 注意:使用Container对象的removeAll方法,而不是Rectangle对象
|
||||
try {
|
||||
// 防御性编程:检查容器是否存在
|
||||
if (this.selectedTileContainer && typeof this.selectedTileContainer.removeAll === 'function') {
|
||||
this.selectedTileContainer.removeAll(true);
|
||||
} else {
|
||||
console.error('selectTile: selectedTileContainer 不存在或没有 removeAll 方法');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('selectTile: 清空选中瓦片容器失败:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedTile !== null && this.selectedTile !== -1) {
|
||||
try {
|
||||
// 防御性编程:检查瓦片索引是否在有效范围内
|
||||
if (this.selectedTile >= 0 && this.selectedTile < this.tileCount) {
|
||||
const tileKey = this.tileSet[this.selectedTile];
|
||||
|
||||
// 防御性编程:检查纹理是否存在
|
||||
if (this.textures.exists(tileKey)) {
|
||||
// 创建一个新的image,专门用于显示选中的瓦片
|
||||
const tile = this.add.image(0, 0, tileKey);
|
||||
tile.setOrigin(0, 0);
|
||||
|
||||
// 放大显示
|
||||
tile.setScale(2);
|
||||
|
||||
this.selectedTileContainer.add(tile);
|
||||
|
||||
// 添加选中边框,匹配放大后的瓦片大小
|
||||
const border = this.add.rectangle(0, 0, this.tileSize*2, this.tileSize*2, 0x00ff00, 0);
|
||||
border.setOrigin(0, 0);
|
||||
border.setStrokeStyle(2, 0x00ff00, 1);
|
||||
this.selectedTileContainer.add(border);
|
||||
|
||||
// 显示瓦片索引和行列信息
|
||||
const columns = 18; // 瓦片排列列数
|
||||
const row = Math.floor(this.selectedTile / columns);
|
||||
const col = this.selectedTile % columns;
|
||||
|
||||
// 瓦片索引文本
|
||||
this.selectedTileContainer.add(this.add.text(
|
||||
40, 0,
|
||||
`索引: ${this.selectedTile}`,
|
||||
{
|
||||
fontSize: '12px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}
|
||||
).setOrigin(0, 0));
|
||||
|
||||
// 瓦片行列文本
|
||||
this.selectedTileContainer.add(this.add.text(
|
||||
40, 16,
|
||||
`行列: (${col},${row})`,
|
||||
{
|
||||
fontSize: '12px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}
|
||||
).setOrigin(0, 0));
|
||||
} else {
|
||||
console.error(`selectTile: 纹理 ${tileKey} 不存在`);
|
||||
}
|
||||
} else {
|
||||
console.error(`selectTile: 瓦片索引 ${this.selectedTile} 超出范围 (0-${this.tileCount-1})`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('selectTile: 显示选中瓦片失败:', error);
|
||||
}
|
||||
} else if (this.selectedTile === -1) {
|
||||
// 显示删除模式
|
||||
try {
|
||||
const deleteText = this.add.text(0, 20, '删除模式', {
|
||||
fontSize: '14px',
|
||||
fill: '#ff0000',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
});
|
||||
deleteText.setOrigin(0, 0);
|
||||
this.selectedTileContainer.add(deleteText);
|
||||
} catch (error) {
|
||||
console.error('selectTile: 显示删除模式失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 放置瓦片或设置地形
|
||||
*/
|
||||
placeTile(pointer) {
|
||||
// 将屏幕坐标转换为世界坐标
|
||||
const worldPoint = this.cameras.main.getWorldPoint(pointer.x, pointer.y);
|
||||
|
||||
// 计算瓦片坐标
|
||||
const tileX = Math.floor(worldPoint.x / this.tileSize);
|
||||
const tileY = Math.floor(worldPoint.y / this.tileSize);
|
||||
|
||||
// 检查瓦片坐标是否在地图范围内
|
||||
if (tileX < 0 || tileX >= this.mapWidth || tileY < 0 || tileY >= this.mapHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已经绘制过这个位置,避免重复绘制
|
||||
if (this.lastDrawnTile && this.lastDrawnTile.x === tileX && this.lastDrawnTile.y === tileY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 地形模式:只设置地形,不修改瓦片
|
||||
if (this.isTerrainMode) {
|
||||
// 更新地图数据中的地形ID
|
||||
this.mapData[tileY][tileX].terrainId = this.selectedTerrain;
|
||||
console.log(`已设置瓦片(${tileX},${tileY})的地形为: ${this.terrainTypes[this.selectedTerrain].name}`);
|
||||
}
|
||||
// 瓦片模式:放置或删除瓦片
|
||||
else if (this.selectedTile === -1) {
|
||||
this.removeTile(tileX, tileY);
|
||||
} else {
|
||||
// 放置瓦片
|
||||
const tileKey = this.tileSet[this.selectedTile];
|
||||
|
||||
// 检查该位置是否已有瓦片
|
||||
const existingTile = this.getTileAt(tileX, tileY);
|
||||
if (existingTile) {
|
||||
// 删除现有瓦片
|
||||
existingTile.destroy();
|
||||
}
|
||||
|
||||
// 计算精确的绘制位置
|
||||
const drawX = tileX * this.tileSize;
|
||||
const drawY = tileY * this.tileSize;
|
||||
|
||||
console.log(`鼠标位置: (${pointer.x}, ${pointer.y}) -> 世界坐标: (${worldPoint.x}, ${worldPoint.y}) -> 瓦片坐标: (${tileX}, ${tileY}) -> 绘制位置: (${drawX}, ${drawY})`);
|
||||
|
||||
// 创建新瓦片
|
||||
try {
|
||||
const tile = this.add.image(drawX, drawY, tileKey);
|
||||
tile.setOrigin(0, 0);
|
||||
tile.setDepth(2);
|
||||
this.mapLayer.add(tile);
|
||||
|
||||
// 更新地图数据,包含瓦片索引和地形ID
|
||||
this.mapData[tileY][tileX] = {
|
||||
tileIndex: this.selectedTile,
|
||||
terrainId: this.selectedTerrain
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('放置瓦片失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 记录最后绘制的瓦片位置
|
||||
this.lastDrawnTile = { x: tileX, y: tileY };
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除瓦片
|
||||
*/
|
||||
removeTile(tileX, tileY) {
|
||||
// 检查该位置是否已有瓦片
|
||||
const existingTile = this.getTileAt(tileX, tileY);
|
||||
if (existingTile) {
|
||||
// 删除现有瓦片
|
||||
existingTile.destroy();
|
||||
|
||||
// 更新地图数据,保留地形信息
|
||||
this.mapData[tileY][tileX] = {
|
||||
tileIndex: -1,
|
||||
terrainId: this.mapData[tileY][tileX].terrainId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定位置的瓦片
|
||||
*/
|
||||
getTileAt(tileX, tileY) {
|
||||
const children = this.mapLayer.getAll();
|
||||
for (const child of children) {
|
||||
const childTileX = Math.floor(child.x / this.tileSize);
|
||||
const childTileY = Math.floor(child.y / this.tileSize);
|
||||
if (childTileX === tileX && childTileY === tileY) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空地图
|
||||
*/
|
||||
clearMap() {
|
||||
// 删除所有瓦片
|
||||
this.mapLayer.removeAll(true);
|
||||
|
||||
// 重置地图数据
|
||||
for (let y = 0; y < this.mapHeight; y++) {
|
||||
for (let x = 0; x < this.mapWidth; x++) {
|
||||
this.mapData[y][x] = { tileIndex: -1, terrainId: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
console.log('地图已清空');
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存地图
|
||||
*/
|
||||
saveMap() {
|
||||
// 将地图数据转换为JSON
|
||||
const mapJson = JSON.stringify(this.mapData);
|
||||
|
||||
// 创建下载链接
|
||||
const blob = new Blob([mapJson], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'map.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
console.log('地图已保存');
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载地图
|
||||
*/
|
||||
loadMap() {
|
||||
// 创建文件选择器
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
try {
|
||||
const mapData = JSON.parse(event.target.result);
|
||||
this.loadMapData(mapData);
|
||||
} catch (error) {
|
||||
console.error('加载地图失败:', error);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载地图数据
|
||||
*/
|
||||
loadMapData(mapData) {
|
||||
// 清空当前地图
|
||||
this.clearMap();
|
||||
|
||||
// 更新地图数据
|
||||
this.mapData = mapData;
|
||||
|
||||
// 重新绘制地图
|
||||
for (let y = 0; y < this.mapHeight; y++) {
|
||||
for (let x = 0; x < this.mapWidth; x++) {
|
||||
// 兼容旧数据格式和新数据格式
|
||||
const tileData = typeof this.mapData[y][x] === 'object' ? this.mapData[y][x] : { tileIndex: this.mapData[y][x], terrainId: 0 };
|
||||
const tileIndex = tileData.tileIndex;
|
||||
|
||||
if (tileIndex !== -1 && tileIndex < this.tileSet.length) {
|
||||
const tileKey = this.tileSet[tileIndex];
|
||||
try {
|
||||
const tile = this.add.image(x * this.tileSize, y * this.tileSize, tileKey);
|
||||
tile.setOrigin(0, 0);
|
||||
tile.setDepth(2);
|
||||
this.mapLayer.add(tile);
|
||||
} catch (error) {
|
||||
console.error(`加载地图瓦片(${x},${y})失败:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('地图已加载');
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
// 资源包测试场景
|
||||
|
||||
export default class ResourceTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'ResourceTestScene' });
|
||||
this.resourceCategories = ['PNG/Enemies', 'PNG/Interface', 'PNG/Players', 'PNG/Tiles', 'PNG/Weapons', 'Sounds'];
|
||||
this.currentCategory = 0;
|
||||
this.assetsLoaded = false;
|
||||
this.loadedAssets = {
|
||||
images: [],
|
||||
sounds: []
|
||||
};
|
||||
}
|
||||
|
||||
preload() {
|
||||
// 加载资源
|
||||
this.loadAssets();
|
||||
|
||||
// 显示加载进度
|
||||
this.createLoadingUI();
|
||||
}
|
||||
|
||||
create() {
|
||||
// 物理世界和相机边界
|
||||
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.cameras.main.setBackgroundColor('#000000');
|
||||
|
||||
// 创建主UI
|
||||
this.createMainUI();
|
||||
|
||||
// 设置输入处理
|
||||
this.setupInput();
|
||||
|
||||
// 如果资源已经加载完成,初始化资源显示
|
||||
if (this.assetsLoaded) {
|
||||
this.updateResourceDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载所有资源
|
||||
*/
|
||||
loadAssets() {
|
||||
// 加载PNG资源
|
||||
this.load.image('enemies-tilemap', 'assets/PNG/Enemies/Tilemap/tilemap.png');
|
||||
this.load.image('enemies-tilemap-packed', 'assets/PNG/Enemies/Tilemap/tilemap_packed.png');
|
||||
|
||||
this.load.image('interface-tilemap', 'assets/PNG/Interface/Tilemap/tilemap.png');
|
||||
this.load.image('interface-tilemap-packed', 'assets/PNG/Interface/Tilemap/tilemap_packed.png');
|
||||
|
||||
this.load.image('players-tilemap', 'assets/PNG/Players/Tilemap/tilemap.png');
|
||||
this.load.image('players-tilemap-packed', 'assets/PNG/Players/Tilemap/tilemap_packed.png');
|
||||
|
||||
this.load.image('tiles-tilemap', 'assets/PNG/Tiles/Tilemap/tilemap.png');
|
||||
this.load.image('tiles-tilemap-packed', 'assets/PNG/Tiles/Tilemap/tilemap_packed.png');
|
||||
|
||||
this.load.image('weapons-tilemap', 'assets/PNG/Weapons/Tilemap/tilemap.png');
|
||||
this.load.image('weapons-tilemap-packed', 'assets/PNG/Weapons/Tilemap/tilemap_packed.png');
|
||||
|
||||
// 加载前几个敌人瓦片
|
||||
for (let i = 0; i < 8; i++) {
|
||||
this.load.image(`enemy-tile-${i}`, `assets/PNG/Enemies/Tiles/tile_${String(i).padStart(4, '0')}.png`);
|
||||
}
|
||||
|
||||
// 加载前几个界面瓦片
|
||||
for (let i = 0; i < 16; i++) {
|
||||
this.load.image(`interface-tile-${i}`, `assets/PNG/Interface/Tiles/tile_${String(i).padStart(4, '0')}.png`);
|
||||
}
|
||||
|
||||
// 加载前几个玩家瓦片
|
||||
for (let i = 0; i < 8; i++) {
|
||||
this.load.image(`player-tile-${i}`, `assets/PNG/Players/Tiles/tile_${String(i).padStart(4, '0')}.png`);
|
||||
}
|
||||
|
||||
// 加载前几个地形瓦片
|
||||
for (let i = 0; i < 16; i++) {
|
||||
this.load.image(`tile-${i}`, `assets/PNG/Tiles/Tiles/tile_${String(i).padStart(4, '0')}.png`);
|
||||
}
|
||||
|
||||
// 加载前几个武器瓦片
|
||||
for (let i = 0; i < 8; i++) {
|
||||
this.load.image(`weapon-tile-${i}`, `assets/PNG/Weapons/Tiles/tile_${String(i).padStart(4, '0')}.png`);
|
||||
}
|
||||
|
||||
// 加载音效资源
|
||||
this.load.audio('coin-a', 'assets/Sounds/coin-a.ogg');
|
||||
this.load.audio('coin-b', 'assets/Sounds/coin-b.ogg');
|
||||
this.load.audio('jump-a', 'assets/Sounds/jump-a.ogg');
|
||||
this.load.audio('shoot-a', 'assets/Sounds/shoot-a.ogg');
|
||||
this.load.audio('explosion-a', 'assets/Sounds/explosion-a.ogg');
|
||||
this.load.audio('hurt-a', 'assets/Sounds/hurt-a.ogg');
|
||||
|
||||
// 加载完成回调
|
||||
this.load.on('complete', () => {
|
||||
this.assetsLoaded = true;
|
||||
this.loadingText.setText('资源加载完成!');
|
||||
// 只有在resourceDisplay初始化后才调用updateResourceDisplay
|
||||
if (this.resourceDisplay) {
|
||||
this.updateResourceDisplay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建加载UI
|
||||
*/
|
||||
createLoadingUI() {
|
||||
// 加载文本
|
||||
this.loadingText = this.add.text(10, 10, '加载资源...', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
this.loadingText.setScrollFactor(0, 0);
|
||||
this.loadingText.setDepth(100);
|
||||
|
||||
// 加载进度条
|
||||
this.progressBar = this.add.rectangle(10, 40, 300, 20, 0x555555);
|
||||
this.progressBar.setOrigin(0, 0);
|
||||
this.progressBar.setScrollFactor(0, 0);
|
||||
this.progressBar.setDepth(100);
|
||||
|
||||
this.progressFill = this.add.rectangle(10, 40, 0, 20, 0x00ff00);
|
||||
this.progressFill.setOrigin(0, 0);
|
||||
this.progressFill.setScrollFactor(0, 0);
|
||||
this.progressFill.setDepth(100);
|
||||
|
||||
// 更新进度
|
||||
this.load.on('progress', (progress) => {
|
||||
this.progressFill.width = progress * 300;
|
||||
this.loadingText.setText(`加载资源... ${Math.round(progress * 100)}%`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建主UI
|
||||
*/
|
||||
createMainUI() {
|
||||
// 标题
|
||||
this.titleText = this.add.text(this.sys.game.config.width / 2, 50, '资源包测试场景', {
|
||||
fontSize: '32px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
});
|
||||
this.titleText.setOrigin(0.5, 0.5);
|
||||
this.titleText.setScrollFactor(0, 0);
|
||||
this.titleText.setDepth(10);
|
||||
|
||||
// 分类选择
|
||||
this.categoryText = this.add.text(10, 70, `资源分类: ${this.resourceCategories[this.currentCategory]}`, {
|
||||
fontSize: '18px',
|
||||
fill: '#00ff00',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
this.categoryText.setScrollFactor(0, 0);
|
||||
this.categoryText.setDepth(10);
|
||||
|
||||
// 导航按钮
|
||||
this.createNavigationButtons();
|
||||
|
||||
// 资源显示区域
|
||||
this.resourceDisplay = this.add.container(0, 0);
|
||||
this.resourceDisplay.setDepth(5);
|
||||
|
||||
// 操作提示
|
||||
this.instructionText = this.add.text(10, this.sys.game.config.height - 100,
|
||||
'← → 键:切换资源分类 | 空格键:返回启动场景 | 点击音效图标播放音效', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
this.instructionText.setScrollFactor(0, 0);
|
||||
this.instructionText.setDepth(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建导航按钮
|
||||
*/
|
||||
createNavigationButtons() {
|
||||
// 上一页按钮
|
||||
this.prevButton = this.add.rectangle(10, 100, 120, 40, 0x333333);
|
||||
this.prevButton.setOrigin(0, 0);
|
||||
this.prevButton.setScrollFactor(0, 0);
|
||||
this.prevButton.setDepth(10);
|
||||
this.prevButton.setInteractive({ useHandCursor: true });
|
||||
|
||||
this.prevButtonText = this.add.text(20, 110, '上一页', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
this.prevButtonText.setScrollFactor(0, 0);
|
||||
this.prevButtonText.setDepth(11);
|
||||
|
||||
// 下一页按钮
|
||||
this.nextButton = this.add.rectangle(140, 100, 120, 40, 0x333333);
|
||||
this.nextButton.setOrigin(0, 0);
|
||||
this.nextButton.setScrollFactor(0, 0);
|
||||
this.nextButton.setDepth(10);
|
||||
this.nextButton.setInteractive({ useHandCursor: true });
|
||||
|
||||
this.nextButtonText = this.add.text(150, 110, '下一页', {
|
||||
fontSize: '16px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
this.nextButtonText.setScrollFactor(0, 0);
|
||||
this.nextButtonText.setDepth(11);
|
||||
|
||||
// 按钮事件
|
||||
this.prevButton.on('pointerdown', () => {
|
||||
this.currentCategory = (this.currentCategory - 1 + this.resourceCategories.length) % this.resourceCategories.length;
|
||||
this.updateCategoryDisplay();
|
||||
});
|
||||
|
||||
this.nextButton.on('pointerdown', () => {
|
||||
this.currentCategory = (this.currentCategory + 1) % this.resourceCategories.length;
|
||||
this.updateCategoryDisplay();
|
||||
});
|
||||
|
||||
// 悬停效果
|
||||
this.prevButton.on('pointerover', () => this.prevButton.setFillStyle(0x555555));
|
||||
this.prevButton.on('pointerout', () => this.prevButton.setFillStyle(0x333333));
|
||||
this.nextButton.on('pointerover', () => this.nextButton.setFillStyle(0x555555));
|
||||
this.nextButton.on('pointerout', () => this.nextButton.setFillStyle(0x333333));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新分类显示
|
||||
*/
|
||||
updateCategoryDisplay() {
|
||||
this.categoryText.setText(`资源分类: ${this.resourceCategories[this.currentCategory]}`);
|
||||
this.updateResourceDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新资源显示
|
||||
*/
|
||||
updateResourceDisplay() {
|
||||
if (!this.assetsLoaded) return;
|
||||
|
||||
// 清空当前显示的资源(如果resourceDisplay已经初始化)
|
||||
if (this.resourceDisplay) {
|
||||
this.resourceDisplay.removeAll(true);
|
||||
}
|
||||
|
||||
// 根据当前分类显示资源
|
||||
switch (this.currentCategory) {
|
||||
case 0: // PNG/Enemies
|
||||
this.displayEnemyAssets();
|
||||
break;
|
||||
case 1: // PNG/Interface
|
||||
this.displayInterfaceAssets();
|
||||
break;
|
||||
case 2: // PNG/Players
|
||||
this.displayPlayerAssets();
|
||||
break;
|
||||
case 3: // PNG/Tiles
|
||||
this.displayTileAssets();
|
||||
break;
|
||||
case 4: // PNG/Weapons
|
||||
this.displayWeaponAssets();
|
||||
break;
|
||||
case 5: // Sounds
|
||||
this.displaySoundAssets();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示敌人资源
|
||||
*/
|
||||
displayEnemyAssets() {
|
||||
const centerX = 0;
|
||||
const centerY = 0;
|
||||
|
||||
// 显示瓦片地图
|
||||
this.resourceDisplay.add(this.add.image(centerX - 300, centerY - 200, 'enemies-tilemap').setScale(0.5));
|
||||
this.resourceDisplay.add(this.add.image(centerX + 300, centerY - 200, 'enemies-tilemap-packed').setScale(0.5));
|
||||
|
||||
// 显示瓦片
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const x = centerX - 200 + (i % 4) * 100;
|
||||
const y = centerY + 100 + Math.floor(i / 4) * 100;
|
||||
const tile = this.add.image(x, y, `enemy-tile-${i}`).setScale(2);
|
||||
this.resourceDisplay.add(tile);
|
||||
|
||||
// 添加瓦片编号
|
||||
this.resourceDisplay.add(this.add.text(x - 20, y + 40, `Tile ${i}`, {
|
||||
fontSize: '12px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示界面资源
|
||||
*/
|
||||
displayInterfaceAssets() {
|
||||
const centerX = 0;
|
||||
const centerY = 0;
|
||||
|
||||
// 显示瓦片地图
|
||||
this.resourceDisplay.add(this.add.image(centerX - 300, centerY - 200, 'interface-tilemap').setScale(0.5));
|
||||
this.resourceDisplay.add(this.add.image(centerX + 300, centerY - 200, 'interface-tilemap-packed').setScale(0.5));
|
||||
|
||||
// 显示瓦片
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const x = centerX - 300 + (i % 8) * 75;
|
||||
const y = centerY + 100 + Math.floor(i / 8) * 75;
|
||||
const tile = this.add.image(x, y, `interface-tile-${i}`).setScale(1.5);
|
||||
this.resourceDisplay.add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示玩家资源
|
||||
*/
|
||||
displayPlayerAssets() {
|
||||
const centerX = 0;
|
||||
const centerY = 0;
|
||||
|
||||
// 显示瓦片地图
|
||||
this.resourceDisplay.add(this.add.image(centerX - 300, centerY - 200, 'players-tilemap').setScale(0.5));
|
||||
this.resourceDisplay.add(this.add.image(centerX + 300, centerY - 200, 'players-tilemap-packed').setScale(0.5));
|
||||
|
||||
// 显示瓦片
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const x = centerX - 200 + (i % 4) * 100;
|
||||
const y = centerY + 100 + Math.floor(i / 4) * 100;
|
||||
const tile = this.add.image(x, y, `player-tile-${i}`).setScale(2);
|
||||
this.resourceDisplay.add(tile);
|
||||
|
||||
// 添加瓦片编号
|
||||
this.resourceDisplay.add(this.add.text(x - 20, y + 40, `Player ${i}`, {
|
||||
fontSize: '12px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示地形瓦片资源
|
||||
*/
|
||||
displayTileAssets() {
|
||||
const centerX = 0;
|
||||
const centerY = 0;
|
||||
|
||||
// 显示瓦片地图
|
||||
this.resourceDisplay.add(this.add.image(centerX - 300, centerY - 200, 'tiles-tilemap').setScale(0.5));
|
||||
this.resourceDisplay.add(this.add.image(centerX + 300, centerY - 200, 'tiles-tilemap-packed').setScale(0.5));
|
||||
|
||||
// 显示瓦片
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const x = centerX - 300 + (i % 8) * 75;
|
||||
const y = centerY + 100 + Math.floor(i / 8) * 75;
|
||||
const tile = this.add.image(x, y, `tile-${i}`).setScale(1.5);
|
||||
this.resourceDisplay.add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示武器资源
|
||||
*/
|
||||
displayWeaponAssets() {
|
||||
const centerX = 0;
|
||||
const centerY = 0;
|
||||
|
||||
// 显示瓦片地图
|
||||
this.resourceDisplay.add(this.add.image(centerX - 300, centerY - 200, 'weapons-tilemap').setScale(0.5));
|
||||
this.resourceDisplay.add(this.add.image(centerX + 300, centerY - 200, 'weapons-tilemap-packed').setScale(0.5));
|
||||
|
||||
// 显示瓦片
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const x = centerX - 200 + (i % 4) * 100;
|
||||
const y = centerY + 100 + Math.floor(i / 4) * 100;
|
||||
const tile = this.add.image(x, y, `weapon-tile-${i}`).setScale(2);
|
||||
this.resourceDisplay.add(tile);
|
||||
|
||||
// 添加瓦片编号
|
||||
this.resourceDisplay.add(this.add.text(x - 20, y + 40, `Weapon ${i}`, {
|
||||
fontSize: '12px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示音效资源
|
||||
*/
|
||||
displaySoundAssets() {
|
||||
const centerX = 0;
|
||||
const centerY = 0;
|
||||
const soundNames = ['coin-a', 'coin-b', 'jump-a', 'shoot-a', 'explosion-a', 'hurt-a'];
|
||||
const soundLabels = ['硬币A', '硬币B', '跳跃', '射击', '爆炸', '受伤'];
|
||||
|
||||
for (let i = 0; i < soundNames.length; i++) {
|
||||
const x = centerX - 200 + (i % 3) * 200;
|
||||
const y = centerY + Math.floor(i / 3) * 150;
|
||||
|
||||
// 创建音效按钮
|
||||
const button = this.add.rectangle(x, y, 150, 80, 0x444444);
|
||||
button.setInteractive({ useHandCursor: true });
|
||||
button.soundKey = soundNames[i];
|
||||
this.resourceDisplay.add(button);
|
||||
|
||||
// 添加标签
|
||||
this.resourceDisplay.add(this.add.text(x - 60, y - 20, soundLabels[i], {
|
||||
fontSize: '14px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}));
|
||||
|
||||
this.resourceDisplay.add(this.add.text(x - 60, y, soundNames[i], {
|
||||
fontSize: '12px',
|
||||
fill: '#cccccc',
|
||||
fontFamily: 'Arial'
|
||||
}));
|
||||
|
||||
// 添加播放提示
|
||||
this.resourceDisplay.add(this.add.text(x - 40, y + 20, '点击播放', {
|
||||
fontSize: '12px',
|
||||
fill: '#00ff00',
|
||||
fontFamily: 'Arial'
|
||||
}));
|
||||
|
||||
// 按钮事件
|
||||
button.on('pointerdown', () => {
|
||||
this.sound.play(button.soundKey);
|
||||
});
|
||||
|
||||
button.on('pointerover', () => {
|
||||
button.setFillStyle(0x666666);
|
||||
});
|
||||
|
||||
button.on('pointerout', () => {
|
||||
button.setFillStyle(0x444444);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入处理
|
||||
*/
|
||||
setupInput() {
|
||||
// 键盘输入
|
||||
this.input.keyboard.on('keydown-LEFT', () => {
|
||||
this.currentCategory = (this.currentCategory - 1 + this.resourceCategories.length) % this.resourceCategories.length;
|
||||
this.updateCategoryDisplay();
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-RIGHT', () => {
|
||||
this.currentCategory = (this.currentCategory + 1) % this.resourceCategories.length;
|
||||
this.updateCategoryDisplay();
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keydown-SPACE', () => {
|
||||
this.scene.start('BootScene');
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
// 每帧更新
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// 测试场景:单位选择与移动命令
|
||||
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 SelectionAndMovementTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'SelectionAndMovementTestScene' });
|
||||
}
|
||||
|
||||
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, '测试场景:单位选择与移动命令', {
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// 测试场景:目标点移动
|
||||
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 TargetMovementTestScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'TargetMovementTestScene' });
|
||||
}
|
||||
|
||||
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, '测试场景:目标点移动', {
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user