// 背包UI组件:负责显示背包界面和处理用户交互 export class InventoryUI { constructor(scene, inventory, cellSize = 40) { this.scene = scene; this.inventory = inventory; this.cellSize = cellSize; this.isOpen = false; this.selectedItem = null; this.hoveredCell = null; // 拖动相关属性 this.isDragging = false; this.draggedItem = null; this.draggedSprite = null; this.dragOffset = { x: 0, y: 0 }; this.container = null; this.gridCells = []; this.itemSprites = new Map(); // 计算背包位置(相机右侧) this.calculatePosition(); this.createUI(); // 添加全局鼠标移动事件监听 this.scene.input.on('pointermove', (pointer) => { this.onMouseMove(pointer); }); // 添加全局鼠标释放事件监听 this.scene.input.on('pointerup', (pointer) => { this.onMouseUp(pointer); }); } /** * 计算背包位置(相机右侧) */ calculatePosition() { // 背包宽度 const inventoryWidth = this.inventory.width * this.cellSize + 20; // 固定在屏幕右侧,有一定边距 this.x = this.scene.sys.game.config.width - inventoryWidth - 20; this.y = 20; } /** * 创建背包UI */ createUI() { // 创建容器 this.container = this.scene.add.container(this.x, this.y); this.container.setDepth(100); this.container.setScrollFactor(0); // 固定在屏幕上,不随相机移动 // 创建背景 const background = this.scene.add.rectangle( 0, 0, this.inventory.width * this.cellSize + 20, this.inventory.height * this.cellSize + 20, 0x333333, 0.9 ); background.setOrigin(0, 0); background.setScrollFactor(0); // 固定在屏幕上 background.setDepth(-1); // 确保背景在所有内容下方 // 添加交互,阻止点击事件穿透到下方地图 background.setInteractive(); background.on('pointerdown', (pointer) => { return false; // 阻止事件冒泡 }); background.on('pointerover', (pointer) => { return false; // 阻止事件冒泡 }); background.on('pointerout', (pointer) => { return false; // 阻止事件冒泡 }); this.container.add(background); // 创建网格 this.createGrid(); // 创建关闭按钮 this.createCloseButton(); // 创建统计信息 this.createStats(); // 初始化时隐藏 this.hide(); } /** * 创建背包网格 */ createGrid() { const offsetX = 10; const offsetY = 10; for (let y = 0; y < this.inventory.height; y++) { this.gridCells[y] = []; for (let x = 0; x < this.inventory.width; x++) { const cellX = offsetX + x * this.cellSize; const cellY = offsetY + y * this.cellSize; // 创建单元格 const cell = this.scene.add.rectangle(cellX, cellY, this.cellSize, this.cellSize, 0x666666, 0.5); cell.setOrigin(0, 0); cell.setStrokeStyle(1, 0xffffff); cell.setScrollFactor(0); // 固定在屏幕上,不随相机移动 // 添加交互 cell.setInteractive(); // 绑定点击事件 cell.on('pointerdown', (pointer) => { console.log('单元格点击事件触发:', { x, y }); this.onCellClicked(x, y); return false; // 阻止事件冒泡 }); cell.on('pointerover', (pointer) => { this.onCellHovered(x, y); return false; // 阻止事件冒泡 }); cell.on('pointerout', (pointer) => { this.onCellUnhovered(); return false; // 阻止事件冒泡 }); this.container.add(cell); this.gridCells[y][x] = cell; } } } /** * 创建关闭按钮 */ createCloseButton() { const buttonX = this.inventory.width * this.cellSize + 25; const buttonY = 10; const button = this.scene.add.text(buttonX, buttonY, 'X', { font: '24px Arial', fill: '#ffffff' }); button.setInteractive({ useHandCursor: true }); button.on('pointerdown', (pointer) => { this.hide(); return false; // 阻止事件冒泡 }); button.on('pointerover', (pointer) => { button.setFill('#ff0000'); return false; // 阻止事件冒泡 }); button.on('pointerout', (pointer) => { button.setFill('#ffffff'); return false; // 阻止事件冒泡 }); this.container.add(button); } /** * 创建统计信息 */ createStats() { const statsX = this.inventory.width * this.cellSize + 30; const statsY = 50; // 创建统计信息背景 const statsBackground = this.scene.add.rectangle( statsX - 20, statsY - 20, 120, 100, 0x222222, 0.9 ); statsBackground.setOrigin(0, 0); statsBackground.setScrollFactor(0); // 添加交互,阻止事件穿透 statsBackground.setInteractive(); statsBackground.on('pointerdown', (pointer) => { return false; // 阻止事件冒泡 }); this.container.add(statsBackground); this.statsText = this.scene.add.text(statsX, statsY, '', { font: '12px Arial', fill: '#ffffff' }); this.container.add(this.statsText); this.updateStats(); } /** * 更新统计信息 */ updateStats() { const stats = this.inventory.getStats(); this.statsText.setText([ `物品数量: ${stats.totalItems}`, `总重量: ${stats.totalWeight.toFixed(1)}`, `总体积: ${stats.totalVolume.toFixed(1)}`, `空位数: ${stats.emptySlots}` ]); } /** * 物品精灵点击事件 * @param {Object} item - 点击的物品 */ onItemSpriteClicked(item) { console.log('onItemSpriteClicked:', item.name, 'ID:', item.id); if (this.selectedItem && this.selectedItem.id === item.id) { // 如果点击的是已选中的物品,取消选择 console.log('点击已选中物品,取消选择:', item.name); this.deselectItem(); } else { // 如果已经有选中的物品,先取消选择 if (this.selectedItem) { console.log('已有选中物品,先取消选择:', this.selectedItem.name); this.deselectItem(); } // 选中当前物品 console.log('选中物品:', item.name); this.selectItem(item); } } /** * 物品精灵鼠标按下事件,用于启动拖动 * @param {Object} item - 物品 * @param {Phaser.Input.Pointer} pointer - 指针对象 */ onItemSpriteDown(item, pointer) { console.log('onItemSpriteDown:', item.name, '开始拖动'); this.isDragging = true; this.draggedItem = item; // 如果没有选中物品,先选中 if (!this.selectedItem || this.selectedItem.id !== item.id) { if (this.selectedItem) { this.deselectItem(); } this.selectItem(item); } // 获取物品精灵 const spriteObj = this.itemSprites.get(item.id); if (spriteObj) { this.draggedSprite = spriteObj.sprite; // 计算鼠标偏移量 const spriteRect = this.draggedSprite.getBounds(); this.dragOffset.x = pointer.x - this.container.x - spriteRect.x; this.dragOffset.y = pointer.y - this.container.y - spriteRect.y; // 提升精灵深度,确保拖动时显示在最上层 this.draggedSprite.setDepth(10); } } /** * 全局鼠标移动事件处理 * @param {Phaser.Input.Pointer} pointer - 指针对象 */ onMouseMove(pointer) { // 如果还没有开始拖动,检查是否应该开始拖动 if (this.clickItem && !this.isDragging) { const distance = Math.sqrt( Math.pow(pointer.x - this.clickPosition.x, 2) + Math.pow(pointer.y - this.clickPosition.y, 2) ); // 如果移动距离超过阈值,开始拖动 if (distance > 5) { this.startDragging(); } } // 如果正在拖动,更新精灵位置 if (this.isDragging && this.draggedSprite) { // 更新拖动精灵的位置 const localX = pointer.x - this.container.x - this.dragOffset.x; const localY = pointer.y - this.container.y - this.dragOffset.y; this.draggedSprite.setPosition(localX, localY); // 检查鼠标是否悬停在某个单元格上 this.updateDragHover(pointer); } } /** * 开始拖动物品 */ startDragging() { console.log('startDragging:', this.clickItem.name); this.isDragging = true; this.draggedItem = this.clickItem; // 获取物品精灵 const spriteObj = this.itemSprites.get(this.draggedItem.id); if (spriteObj) { this.draggedSprite = spriteObj.sprite; // 计算鼠标偏移量 const spriteRect = this.draggedSprite.getBounds(); this.dragOffset.x = this.clickPosition.x - this.container.x - spriteRect.x; this.dragOffset.y = this.clickPosition.y - this.container.y - spriteRect.y; // 提升精灵深度,确保拖动时显示在最上层 this.draggedSprite.setDepth(10); } } /** * 更新拖动时的悬停状态 * @param {Phaser.Input.Pointer} pointer - 指针对象 */ updateDragHover(pointer) { // 计算鼠标在背包容器内的位置 const localX = pointer.x - this.container.x; const localY = pointer.y - this.container.y; // 计算对应的单元格坐标 const cellX = Math.floor((localX - 10) / this.cellSize); const cellY = Math.floor((localY - 10) / this.cellSize); // 检查坐标是否在背包范围内 if (cellX >= 0 && cellX < this.inventory.width && cellY >= 0 && cellY < this.inventory.height) { // 更新悬停单元格 if (!this.hoveredCell || this.hoveredCell.x !== cellX || this.hoveredCell.y !== cellY) { // 移除之前的悬停效果 if (this.hoveredCell) { this.onCellUnhovered(); } // 设置新的悬停单元格 this.onCellHovered(cellX, cellY); } } else { // 鼠标移出背包范围,移除悬停效果 if (this.hoveredCell) { this.onCellUnhovered(); } } } /** * 全局鼠标释放事件处理 * @param {Phaser.Input.Pointer} pointer - 指针对象 */ onMouseUp(pointer) { // 如果正在拖动,处理拖动结束 if (this.isDragging && this.draggedItem) { console.log('onMouseUp: 结束拖动'); // 计算鼠标在背包容器内的位置 const localX = pointer.x - this.container.x; const localY = pointer.y - this.container.y; // 计算对应的单元格坐标 const cellX = Math.floor((localX - 10) / this.cellSize); const cellY = Math.floor((localY - 10) / this.cellSize); // 尝试将物品放置到新位置 let success = false; // 检查坐标是否在背包范围内 if (cellX >= 0 && cellX < this.inventory.width && cellY >= 0 && cellY < this.inventory.height) { const targetItem = this.inventory.getItemAt(cellX, cellY); if (targetItem) { // 如果目标位置有物品,尝试交换 success = this.inventory.swapItems(this.draggedItem.id, targetItem.id); } else { // 如果目标位置是空的,尝试移动 success = this.inventory.moveItem(this.draggedItem.id, cellX, cellY); } } // 重置拖动状态 this.isDragging = false; if (this.draggedSprite) { this.draggedSprite.setDepth(1); // 恢复精灵深度 this.draggedSprite = null; } // 更新UI if (success) { this.deselectItem(); this.updateUI(); } else { // 如果移动失败,重置精灵位置 this.updateUI(); } this.draggedItem = null; this.dragOffset = { x: 0, y: 0 }; // 移除悬停效果 if (this.hoveredCell) { this.onCellUnhovered(); } } // 重置点击状态 this.clickItem = null; this.clickPosition = null; this.clickTime = null; } /** * 单元格点击事件 * @param {number} x - 单元格X坐标 * @param {number} y - 单元格Y坐标 */ onCellClicked(x, y) { console.log('onCellClicked:', { x, y }); const item = this.inventory.getItemAt(x, y); if (this.selectedItem) { console.log('已有选中物品:', this.selectedItem.name, 'ID:', this.selectedItem.id); let success = false; // 如果有选中的物品,尝试移动它 if (item) { // 如果点击的是另一个物品,尝试交换 console.log('点击另一个物品,尝试交换:', item.name, 'ID:', item.id); success = this.inventory.swapItems(this.selectedItem.id, item.id); console.log('交换结果:', success); } else { // 如果点击的是空位,尝试移动 console.log('点击空位,尝试移动物品:', this.selectedItem.name, '到位置:', { x, y }); success = this.inventory.moveItem(this.selectedItem.id, x, y); console.log('移动结果:', success); } if (success) { // 只有操作成功时才取消选择并更新UI console.log('操作成功,取消选择并更新UI'); this.deselectItem(); this.updateUI(); } else { console.log('操作失败,保持选择状态'); } } else if (item) { // 如果没有选中的物品,选中点击的物品 console.log('没有选中物品,选中点击的物品:', item.name); this.selectItem(item); } else { console.log('点击空位,没有选中物品,无操作'); } } /** * 单元格悬停事件 * @param {number} x - 单元格X坐标 * @param {number} y - 单元格Y坐标 */ onCellHovered(x, y) { this.hoveredCell = { x, y }; // 高亮显示悬停的单元格 this.gridCells[y][x].setFillStyle(0x888888, 0.7); // 显示物品信息 const item = this.inventory.getItemAt(x, y); if (item) { this.showItemInfo(item); } } /** * 单元格取消悬停事件 */ onCellUnhovered() { if (this.hoveredCell) { // 恢复单元格颜色 const { x, y } = this.hoveredCell; this.gridCells[y][x].setFillStyle(0x666666, 0.5); this.hoveredCell = null; } // 隐藏物品信息 this.hideItemInfo(); } /** * 选择物品 * @param {Object} item - 要选择的物品 */ selectItem(item) { this.selectedItem = item; // 高亮显示物品边框,不改变单元格颜色 const spriteObj = this.itemSprites.get(item.id); if (spriteObj) { spriteObj.border.setStrokeStyle(2, 0xffff00, 1); // 黄色边框表示选中 } console.log('选中物品:', item.name); } /** * 取消选择物品 */ deselectItem() { if (!this.selectedItem) return; // 恢复物品边框颜色,不改变单元格颜色 const spriteObj = this.itemSprites.get(this.selectedItem.id); if (spriteObj) { spriteObj.border.setStrokeStyle(1, 0xffffff, 1); // 恢复白色边框 } console.log('取消选择物品:', this.selectedItem.name); this.selectedItem = null; } /** * 显示物品信息 * @param {Object} item - 物品 */ showItemInfo(item) { // 创建物品信息面板 this.itemInfoPanel = this.scene.add.rectangle(0, 0, 200, 100, 0x000000, 0.8); this.itemInfoPanel.setOrigin(0, 0); this.itemInfoPanel.setStrokeStyle(1, 0xffffff); this.itemInfoPanel.setPosition( this.container.x + this.inventory.width * this.cellSize + 50, this.container.y + this.inventory.height * this.cellSize + 20 ); // 阻止事件冒泡,防止点击物品信息面板时触发角色移动 this.itemInfoPanel.setInteractive(); this.itemInfoPanel.on('pointerdown', (pointer) => { return false; // 阻止事件冒泡 }); // 文本本身不需要交互,只需要面板阻止事件即可 // 如果需要文本也阻止事件,可以添加setInteractive和事件处理 this.itemInfoPanel.setScrollFactor(0); // 固定在屏幕上 // 添加物品信息文本 this.itemInfoText = this.scene.add.text( this.itemInfoPanel.x + 10, this.itemInfoPanel.y + 10, `${item.name}\n${item.description}\n重量: ${item.weight}\n价值: ${item.value}`, { font: '12px Arial', fill: '#ffffff', wordWrap: { width: 180 } } ); this.itemInfoText.setScrollFactor(0); // 固定在屏幕上 this.container.add(this.itemInfoPanel); this.container.add(this.itemInfoText); } /** * 隐藏物品信息 */ hideItemInfo() { if (this.itemInfoPanel) { this.itemInfoPanel.destroy(); this.itemInfoText.destroy(); this.itemInfoPanel = null; this.itemInfoText = null; } } /** * 更新UI显示 */ updateUI() { // 移除所有物品精灵和边框 this.itemSprites.forEach(spriteObj => { spriteObj.sprite.destroy(); spriteObj.border.destroy(); }); this.itemSprites.clear(); // 重新添加所有物品精灵 const items = this.inventory.getAllItems(); if (items instanceof Map) { // 如果是Map对象,使用Map的forEach方法 items.forEach(item => { this.addItemSprite(item); }); } else { // 如果是数组,使用数组的forEach方法 items.forEach(item => { this.addItemSprite(item); }); } // 更新统计信息 this.updateStats(); } /** * 添加物品精灵 * @param {Object} item - 物品 */ addItemSprite(item) { const { x, y } = item.position; const { width, height } = item.gridSize; // 计算精灵位置和大小 const spriteX = 10 + x * this.cellSize; const spriteY = 10 + y * this.cellSize; const spriteWidth = width * this.cellSize; const spriteHeight = height * this.cellSize; // 创建精灵 - 使用物品类型ID而不是唯一ID,因为纹理是用物品类型ID创建的 const sprite = this.scene.add.sprite(spriteX, spriteY, item.itemTypeId || item.id); sprite.setOrigin(0, 0); sprite.setDisplaySize(spriteWidth, spriteHeight); sprite.setScrollFactor(0); // 固定在屏幕上 sprite.setDepth(1); // 确保精灵在单元格之上 // 添加边框 const border = this.scene.add.rectangle(spriteX, spriteY, spriteWidth, spriteHeight, 0xffffff, 0); border.setOrigin(0, 0); border.setStrokeStyle(1, 0xffffff); border.setScrollFactor(0); // 固定在屏幕上 border.setDepth(2); // 确保边框在精灵之上 // 处理物品精灵的点击事件 sprite.setInteractive(); sprite.on('pointerdown', (pointer) => { this.onItemSpriteDown(item, pointer); return false; // 阻止事件冒泡 }); sprite.on('pointerup', (pointer) => { this.onItemSpriteUp(item, pointer); return false; // 阻止事件冒泡 }); border.setInteractive(); border.on('pointerdown', (pointer) => { this.onItemSpriteDown(item, pointer); return false; // 阻止事件冒泡 }); border.on('pointerup', (pointer) => { this.onItemSpriteUp(item, pointer); return false; // 阻止事件冒泡 }); this.container.add(sprite); this.container.add(border); this.itemSprites.set(item.id, { sprite, border }); } /** * 显示背包 */ show() { this.container.setVisible(true); this.isOpen = true; this.updateUI(); } /** * 隐藏背包 */ hide() { this.container.setVisible(false); this.isOpen = false; this.deselectItem(); this.hideItemInfo(); } /** * 切换背包显示 */ toggle() { if (this.isOpen) { this.hide(); } else { this.show(); } } /** * 添加物品到背包并更新UI * @param {Object} item - 要添加的物品 * @returns {boolean} 是否添加成功 */ addItem(item) { const success = this.inventory.addItem(item); if (success) { this.updateUI(); } return success; } /** * 从背包中移除物品并更新UI * @param {string} itemId - 物品ID * @returns {Object|null} 移除的物品 */ removeItem(itemId) { const item = this.inventory.removeItem(itemId); if (item) { this.updateUI(); } return item; } /** * 销毁背包UI */ destroy() { this.container.destroy(); } }