From 35e10a2abccfa2482bdd58a779adbe071be4649b Mon Sep 17 00:00:00 2001 From: chicrobot Date: Mon, 5 Jan 2026 02:55:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(inventory)=20=E6=B7=BB=E5=8A=A0=E8=83=8C?= =?UTF-8?q?=E5=8C=85=E7=89=A9=E5=93=81=E7=A7=BB=E5=8A=A8=E3=80=81=E6=8B=BE?= =?UTF-8?q?=E5=8F=96=E5=88=B0=E8=83=8C=E5=8C=85=E3=80=81=E4=B8=A2=E5=BC=83?= =?UTF-8?q?=E8=83=8C=E5=8C=85=E7=89=A9=E5=93=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- items/Inventory.js | 138 ++++++++++++-------- items/Item.js | 5 + items/ItemManager.js | 21 +++ scenes/InventoryTestScene.js | 6 +- systems/InputSystem.js | 114 ++++++++++++++++- ui/InventoryUI.js | 239 +++++++++++++++++++++++++++-------- 6 files changed, 408 insertions(+), 115 deletions(-) diff --git a/items/Inventory.js b/items/Inventory.js index 5333098..e858ec4 100644 --- a/items/Inventory.js +++ b/items/Inventory.js @@ -26,25 +26,29 @@ export class Inventory { } /** - * 检查物品是否可以放置在指定位置 + * 检查物品是否可以放置在指定位置(以物品中心为基础点) * @param {Object} item - 要放置的物品 - * @param {number} x - 起始X坐标 - * @param {number} y - 起始Y坐标 + * @param {number} centerX - 中心X坐标 + * @param {number} centerY - 中心Y坐标 * @returns {boolean} 是否可以放置 */ - canPlaceItem(item, x, y) { + canPlaceItem(item, centerX, centerY) { const { gridSize } = item; + // 将中心坐标转换为左上角坐标 + const topLeftX = Math.floor(centerX - (gridSize.width - 1) / 2); + const topLeftY = Math.floor(centerY - (gridSize.height - 1) / 2); + // 检查物品尺寸是否在背包范围内 - if (x < 0 || y < 0 || x + gridSize.width > this.width || y + gridSize.height > this.height) { + if (topLeftX < 0 || topLeftY < 0 || topLeftX + gridSize.width > this.width || topLeftY + gridSize.height > this.height) { return false; } // 检查物品占用的所有格子是否都是空的或属于物品自身 for (let dy = 0; dy < gridSize.height; dy++) { for (let dx = 0; dx < gridSize.width; dx++) { - const gridX = x + dx; - const gridY = y + dy; + const gridX = topLeftX + dx; + const gridY = topLeftY + dy; const cellItemId = this.grid[gridY][gridX]; // 如果格子不是空的,且不是物品自身,则不能放置 @@ -58,29 +62,39 @@ export class Inventory { } /** - * 放置物品到背包中 + * 放置物品到背包中(以物品中心为基础点) * @param {Object} item - 要放置的物品 - * @param {number} x - 起始X坐标 - * @param {number} y - 起始Y坐标 + * @param {number} centerX - 中心X坐标 + * @param {number} centerY - 中心Y坐标 * @returns {boolean} 是否放置成功 */ - placeItem(item, x, y) { - if (!this.canPlaceItem(item, x, y)) { + placeItem(item, centerX, centerY) { + if (!this.canPlaceItem(item, centerX, centerY)) { return false; } const { gridSize } = item; + // 将中心坐标转换为左上角坐标 + const topLeftX = Math.floor(centerX - (gridSize.width - 1) / 2); + const topLeftY = Math.floor(centerY - (gridSize.height - 1) / 2); + + // 计算物品中心坐标(用于物品信息) + const centerPos = { + x: centerX, + y: centerY + }; + // 记录物品在背包中的位置信息 - 同时设置position和positionInInventory确保一致性 - item.position = { x, y }; - item.positionInInventory = { x, y }; + item.position = centerPos; + item.positionInInventory = centerPos; item.inventory = this; item.inInventory = true; // 更新网格和物品列表 for (let dy = 0; dy < gridSize.height; dy++) { for (let dx = 0; dx < gridSize.width; dx++) { - this.grid[y + dy][x + dx] = item.id; + this.grid[topLeftY + dy][topLeftX + dx] = item.id; } } @@ -120,10 +134,14 @@ export class Inventory { const { position, gridSize } = item; + // 计算物品占用的左上角坐标(从中心坐标转换) + const topLeftX = Math.floor(position.x - (gridSize.width - 1) / 2); + const topLeftY = Math.floor(position.y - (gridSize.height - 1) / 2); + // 清空网格 for (let dy = 0; dy < gridSize.height; dy++) { for (let dx = 0; dx < gridSize.width; dx++) { - this.grid[position.y + dy][position.x + dx] = null; + this.grid[topLeftY + dy][topLeftX + dx] = null; } } @@ -140,59 +158,65 @@ export class Inventory { } /** - * 移动背包中的物品 + * 移动背包中的物品(以物品中心为基础点) * @param {string} itemId - 物品ID - * @param {number} newX - 新的X坐标 - * @param {number} newY - 新的Y坐标 + * @param {number} newCenterX - 新的中心X坐标 + * @param {number} newCenterY - 新的中心Y坐标 * @returns {boolean} 是否移动成功 */ - moveItem(itemId, newX, newY) { - console.log('moveItem called:', itemId, 'to position:', { newX, newY }); + moveItem(itemId, newCenterX, newCenterY) { + console.log('moveItem called:', itemId, 'to center position:', { newCenterX, newCenterY }); const item = this.items.get(itemId); if (!item) { console.log('moveItem failed: 物品不存在'); return false; } - console.log('移动物品:', item.name, '当前位置:', item.position, '目标位置:', { newX, newY }); + console.log('移动物品:', item.name, '当前中心位置:', item.position, '目标中心位置:', { newCenterX, newCenterY }); // 检查新位置是否可以放置 - if (!this.canPlaceItem(item, newX, newY)) { + if (!this.canPlaceItem(item, newCenterX, newCenterY)) { console.log('moveItem failed: 新位置无法放置物品'); return false; } - // 保存旧位置信息 - const oldX = item.position.x; - const oldY = item.position.y; const { gridSize } = item; - console.log('清空旧位置网格:', { oldX, oldY }, '尺寸:', gridSize); - // 清空旧位置的网格 + // 清空旧位置的网格(需要先计算旧位置的左上角坐标) + const oldCenterX = item.position.x; + const oldCenterY = item.position.y; + const oldTopLeftX = Math.floor(oldCenterX - (gridSize.width - 1) / 2); + const oldTopLeftY = Math.floor(oldCenterY - (gridSize.height - 1) / 2); + + console.log('清空旧位置网格:', { oldTopLeftX, oldTopLeftY }, '尺寸:', gridSize); for (let dy = 0; dy < gridSize.height; dy++) { for (let dx = 0; dx < gridSize.width; dx++) { - this.grid[oldY + dy][oldX + dx] = null; + this.grid[oldTopLeftY + dy][oldTopLeftX + dx] = null; } } - // 更新物品位置 - item.position = { x: newX, y: newY }; - item.positionInInventory = { x: newX, y: newY }; - console.log('更新物品位置:', item.position); + // 更新物品位置为新的中心坐标 + const newCenterPos = { x: newCenterX, y: newCenterY }; + item.position = newCenterPos; + item.positionInInventory = newCenterPos; + console.log('更新物品中心位置:', item.position); + + // 更新新位置的网格(需要计算新位置的左上角坐标) + const newTopLeftX = Math.floor(newCenterX - (gridSize.width - 1) / 2); + const newTopLeftY = Math.floor(newCenterY - (gridSize.height - 1) / 2); - // 更新新位置的网格 for (let dy = 0; dy < gridSize.height; dy++) { for (let dx = 0; dx < gridSize.width; dx++) { - this.grid[newY + dy][newX + dx] = item.id; + this.grid[newTopLeftY + dy][newTopLeftX + dx] = item.id; } } - console.log('moveItem success:', item.name, '已移动到', { newX, newY }); + console.log('moveItem success:', item.name, '已移动到中心位置', { newCenterX, newCenterY }); return true; } /** - * 交换两个物品的位置 + * 交换两个物品的位置(以物品中心为基础点) * @param {string} itemId1 - 第一个物品ID * @param {string} itemId2 - 第二个物品ID * @returns {boolean} 是否交换成功 @@ -208,33 +232,34 @@ export class Inventory { } console.log('交换物品:', item1.name, '与', item2.name); - console.log('物品1位置:', item1.position, '物品2位置:', item2.position); + console.log('物品1中心位置:', item1.position, '物品2中心位置:', item2.position); - const pos1 = { ...item1.position }; - const pos2 = { ...item2.position }; + // 保存两个物品的中心位置 + const centerPos1 = { ...item1.position }; + const centerPos2 = { ...item2.position }; // 移除两个物品 console.log('移除两个物品'); this.removeItem(itemId1); this.removeItem(itemId2); - // 尝试在对方位置放置 - console.log('尝试将物品1放置到物品2的位置:', pos2); - if (!this.placeItem(item1, pos2.x, pos2.y)) { + // 尝试在对方位置放置(使用中心坐标) + console.log('尝试将物品1放置到物品2的中心位置:', centerPos2); + if (!this.placeItem(item1, centerPos2.x, centerPos2.y)) { // 回滚:将两个物品放回原位置 console.log('swapItems failed: 物品1无法放置到物品2的位置,回滚'); - this.placeItem(item1, pos1.x, pos1.y); - this.placeItem(item2, pos2.x, pos2.y); + this.placeItem(item1, centerPos1.x, centerPos1.y); + this.placeItem(item2, centerPos2.x, centerPos2.y); return false; } - console.log('尝试将物品2放置到物品1的位置:', pos1); - if (!this.placeItem(item2, pos1.x, pos1.y)) { + console.log('尝试将物品2放置到物品1的中心位置:', centerPos1); + if (!this.placeItem(item2, centerPos1.x, centerPos1.y)) { // 回滚:将第一个物品放回原位置,第二个物品也放回原位置 console.log('swapItems failed: 物品2无法放置到物品1的位置,回滚'); this.removeItem(itemId1); - this.placeItem(item1, pos1.x, pos1.y); - this.placeItem(item2, pos2.x, pos2.y); + this.placeItem(item1, centerPos1.x, centerPos1.y); + this.placeItem(item2, centerPos2.x, centerPos2.y); return false; } @@ -243,9 +268,9 @@ export class Inventory { } /** - * 查找第一个可用的空位 + * 查找第一个可用的空位(返回中心坐标) * @param {Object} item - 要放置的物品 - * @returns {Object|null} 可用位置 + * @returns {Object|null} 可用中心位置 */ findEmptySpot(item) { const { gridSize } = item; @@ -256,11 +281,16 @@ export class Inventory { return null; } - // 遍历背包网格,寻找可用位置 + // 遍历背包网格,寻找可用位置(以左上角为起点) for (let y = 0; y <= this.height - gridSize.height; y++) { for (let x = 0; x <= this.width - gridSize.width; x++) { - if (this.canPlaceItem(item, x, y)) { - return { x, y }; + // 计算该左上角位置对应的中心坐标 + // 注意:这里需要确保与canPlaceItem方法中的坐标转换对称 + const centerX = x + (gridSize.width - 1) / 2; + const centerY = y + (gridSize.height - 1) / 2; + + if (this.canPlaceItem(item, centerX, centerY)) { + return { x: centerX, y: centerY }; } } } diff --git a/items/Item.js b/items/Item.js index 86884df..021d9c0 100644 --- a/items/Item.js +++ b/items/Item.js @@ -111,6 +111,11 @@ export default class Item extends Phaser.GameObjects.Sprite { this.inInventory = true; this.visible = false; // 在场景中隐藏物品 this.scene.events.emit('item-picked-up', this, user); + + // 如果场景中有背包UI,更新UI + if (this.scene.inventoryUI) { + this.scene.inventoryUI.updateUI(); + } } else { console.log('背包空间不足'); } diff --git a/items/ItemManager.js b/items/ItemManager.js index 38027d0..a51b882 100644 --- a/items/ItemManager.js +++ b/items/ItemManager.js @@ -278,6 +278,27 @@ export class ItemManager { return this.itemTemplates.get(itemId) || null; } + /** + * 获取当前场景中的所有活动物品 + * @returns {Array} - 活动物品数组 + */ + getActiveItems() { + return Array.from(this.activeItems); + } + + /** + * 清理所有物品 + */ + clearAllItems() { + // 销毁所有活动物品 + this.activeItems.forEach(item => { + item.destroy(); + }); + + // 清空活动物品集合 + this.activeItems.clear(); + } + /** * 获取所有物品模板 * @returns {Map} - 所有物品模板的Map对象 diff --git a/scenes/InventoryTestScene.js b/scenes/InventoryTestScene.js index 0d52d6c..a42d7c1 100644 --- a/scenes/InventoryTestScene.js +++ b/scenes/InventoryTestScene.js @@ -180,8 +180,8 @@ export default class InventoryTestScene extends Phaser.Scene { // 键盘输入 this.cursors = this.input.keyboard.createCursorKeys(); - // 背包快捷键(I键) - this.input.keyboard.on('keydown-I', () => { + // 背包快捷键(B键) + this.input.keyboard.on('keydown-B', () => { this.inventoryUI.toggle(); }); @@ -381,7 +381,7 @@ export default class InventoryTestScene extends Phaser.Scene { key4Text.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动 key4Text.setDepth(10); // 设置深度,确保显示在侧边栏上方 - const keyIText = this.add.text(10, 220, '按I键:打开/关闭背包', { + const keyIText = this.add.text(10, 220, '按B键:打开/关闭背包', { fontSize: '16px', fill: '#ffffff' }); diff --git a/systems/InputSystem.js b/systems/InputSystem.js index a228ef8..a57824a 100644 --- a/systems/InputSystem.js +++ b/systems/InputSystem.js @@ -39,6 +39,12 @@ export class InputSystem { // 右移键:同时支持D键和方向右键 right: [kb.addKey(Phaser.Input.Keyboard.KeyCodes.D), kb.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT)] }; + + // 添加F键快捷拾取功能 + this.fKey = kb.addKey(Phaser.Input.Keyboard.KeyCodes.F); + this.fKey.on('down', () => { + this.handleQuickPickup(); + }); // 禁用浏览器右键菜单,避免影响游戏操作 scene.input.mouse.disableContextMenu(); @@ -85,8 +91,8 @@ export class InputSystem { // 处理鼠标/触摸按下事件 // @param {Object} pointer - 指针事件对象,包含鼠标/触摸的位置等信息 onPointerDown(pointer) { - // 检查鼠标是否在小地图、摇杆或侧边栏上,如果是则不处理 - if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer) || this.isMouseOnSidebar(pointer)) { + // 检查鼠标是否在UI元素上,如果是则不处理 + if (this.isMouseOnUI(pointer)) { return; } @@ -157,8 +163,8 @@ export class InputSystem { // 处理鼠标/触摸移动事件 // @param {Object} pointer - 指针事件对象 onPointerMove(pointer) { - // 检查鼠标是否在小地图、摇杆或侧边栏上,如果是则不处理 - if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer) || this.isMouseOnSidebar(pointer)) { + // 检查鼠标是否在UI元素上,如果是则不处理 + if (this.isMouseOnUI(pointer)) { return; } @@ -193,8 +199,8 @@ export class InputSystem { // 处理鼠标/触摸释放事件 // @param {Object} pointer - 指针事件对象 onPointerUp(pointer) { - // 检查鼠标是否在小地图、摇杆或侧边栏上,如果是则不处理 - if (this.isMouseOnMiniMap(pointer) || this.isMouseOnJoystick(pointer) || this.isMouseOnSidebar(pointer)) { + // 检查鼠标是否在UI元素上,如果是则不处理 + if (this.isMouseOnUI(pointer)) { // 重置框选起始点和拖动状态 this.selectionStart = null; this.dragStart = null; @@ -278,6 +284,18 @@ export class InputSystem { mouseY >= mapTop && mouseY <= mapBottom; } + /** + * 检查鼠标是否在UI元素上 + * @param {Phaser.Input.Pointer} pointer - 指针事件对象 + * @returns {boolean} - 如果鼠标在UI元素上返回true,否则返回false + */ + isMouseOnUI(pointer) { + return this.isMouseOnMiniMap(pointer) || + this.isMouseOnJoystick(pointer) || + this.isMouseOnSidebar(pointer) || + this.isMouseOnInventoryUI(pointer); + } + /** * 检查鼠标是否在虚拟摇杆上 * @param {Phaser.Input.Pointer} pointer - 指针事件对象 @@ -349,4 +367,88 @@ export class InputSystem { return mouseX >= bounds.x && mouseX <= bounds.right && mouseY >= bounds.y && mouseY <= bounds.bottom; } + + /** + * 检查鼠标是否在背包UI上 + * @param {Phaser.Input.Pointer} pointer - 指针事件对象 + * @returns {boolean} - 如果鼠标在背包UI上返回true,否则返回false + */ + isMouseOnInventoryUI(pointer) { + // 检查场景中是否有背包UI + if (!this.scene.inventoryUI) { + return false; + } + + // 获取背包UI实例 + const inventoryUI = this.scene.inventoryUI; + // 获取鼠标在屏幕上的坐标 + const mouseX = pointer.x; + const mouseY = pointer.y; + + // 获取背包UI的边界 + const container = inventoryUI.container; + if (!container) { + return false; + } + + const containerBounds = container.getBounds(); + + // 检查鼠标坐标是否在背包UI边界内 + return mouseX >= containerBounds.x && mouseX <= containerBounds.right && + mouseY >= containerBounds.y && mouseY <= containerBounds.bottom; + } + + /** + * 处理F键快捷拾取功能 + */ + handleQuickPickup() { + console.log('handleQuickPickup: 尝试快捷拾取物品'); + + // 确保场景中有玩家和物品管理器 + if (!this.scene.player || !this.scene.itemManager) { + console.log('handleQuickPickup: 缺少玩家或物品管理器'); + return; + } + + const player = this.scene.player; + const itemManager = this.scene.itemManager; + + // 获取所有活动物品 + const activeItems = itemManager.getActiveItems ? itemManager.getActiveItems() : []; + if (activeItems.length === 0) { + console.log('handleQuickPickup: 没有可拾取的物品'); + return; + } + + const pickupDistance = 100; // 最大拾取距离 + let closestItem = null; + let minDistance = pickupDistance; + + // 找到距离玩家最近且在拾取范围内的物品 + activeItems.forEach(item => { + // 计算物品与玩家的距离 + const dx = item.x - player.x; + const dy = item.y - player.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + // 如果物品在拾取范围内且距离更近,更新最近物品 + if (distance < minDistance) { + minDistance = distance; + closestItem = item; + } + }); + + // 如果找到最近物品,尝试拾取 + if (closestItem) { + console.log(`handleQuickPickup: 拾取距离最近的物品: ${closestItem.name},距离: ${Math.round(minDistance)}`); + closestItem.pickup(player); + + // 更新背包UI,确保物品立即显示在背包中 + if (this.scene.inventoryUI) { + this.scene.inventoryUI.updateUI(); + } + } else { + console.log('handleQuickPickup: 没有在拾取范围内的物品'); + } + } } \ No newline at end of file diff --git a/ui/InventoryUI.js b/ui/InventoryUI.js index 9584d3f..17e79a3 100644 --- a/ui/InventoryUI.js +++ b/ui/InventoryUI.js @@ -36,13 +36,13 @@ export class InventoryUI { } /** - * 计算背包位置(相机右侧) + * 计算背包位置(屏幕左上角) */ calculatePosition() { // 背包宽度 const inventoryWidth = this.inventory.width * this.cellSize + 20; - // 固定在屏幕右侧,有一定边距 - this.x = this.scene.sys.game.config.width - inventoryWidth - 20; + // 固定在屏幕左上角,有一定边距 + this.x = 20; this.y = 20; } @@ -237,35 +237,34 @@ export class InventoryUI { } /** - * 物品精灵鼠标按下事件,用于启动拖动 + * 物品精灵鼠标按下事件,用于记录点击状态和位置 * @param {Object} item - 物品 * @param {Phaser.Input.Pointer} pointer - 指针对象 */ onItemSpriteDown(item, pointer) { - console.log('onItemSpriteDown:', item.name, '开始拖动'); - this.isDragging = true; - this.draggedItem = item; + console.log('onItemSpriteDown:', item.name, '记录点击位置'); + // 记录点击的物品和位置,用于判断是否开始拖动 + this.clickItem = item; + this.clickPosition = { x: pointer.x, y: pointer.y }; + this.clickTime = Date.now(); - // 如果没有选中物品,先选中 - if (!this.selectedItem || this.selectedItem.id !== item.id) { + // 切换选择状态:如果已选中,则取消选择;如果未选中,则选中 + if (this.selectedItem && this.selectedItem.id === item.id) { + // 如果点击的是已选中物品,取消选择 + console.log('点击已选中物品精灵,取消选择:', item.name); + this.deselectItem(); + } else { + // 如果没有选中物品或点击的是另一个物品,选中当前物品 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); - } + // 不立即开始拖动,只在鼠标移动超过阈值时才开始 + this.isDragging = false; + this.draggedItem = null; + this.draggedSprite = null; } /** @@ -286,13 +285,13 @@ export class InventoryUI { } } - // 如果正在拖动,更新精灵位置 + // 如果正在拖动,更新精灵位置,使其中心锁定在鼠标位置 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; + // 更新拖动精灵的位置,使其中心锁定在鼠标位置 + const spriteX = pointer.x - this.container.x - this.spriteCenterOffset.x; + const spriteY = pointer.y - this.container.y - this.spriteCenterOffset.y; - this.draggedSprite.setPosition(localX, localY); + this.draggedSprite.setPosition(spriteX, spriteY); // 检查鼠标是否悬停在某个单元格上 this.updateDragHover(pointer); @@ -307,22 +306,94 @@ export class InventoryUI { this.isDragging = true; this.draggedItem = this.clickItem; + // 拖动时隐藏物品详情 + this.hideItemInfo(); + // 获取物品精灵 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.spriteCenterOffset = { + x: spriteRect.width / 2, + y: spriteRect.height / 2 + }; // 提升精灵深度,确保拖动时显示在最上层 this.draggedSprite.setDepth(10); + + // 创建预计放下位置的预览精灵 + this.createDragPreview(); } } /** - * 更新拖动时的悬停状态 + * 创建预计放下位置的预览 + */ + createDragPreview() { + if (this.dragPreview) { + this.dragPreview.destroy(); + this.dragPreview = null; + } + + const { width, height } = this.draggedItem.gridSize; + const previewWidth = width * this.cellSize; + const previewHeight = height * this.cellSize; + + // 创建预览矩形(半透明,用于显示预计放下位置) + this.dragPreview = this.scene.add.rectangle(0, 0, previewWidth, previewHeight, 0xffffff, 0.3); + this.dragPreview.setOrigin(0, 0); + this.dragPreview.setStrokeStyle(2, 0x00ff00, 1); + this.dragPreview.setScrollFactor(0); // 固定在屏幕上 + this.dragPreview.setDepth(5); // 确保预览在背景之上,在拖动的精灵之下 + + this.container.add(this.dragPreview); + } + + /** + * 更新预计放下位置的预览 + * @param {number} centerX - 预计放下的中心X坐标 + * @param {number} centerY - 预计放下的中心Y坐标 + */ + updateDragPreview(centerX, centerY) { + if (!this.dragPreview || !this.draggedItem) return; + + const { width, height } = this.draggedItem.gridSize; + + // 计算预计放下位置的左上角坐标 + const topLeftX = Math.floor(centerX - (width - 1) / 2); + const topLeftY = Math.floor(centerY - (height - 1) / 2); + + // 更新预览位置 + const previewX = 10 + topLeftX * this.cellSize; + const previewY = 10 + topLeftY * this.cellSize; + this.dragPreview.setPosition(previewX, previewY); + + // 根据是否可以放置更新预览颜色 + const canPlace = this.inventory.canPlaceItem(this.draggedItem, centerX, centerY); + if (canPlace) { + this.dragPreview.setFillStyle(0x00ff00, 0.3); + this.dragPreview.setStrokeStyle(2, 0x00ff00, 1); + } else { + this.dragPreview.setFillStyle(0xff0000, 0.3); + this.dragPreview.setStrokeStyle(2, 0xff0000, 1); + } + } + + /** + * 销毁预计放下位置的预览 + */ + destroyDragPreview() { + if (this.dragPreview) { + this.dragPreview.destroy(); + this.dragPreview = null; + } + } + + /** + * 更新拖动时的悬停状态和预览位置 * @param {Phaser.Input.Pointer} pointer - 指针对象 */ updateDragHover(pointer) { @@ -345,11 +416,24 @@ export class InventoryUI { // 设置新的悬停单元格 this.onCellHovered(cellX, cellY); } + + // 更新预计放下位置的预览 + this.updateDragPreview(cellX, cellY); + + // 确保预览可见 + if (this.dragPreview) { + this.dragPreview.setVisible(true); + } } else { // 鼠标移出背包范围,移除悬停效果 if (this.hoveredCell) { this.onCellUnhovered(); } + + // 隐藏预览 + if (this.dragPreview) { + this.dragPreview.setVisible(false); + } } } @@ -377,15 +461,52 @@ export class InventoryUI { 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); + // 检查新位置是否与旧位置完全相同 + const isSamePosition = this.draggedItem.position.x === cellX && this.draggedItem.position.y === cellY; + + // 如果目标位置是物品原来的位置且位置完全相同,直接取消拖动 + if (targetItem && targetItem.id === this.draggedItem.id && isSamePosition) { + console.log('拖动到原起始位置,直接取消拖动'); + success = true; + } else if (targetItem) { + if (targetItem.id === this.draggedItem.id) { + // 如果目标位置是当前拖动物品的非起始位置,尝试移动 + console.log('拖动到当前物品的非起始位置,尝试移动:', this.draggedItem.name, '到位置:', { cellX, cellY }); + success = this.inventory.moveItem(this.draggedItem.id, cellX, cellY); + console.log('移动结果:', success); + } else { + // 如果目标位置有其他物品,尝试交换 + success = this.inventory.swapItems(this.draggedItem.id, targetItem.id); + } } else { // 如果目标位置是空的,尝试移动 success = this.inventory.moveItem(this.draggedItem.id, cellX, cellY); } + } else { + // 鼠标释放位置不在背包范围内,执行丢弃操作 + console.log('拖动出背包,执行丢弃操作:', this.draggedItem.name); + + // 获取物品ID和基本信息 + const itemId = this.draggedItem.itemTypeId || this.draggedItem.id; + + // 从背包中移除物品 + const removedItem = this.inventory.removeItem(this.draggedItem.id); + + if (removedItem) { + // 在角色位置创建该物品 + if (this.scene.itemManager) { + const player = this.scene.player; + this.scene.itemManager.createItem(itemId, player.x, player.y); + console.log('已在角色位置创建丢弃的物品:', removedItem.name); + } + + success = true; + } } + // 销毁预计放下位置的预览 + this.destroyDragPreview(); + // 重置拖动状态 this.isDragging = false; if (this.draggedSprite) { @@ -403,7 +524,7 @@ export class InventoryUI { } this.draggedItem = null; - this.dragOffset = { x: 0, y: 0 }; + this.spriteCenterOffset = null; // 移除悬停效果 if (this.hoveredCell) { @@ -428,9 +549,17 @@ export class InventoryUI { if (this.selectedItem) { console.log('已有选中物品:', this.selectedItem.name, 'ID:', this.selectedItem.id); + + // 如果点击的是已选中物品,直接取消选择(无论点击的是哪个位置) + if (item && item.id === this.selectedItem.id) { + console.log('点击已选中物品,取消选择:', item.name); + this.deselectItem(); + return; + } + + // 尝试移动或交换物品 let success = false; - // 如果有选中的物品,尝试移动它 if (item) { // 如果点击的是另一个物品,尝试交换 console.log('点击另一个物品,尝试交换:', item.name, 'ID:', item.id); @@ -471,11 +600,12 @@ export class InventoryUI { // 高亮显示悬停的单元格 this.gridCells[y][x].setFillStyle(0x888888, 0.7); - // 显示物品信息 - const item = this.inventory.getItemAt(x, y); + // 只有在没有选中物品且不是拖动状态时,才不显示物品信息(根据需求,只在选中时显示) + // 注释掉显示物品信息的逻辑,只在选中时显示 + /* const item = this.inventory.getItemAt(x, y); if (item) { this.showItemInfo(item); - } + } */ } /** @@ -489,8 +619,11 @@ export class InventoryUI { this.hoveredCell = null; } - // 隐藏物品信息 - this.hideItemInfo(); + // 如果有选中物品,不要隐藏物品信息 + if (!this.selectedItem) { + // 隐藏物品信息 + this.hideItemInfo(); + } } /** @@ -505,6 +638,9 @@ export class InventoryUI { if (spriteObj) { spriteObj.border.setStrokeStyle(2, 0xffff00, 1); // 黄色边框表示选中 } + + // 显示选中物品的详情 + this.showItemInfo(item); console.log('选中物品:', item.name); } @@ -520,6 +656,8 @@ export class InventoryUI { spriteObj.border.setStrokeStyle(1, 0xffffff, 1); // 恢复白色边框 } + // 隐藏物品详情 + this.hideItemInfo(); console.log('取消选择物品:', this.selectedItem.name); this.selectedItem = null; } @@ -603,16 +741,21 @@ export class InventoryUI { } /** - * 添加物品精灵 + * 添加物品精灵(基于物品中心坐标) * @param {Object} item - 物品 */ addItemSprite(item) { - const { x, y } = item.position; + // 获取物品中心坐标和尺寸 + const { x: centerX, y: centerY } = item.position; const { width, height } = item.gridSize; + // 计算左上角坐标 + const topLeftX = Math.floor(centerX - (width - 1) / 2); + const topLeftY = Math.floor(centerY - (height - 1) / 2); + // 计算精灵位置和大小 - const spriteX = 10 + x * this.cellSize; - const spriteY = 10 + y * this.cellSize; + const spriteX = 10 + topLeftX * this.cellSize; + const spriteY = 10 + topLeftY * this.cellSize; const spriteWidth = width * this.cellSize; const spriteHeight = height * this.cellSize; @@ -636,20 +779,12 @@ export class InventoryUI { 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);