Files
CINC/ui/ItemListUI.js

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();
}
}