mirror of
http://junepve.top:3000/chicrobot/CINC.git
synced 2026-10-30 07:41:29 +08:00
591 lines
17 KiB
JavaScript
591 lines
17 KiB
JavaScript
// 物品列表菜单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();
|
|
}
|
|
} |