feat(ItemList) 添加周围物品列表功能

This commit is contained in:
chicrobot committed 2026-01-06 01:13:17 +08:00
1 parent 215e04384c
commit 0ed8c2f6ae
7 files changed
+1349 -14

No files matched your search

+25 -5
View File
@@ -65,7 +65,12 @@ export default class Item extends Phaser.GameObjects.Sprite {
}); });
// 绑定点击事件 // 绑定点击事件
this.on('pointerdown', () => { this.on('pointerdown', (pointer) => {
// 检查鼠标是否在UI元素上,如果是则不处理
if (this.scene.inputSystem && this.scene.inputSystem.isMouseOnUI && this.scene.inputSystem.isMouseOnUI(pointer)) {
return;
}
// 添加视觉反馈:缩放物品 // 添加视觉反馈:缩放物品
this.scene.tweens.add({ this.scene.tweens.add({
targets: this, targets: this,
@@ -101,6 +106,12 @@ export default class Item extends Phaser.GameObjects.Sprite {
* @param {Object} user - 拾取物品的单位 * @param {Object} user - 拾取物品的单位
*/ */
pickup(user) { pickup(user) {
// 检查是否在交互范围内
if (!this.isInInteractionRange(user)) {
console.log('距离太远,无法拾取物品');
return;
}
console.log(`${user.name} 拾取了 ${this.name}`); console.log(`${user.name} 拾取了 ${this.name}`);
// 检查用户是否有背包 // 检查用户是否有背包
@@ -109,13 +120,22 @@ export default class Item extends Phaser.GameObjects.Sprite {
const success = user.inventory.addItem(this); const success = user.inventory.addItem(this);
if (success) { if (success) {
this.inInventory = true; this.inInventory = true;
this.visible = false; // 在场景中隐藏物品
this.scene.events.emit('item-picked-up', this, user); // 保存场景引用,避免在destroy后访问undefined
const scene = this.scene;
// 如果场景中有背包UI,更新UI // 如果场景中有背包UI,更新UI
if (this.scene.inventoryUI) { if (scene && scene.inventoryUI) {
this.scene.inventoryUI.updateUI(); scene.inventoryUI.updateUI();
} }
// 发射物品拾取事件
if (scene && scene.events) {
scene.events.emit('item-picked-up', this, user);
}
// 成功拾取后从地图上删除物品,避免潜在bug
this.destroy();
} else { } else {
console.log('背包空间不足'); console.log('背包空间不足');
} }
+4 -1
View File
@@ -16,6 +16,8 @@ import MovementTestScene6 from './scenes/MovementTestScene6.js';
import ItemTestScene from './scenes/ItemTestScene.js'; import ItemTestScene from './scenes/ItemTestScene.js';
// 导入背包系统测试场景 // 导入背包系统测试场景
import InventoryTestScene from './scenes/InventoryTestScene.js'; import InventoryTestScene from './scenes/InventoryTestScene.js';
// 导入物品列表菜单测试场景
import ItemListTestScene from './scenes/ItemListTestScene.js';
// 创建游戏的最终配置对象 // 创建游戏的最终配置对象
const config = { const config = {
@@ -33,7 +35,8 @@ const config = {
MovementTestScene5, MovementTestScene5,
MovementTestScene6, MovementTestScene6,
ItemTestScene, ItemTestScene,
InventoryTestScene InventoryTestScene,
ItemListTestScene
] ]
}; };
+7 -6
View File
@@ -19,14 +19,15 @@ export default class BootScene extends Phaser.Scene {
// 测试场景列表 // 测试场景列表
const testScenes = [ const testScenes = [
{ key: 'ItemListTestScene', name: '测试场景9:物品列表菜单测试' },
{ key: 'InventoryTestScene', name: '测试场景8:背包系统测试' }, { key: 'InventoryTestScene', name: '测试场景8:背包系统测试' },
{ key: 'MovementTestScene6', name: '测试场景6:小地图测试' },
{ key: 'MovementTestScene1', name: '测试场景1:相对移动(键盘控制)' },
{ key: 'MovementTestScene2', name: '测试场景2:目标点移动' },
{ key: 'MovementTestScene3', name: '测试场景3:单位选择与移动命令' },
{ key: 'MovementTestScene4', name: '测试场景4:虚拟摇杆控制' },
{ key: 'MovementTestScene5', name: '测试场景5:移动更新逻辑' },
{ key: 'ItemTestScene', name: '测试场景7:物品系统测试' }, { key: 'ItemTestScene', name: '测试场景7:物品系统测试' },
{ key: 'MovementTestScene6', name: '测试场景6:小地图测试' },
{ key: 'MovementTestScene5', name: '测试场景5:移动更新逻辑' },
{ key: 'MovementTestScene4', name: '测试场景4:虚拟摇杆控制' },
{ key: 'MovementTestScene3', name: '测试场景3:单位选择与移动命令' },
{ key: 'MovementTestScene2', name: '测试场景2:目标点移动' },
{ key: 'MovementTestScene1', name: '测试场景1:相对移动(键盘控制)' },
{ key: 'GameScene', name: '返回主游戏场景' } { key: 'GameScene', name: '返回主游戏场景' }
]; ];
+673
View File
@@ -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);
});
}
}
+13
View File
@@ -31,6 +31,19 @@ export default class ItemTestScene extends Phaser.Scene {
// 创建单位组 // 创建单位组
this.unitGroup = this.add.group(); 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'); const unit = new Unit(this, 0, 0, 'player');
// 为单位添加一些属性,用于测试物品效果 // 为单位添加一些属性,用于测试物品效果
+36 -2
View File
@@ -293,7 +293,41 @@ export class InputSystem {
return this.isMouseOnMiniMap(pointer) || return this.isMouseOnMiniMap(pointer) ||
this.isMouseOnJoystick(pointer) || this.isMouseOnJoystick(pointer) ||
this.isMouseOnSidebar(pointer) || this.isMouseOnSidebar(pointer) ||
this.isMouseOnInventoryUI(pointer); this.isMouseOnInventoryUI(pointer) ||
this.isMouseOnItemListUI(pointer);
}
/**
* 检查鼠标是否在物品列表菜单上
* @param {Phaser.Input.Pointer} pointer - 指针事件对象
* @returns {boolean} - 如果鼠标在物品列表菜单上返回true,否则返回false
*/
isMouseOnItemListUI(pointer) {
// 检查场景中是否有物品列表UI
if (!this.scene.itemListUI) {
return false;
}
// 获取物品列表UI实例
const itemListUI = this.scene.itemListUI;
// 获取鼠标在屏幕上的坐标
const mouseX = pointer.x;
const mouseY = pointer.y;
// 检查UI是否可见
if (!itemListUI.visible) {
return false;
}
// 直接使用UI的位置和尺寸来计算边界,不依赖container
const uiX = itemListUI.x;
const uiY = itemListUI.y;
const uiWidth = itemListUI.width;
const uiHeight = itemListUI.height;
// 检查鼠标坐标是否在物品列表UI边界内
return mouseX >= uiX && mouseX <= uiX + uiWidth &&
mouseY >= uiY && mouseY <= uiY + uiHeight;
} }
/** /**
@@ -420,7 +454,7 @@ export class InputSystem {
return; return;
} }
const pickupDistance = 100; // 最大拾取距离 const pickupDistance = 50; // 最大拾取距离,与物品交互距离保持一致
let closestItem = null; let closestItem = null;
let minDistance = pickupDistance; let minDistance = pickupDistance;
+591
View File
@@ -0,0 +1,591 @@
// 物品列表菜单UI类,用于显示可交互范围内的所有物品
export class ItemListUI {
constructor(scene, x, y, width = 250, height = 300) {
this.scene = scene;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.visible = false;
this.items = [];
this.selectedItemId = null; // 保存选中物品的ID,而不是索引
this.interactionRange = 50; // 与Item类中的交互距离保持一致
this.itemListElements = [];
this.optionsElements = [];
// 初始化玩家位置缓存,用于优化更新频率
this.lastPlayerX = 0;
this.lastPlayerY = 0;
// 添加更新间隔,限制距离信息的更新频率(毫秒)
this.updateInterval = 100;
this.lastUpdateTime = 0;
this.createUI();
}
/**
* 创建UI元素
*/
createUI() {
console.log('ItemListUI.createUI: 开始创建物品列表UI');
// 直接在场景中创建元素,不使用容器,避免容器带来的坐标问题
// 背景
this.background = this.scene.add.rectangle(this.x, this.y, this.width, this.height, 0x111111, 0.95);
this.background.setOrigin(0, 0);
this.background.setStrokeStyle(2, 0x444444, 1);
this.background.setScrollFactor(0, 0); // 固定在屏幕上
this.background.setDepth(20); // 设置深度,确保显示在其他UI上方
// 标题
this.title = this.scene.add.text(this.x + 10, this.y + 5, '可交互物品', {
fontSize: '16px',
fill: '#ffffff',
fontWeight: 'bold',
fontFamily: 'Arial'
});
this.title.setOrigin(0, 0);
this.title.setScrollFactor(0, 0);
this.title.setDepth(21);
// 物品列表区域
this.listX = this.x;
this.listY = this.y + 30;
this.listHeight = this.height - 60;
console.log('ItemListUI.createUI: 物品列表区域设置完成,位置:', this.listX, this.listY, '高度:', this.listHeight);
// 关闭按钮 - 直接在场景中创建
this.closeButton = this.scene.add.rectangle(this.x + this.width - 25, this.y + 10, 20, 20, 0x555555);
this.closeButton.setOrigin(0.5, 0.5);
this.closeButton.setScrollFactor(0, 0);
this.closeButton.setDepth(21);
this.closeText = this.scene.add.text(this.x + this.width - 25, this.y + 10, 'X', {
fontSize: '14px',
fill: '#ffffff',
fontFamily: 'Arial'
});
this.closeText.setOrigin(0.5, 0.5);
this.closeText.setScrollFactor(0, 0);
this.closeText.setDepth(22);
// 关闭按钮交互 - 确保正确绑定
this.closeButton.setInteractive({
useHandCursor: true
});
this.closeButton.on('pointerdown', () => {
console.log('ItemListUI: 关闭按钮点击');
this.hide();
});
this.closeButton.on('pointerover', () => {
console.log('ItemListUI: 关闭按钮悬停');
this.closeButton.setFillStyle(0x777777);
});
this.closeButton.on('pointerout', () => {
console.log('ItemListUI: 关闭按钮移出');
this.closeButton.setFillStyle(0x555555);
});
// 初始隐藏状态
this.setVisible(this.visible);
console.log('ItemListUI.createUI: UI创建完成,初始可见性:', this.visible);
}
/**
* 更新物品列表,显示可交互范围内的所有物品
* @param {Object} unit - 玩家单位
* @param {Array} items - 所有物品数组
*/
updateItems(unit, items) {
// 过滤出可交互范围内且未被拾取的物品,并计算距离
const newItems = [];
items.forEach(item => {
if (item.isInInteractionRange(unit) && !item.inInventory) {
// 计算物品与玩家的距离
const dx = item.x - unit.x;
const dy = item.y - unit.y;
item.distance = Math.sqrt(dx * dx + dy * dy);
newItems.push(item);
}
});
// 按距离从小到大排序物品列表
newItems.sort((a, b) => a.distance - b.distance);
// 检查物品列表是否发生变化(物品数量或ID变化)
const itemsChanged = this.isItemsChanged(newItems);
// 获取当前时间
const currentTime = Date.now();
// 更新物品列表
this.items = newItems;
// 检查选中的物品是否仍然在列表中,如果不在则清除选中状态
if (this.selectedItemId) {
const isItemStillInList = newItems.some(item => item.id === this.selectedItemId);
if (!isItemStillInList) {
this.selectedItemId = null;
this.clearItemOptions();
}
}
// 只有在以下情况之一时才刷新列表:
// 1. 物品列表发生变化(物品数量或ID变化)
// 2. 距离上一次更新超过指定间隔(用于更新距离信息)
if (itemsChanged || (currentTime - this.lastUpdateTime > this.updateInterval)) {
// 刷新列表
this.refreshList();
this.lastUpdateTime = currentTime;
}
}
/**
* 检查物品列表是否发生变化
* @param {Array} newItems - 新的物品列表
* @returns {boolean} - 列表是否发生变化
*/
isItemsChanged(newItems) {
// 如果物品数量不同,列表肯定发生了变化
if (newItems.length !== this.items.length) {
return true;
}
// 如果列表为空,无需进一步检查
if (newItems.length === 0) {
return false;
}
// 检查物品ID是否完全相同
const oldIds = this.items.map(item => item.id).sort();
const newIds = newItems.map(item => item.id).sort();
for (let i = 0; i < oldIds.length; i++) {
if (oldIds[i] !== newIds[i]) {
return true;
}
}
return false;
}
/**
* 刷新物品列表显示
*/
refreshList() {
console.log('ItemListUI.refreshList: 开始刷新物品列表,物品数量:', this.items.length);
// 清除现有列表项 - 移除所有动态创建的列表项
if (this.itemListElements) {
this.itemListElements.forEach(element => {
element.destroy();
});
}
this.itemListElements = [];
// 如果没有可交互物品,显示提示
if (this.items.length === 0) {
const noItemsText = this.scene.add.text(this.x + 10, this.listY + 10, '没有可交互物品', {
fontSize: '14px',
fill: '#888888',
fontFamily: 'Arial'
});
noItemsText.setOrigin(0, 0);
noItemsText.setScrollFactor(0, 0); // 固定在屏幕上
noItemsText.setDepth(21);
this.itemListElements.push(noItemsText);
return;
}
// 创建新的列表项
this.items.forEach((item, index) => {
const itemHeight = 30;
// 计算在屏幕上的实际Y坐标
const y = this.listY + index * itemHeight;
// 根据物品ID判断是否选中,而不是索引
const isSelected = this.selectedItemId === item.id;
// 列表项背景 - 直接使用屏幕坐标
const background = this.scene.add.rectangle(this.x + 5, y + itemHeight / 2, this.width - 10, itemHeight - 5, isSelected ? 0x444444 : 0x222222);
background.setOrigin(0, 0.5);
background.setScrollFactor(0, 0); // 固定在屏幕上
// 保存原始颜色,用于悬停效果
background.originalColor = isSelected ? 0x444444 : 0x222222;
background.isSelected = isSelected;
// 确保背景在合适的深度
background.setDepth(21);
// 物品名称
const nameText = this.scene.add.text(this.x + 15, y + 5, item.name, {
fontSize: '14px',
fill: '#ffffff',
fontFamily: 'Arial'
});
nameText.setOrigin(0, 0);
nameText.setScrollFactor(0, 0);
nameText.setDepth(22);
// 物品类型
const typeText = this.scene.add.text(this.x + 15, y + 20, `[${item.type}]`, {
fontSize: '12px',
fill: '#888888',
fontFamily: 'Arial'
});
typeText.setOrigin(0, 0);
typeText.setScrollFactor(0, 0);
typeText.setDepth(22);
// 距离信息 - 使用预计算的距离,提高性能
const distanceText = this.scene.add.text(this.x + this.width - 60, y + 15, `${Math.round(item.distance)}m`, {
fontSize: '12px',
fill: '#4CAF50',
fontFamily: 'Arial'
});
distanceText.setOrigin(0, 0.5);
distanceText.setScrollFactor(0, 0);
distanceText.setDepth(22);
// 按键提示
let keyText = null;
if (index < 9) {
keyText = this.scene.add.text(this.x + this.width - 30, y + 15, `${index + 1}`, {
fontSize: '12px',
fill: '#FFC107',
fontFamily: 'Arial',
fontWeight: 'bold'
});
keyText.setOrigin(0, 0.5);
keyText.setScrollFactor(0, 0);
keyText.setDepth(22);
}
// 设置背景为可交互,并确保它能接收鼠标事件
background.setInteractive({
useHandCursor: true
});
// 点击事件
background.on('pointerdown', () => {
console.log(`Item clicked: ${item.name}, index: ${index}, id: ${item.id}`);
this.selectItem(index);
});
// 悬停效果
background.on('pointerover', () => {
console.log(`Item hovered: ${item.name}, index: ${index}`);
if (!background.isSelected) {
background.setFillStyle(0x333333);
}
});
// 移出效果
background.on('pointerout', () => {
console.log(`Item unhovered: ${item.name}, index: ${index}`);
background.setFillStyle(background.originalColor);
});
// 保存元素引用,便于后续清理
this.itemListElements.push(background);
this.itemListElements.push(nameText);
this.itemListElements.push(typeText);
this.itemListElements.push(distanceText);
if (keyText) {
this.itemListElements.push(keyText);
}
console.log(`ItemListUI: 为物品 ${item.name} 创建了列表项,索引 ${index},位置 Y: ${y}`);
});
}
/**
* 选择物品
* @param {number} index - 物品索引
*/
selectItem(index) {
console.log('selectItem called with index:', index, 'items length:', this.items.length);
if (index >= 0 && index < this.items.length) {
// 使用物品ID来保存选中状态,而不是索引
const item = this.items[index];
this.selectedItemId = item.id;
// 触发物品点击事件,与现有系统保持一致
this.scene.events.emit('item-clicked', item);
// 显示操作选项
this.showItemOptions(item);
// 更新列表显示,高亮选中项
this.refreshList();
}
}
/**
* 显示物品操作选项
* @param {Item} item - 物品实例
*/
showItemOptions(item) {
// 清除现有的操作选项
this.clearItemOptions();
// 选项按钮宽度
const buttonWidth = 80;
const buttonHeight = 30;
const buttonSpacing = 5;
// 创建拾取按钮
this.createOptionButton(this.x + 5, this.y + this.height - 55, '拾取', () => {
item.pickup(this.scene.player);
// 更新背包UI
if (this.scene.inventoryUI) {
this.scene.inventoryUI.updateUI();
}
// 更新物品列表
this.updateItems(this.scene.player, this.scene.itemManager.getActiveItems());
// 清除操作选项
this.clearItemOptions();
});
// 创建使用按钮
this.createOptionButton(this.x + buttonWidth + buttonSpacing + 5, this.y + this.height - 55, '使用', () => {
item.use(this.scene.player);
// 更新物品列表
this.updateItems(this.scene.player, this.scene.itemManager.getActiveItems());
// 清除操作选项
this.clearItemOptions();
});
// 创建查看详情按钮
this.createOptionButton(this.x + (buttonWidth + buttonSpacing) * 2 + 5, this.y + this.height - 55, '详情', () => {
// 显示物品详情,与现有系统保持一致
this.scene.events.emit('item-clicked', item);
});
// 显示交互提示
if (this.scene.interactionText) {
this.scene.interactionText.setText('点击按钮或按对应快捷键操作');
}
}
/**
* 创建操作选项按钮
* @param {number} x - 按钮x坐标
* @param {number} y - 按钮y坐标
* @param {string} text - 按钮文本
* @param {function} callback - 点击回调函数
*/
createOptionButton(x, y, text, callback) {
// 按钮背景 - 直接使用屏幕坐标
const button = this.scene.add.rectangle(x, y + 15, 80, 30, 0x333333);
button.setOrigin(0, 0.5);
button.setStrokeStyle(1, 0x666666, 1);
button.setScrollFactor(0, 0); // 固定在屏幕上
button.setDepth(23);
// 按钮文本
const buttonText = this.scene.add.text(x + 40, y + 15, text, {
fontSize: '12px',
fill: '#ffffff',
fontFamily: 'Arial',
align: 'center'
});
buttonText.setOrigin(0.5, 0.5);
buttonText.setScrollFactor(0, 0);
buttonText.setDepth(24);
// 设置交互
button.setInteractive({
useHandCursor: true
});
// 鼠标事件
button.on('pointerdown', callback);
button.on('pointerover', () => {
button.setFillStyle(0x555555);
});
button.on('pointerout', () => {
button.setFillStyle(0x333333);
});
// 保存元素引用,便于后续清理
this.optionsElements.push(button);
this.optionsElements.push(buttonText);
}
/**
* 清除物品操作选项
*/
clearItemOptions() {
if (this.optionsElements) {
this.optionsElements.forEach(element => {
element.destroy();
});
}
this.optionsElements = [];
}
/**
* 设置UI可见性
* @param {boolean} visible - 是否可见
*/
setVisible(visible) {
this.visible = visible;
// 显示/隐藏所有UI元素
if (this.background) {
this.background.visible = visible;
}
if (this.title) {
this.title.visible = visible;
}
if (this.closeButton) {
this.closeButton.visible = visible;
}
if (this.closeText) {
this.closeText.visible = visible;
}
// 显示/隐藏列表项
if (this.itemListElements) {
this.itemListElements.forEach(element => {
element.visible = visible;
});
}
// 显示/隐藏操作选项
this.clearItemOptions();
}
/**
* 打开物品列表
*/
show() {
this.setVisible(true);
// 更新物品列表
if (this.scene.itemManager && this.scene.player) {
this.updateItems(this.scene.player, this.scene.itemManager.getActiveItems());
}
}
/**
* 隐藏物品列表
*/
hide() {
this.setVisible(false);
this.selectedItemId = null; // 清除选中状态
}
/**
* 切换物品列表的显示状态
*/
toggle() {
if (this.visible) {
this.hide();
} else {
this.show();
}
}
/**
* 更新UI位置
* @param {number} x - 新的x坐标
* @param {number} y - 新的y坐标
*/
setPosition(x, y) {
this.x = x;
this.y = y;
// 更新所有UI元素的位置
if (this.background) {
this.background.setPosition(x, y);
}
if (this.title) {
this.title.setPosition(x + 10, y + 5);
}
if (this.closeButton) {
this.closeButton.setPosition(x + this.width - 25, y + 10);
}
if (this.closeText) {
this.closeText.setPosition(x + this.width - 25, y + 10);
}
// 更新列表区域位置
this.listX = x;
this.listY = y + 30;
// 刷新列表以更新位置
if (this.visible) {
this.refreshList();
}
}
/**
* 更新物品列表,应该在每一帧调用
*/
update() {
if (this.visible && this.scene.itemManager && this.scene.player) {
const player = this.scene.player;
const allItems = this.scene.itemManager.getActiveItems();
// 检查玩家位置是否变化
const playerPosChanged = this.lastPlayerX !== player.x || this.lastPlayerY !== player.y;
if (playerPosChanged) {
// 只有在玩家位置变化时才过滤物品并更新,避免不必要的计算
this.updateItems(player, allItems);
// 保存当前玩家位置
this.lastPlayerX = player.x;
this.lastPlayerY = player.y;
} else {
// 玩家位置未变化,检查物品列表是否变化
// 先过滤出可交互范围内的物品
const filteredItems = [];
allItems.forEach(item => {
if (item.isInInteractionRange(player) && !item.inInventory) {
filteredItems.push(item);
}
});
// 检查过滤后的物品列表是否变化
const itemsChanged = this.isItemsChanged(filteredItems);
if (itemsChanged) {
this.updateItems(player, allItems);
}
}
}
}
/**
* 销毁UI
*/
destroy() {
// 销毁所有UI元素
if (this.background) this.background.destroy();
if (this.title) this.title.destroy();
if (this.closeButton) this.closeButton.destroy();
if (this.closeText) this.closeText.destroy();
// 销毁所有列表项
if (this.itemListElements) {
this.itemListElements.forEach(element => {
element.destroy();
});
}
// 销毁所有操作选项
this.clearItemOptions();
}
}