diff --git a/data/items.json b/data/items.json index a3dd5d8..fe0f21d 100644 --- a/data/items.json +++ b/data/items.json @@ -8,9 +8,13 @@ "weight": 1.0, "volume": 1.0, "value": 5, + "gridSize": { + "width": 1, + "height": 2 + }, "sprite": { "width": 16, - "height": 16, + "height": 32, "color": 255 }, "properties": { @@ -27,6 +31,10 @@ "weight": 1.5, "volume": 1.0, "value": 10, + "gridSize": { + "width": 1, + "height": 1 + }, "sprite": { "width": 16, "height": 16, @@ -46,9 +54,13 @@ "weight": 3.0, "volume": 2.0, "value": 20, + "gridSize": { + "width": 1, + "height": 3 + }, "sprite": { "width": 16, - "height": 16, + "height": 48, "color": 8421504 }, "properties": { @@ -64,9 +76,13 @@ "weight": 1.0, "volume": 1.5, "value": 30, + "gridSize": { + "width": 2, + "height": 2 + }, "sprite": { - "width": 16, - "height": 16, + "width": 32, + "height": 32, "color": 16777215 }, "properties": { @@ -82,9 +98,13 @@ "weight": 0.5, "volume": 0.5, "value": 15, + "gridSize": { + "width": 1, + "height": 2 + }, "sprite": { "width": 16, - "height": 16, + "height": 32, "color": 16776960 }, "properties": { @@ -100,9 +120,13 @@ "weight": 0.8, "volume": 1.0, "value": 12, + "gridSize": { + "width": 1, + "height": 2 + }, "sprite": { "width": 16, - "height": 16, + "height": 32, "color": 4194304 }, "properties": { diff --git a/items/Inventory.js b/items/Inventory.js new file mode 100644 index 0000000..5333098 --- /dev/null +++ b/items/Inventory.js @@ -0,0 +1,389 @@ +// 背包系统:管理拾取的物品,支持网格类型的背包系统 + +export class Inventory { + constructor(width = 6, height = 4) { + this.width = width; // 背包宽度(网格数) + this.height = height; // 背包高度(网格数) + this.grid = this.createGrid(); // 创建背包网格 + this.items = new Map(); // 存储背包中的物品 + this.totalWeight = 0; // 总重量 + this.totalVolume = 0; // 总体积 + } + + /** + * 创建背包网格 + * @returns {Array} 二维数组表示的网格 + */ + createGrid() { + const grid = []; + for (let y = 0; y < this.height; y++) { + grid[y] = []; + for (let x = 0; x < this.width; x++) { + grid[y][x] = null; // null表示空格子 + } + } + return grid; + } + + /** + * 检查物品是否可以放置在指定位置 + * @param {Object} item - 要放置的物品 + * @param {number} x - 起始X坐标 + * @param {number} y - 起始Y坐标 + * @returns {boolean} 是否可以放置 + */ + canPlaceItem(item, x, y) { + const { gridSize } = item; + + // 检查物品尺寸是否在背包范围内 + if (x < 0 || y < 0 || x + gridSize.width > this.width || y + 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 cellItemId = this.grid[gridY][gridX]; + + // 如果格子不是空的,且不是物品自身,则不能放置 + if (cellItemId !== null && cellItemId !== item.id) { + return false; + } + } + } + + return true; + } + + /** + * 放置物品到背包中 + * @param {Object} item - 要放置的物品 + * @param {number} x - 起始X坐标 + * @param {number} y - 起始Y坐标 + * @returns {boolean} 是否放置成功 + */ + placeItem(item, x, y) { + if (!this.canPlaceItem(item, x, y)) { + return false; + } + + const { gridSize } = item; + + // 记录物品在背包中的位置信息 - 同时设置position和positionInInventory确保一致性 + item.position = { x, y }; + item.positionInInventory = { x, y }; + 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.items.set(item.id, item); + this.totalWeight += item.weight; + this.totalVolume += item.volume; + + return true; + } + + /** + * 查找物品在背包中的位置 + * @param {string} itemId - 物品ID + * @returns {Object|null} 物品位置信息 + */ + findItemPosition(itemId) { + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + if (this.grid[y][x] === itemId) { + return { x, y }; + } + } + } + return null; + } + + /** + * 从背包中移除物品 + * @param {string} itemId - 物品ID + * @returns {Object|null} 移除的物品 + */ + removeItem(itemId) { + const item = this.items.get(itemId); + if (!item) { + return null; + } + + const { position, gridSize } = item; + + // 清空网格 + 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; + } + } + + // 更新物品信息和统计数据 - 同时清空position和positionInInventory + item.position = null; + item.positionInInventory = null; + item.inventory = null; + item.inInventory = false; + this.items.delete(itemId); + this.totalWeight -= item.weight; + this.totalVolume -= item.volume; + + return item; + } + + /** + * 移动背包中的物品 + * @param {string} itemId - 物品ID + * @param {number} newX - 新的X坐标 + * @param {number} newY - 新的Y坐标 + * @returns {boolean} 是否移动成功 + */ + moveItem(itemId, newX, newY) { + console.log('moveItem called:', itemId, 'to position:', { newX, newY }); + const item = this.items.get(itemId); + if (!item) { + console.log('moveItem failed: 物品不存在'); + return false; + } + + console.log('移动物品:', item.name, '当前位置:', item.position, '目标位置:', { newX, newY }); + + // 检查新位置是否可以放置 + if (!this.canPlaceItem(item, newX, newY)) { + console.log('moveItem failed: 新位置无法放置物品'); + return false; + } + + // 保存旧位置信息 + const oldX = item.position.x; + const oldY = item.position.y; + const { gridSize } = item; + + console.log('清空旧位置网格:', { oldX, oldY }, '尺寸:', gridSize); + // 清空旧位置的网格 + for (let dy = 0; dy < gridSize.height; dy++) { + for (let dx = 0; dx < gridSize.width; dx++) { + this.grid[oldY + dy][oldX + dx] = null; + } + } + + // 更新物品位置 + item.position = { x: newX, y: newY }; + item.positionInInventory = { x: newX, y: newY }; + console.log('更新物品位置:', item.position); + + // 更新新位置的网格 + for (let dy = 0; dy < gridSize.height; dy++) { + for (let dx = 0; dx < gridSize.width; dx++) { + this.grid[newY + dy][newX + dx] = item.id; + } + } + + console.log('moveItem success:', item.name, '已移动到', { newX, newY }); + return true; + } + + /** + * 交换两个物品的位置 + * @param {string} itemId1 - 第一个物品ID + * @param {string} itemId2 - 第二个物品ID + * @returns {boolean} 是否交换成功 + */ + swapItems(itemId1, itemId2) { + console.log('swapItems called:', itemId1, '与', itemId2); + const item1 = this.items.get(itemId1); + const item2 = this.items.get(itemId2); + + if (!item1 || !item2) { + console.log('swapItems failed: 物品不存在'); + return false; + } + + console.log('交换物品:', item1.name, '与', item2.name); + console.log('物品1位置:', item1.position, '物品2位置:', item2.position); + + const pos1 = { ...item1.position }; + const pos2 = { ...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('swapItems failed: 物品1无法放置到物品2的位置,回滚'); + this.placeItem(item1, pos1.x, pos1.y); + this.placeItem(item2, pos2.x, pos2.y); + return false; + } + + console.log('尝试将物品2放置到物品1的位置:', pos1); + if (!this.placeItem(item2, pos1.x, pos1.y)) { + // 回滚:将第一个物品放回原位置,第二个物品也放回原位置 + console.log('swapItems failed: 物品2无法放置到物品1的位置,回滚'); + this.removeItem(itemId1); + this.placeItem(item1, pos1.x, pos1.y); + this.placeItem(item2, pos2.x, pos2.y); + return false; + } + + console.log('swapItems success: 物品交换成功'); + return true; + } + + /** + * 查找第一个可用的空位 + * @param {Object} item - 要放置的物品 + * @returns {Object|null} 可用位置 + */ + findEmptySpot(item) { + const { gridSize } = item; + + // 确保物品尺寸不会超过背包尺寸 + if (gridSize.width > this.width || gridSize.height > this.height) { + console.log(`物品尺寸(${gridSize.width}x${gridSize.height})超过背包尺寸(${this.width}x${this.height})`); + 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 }; + } + } + } + + console.log("未找到可用空位"); + return null; + } + + /** + * 添加物品到背包(自动寻找空位) + * @param {Object} item - 要添加的物品 + * @returns {boolean} 是否添加成功 + */ + addItem(item) { + const spot = this.findEmptySpot(item); + if (!spot) { + return false; + } + + return this.placeItem(item, spot.x, spot.y); + } + + /** + * 获取背包中指定位置的物品 + * @param {number} x - X坐标 + * @param {number} y - Y坐标 + * @returns {Object|null} 物品 + */ + getItemAt(x, y) { + if (x < 0 || y < 0 || x >= this.width || y >= this.height) { + return null; + } + + const itemId = this.grid[y][x]; + return itemId ? this.items.get(itemId) : null; + } + + /** + * 获取背包中的所有物品 + * @returns {Map} 物品映射 + */ + getAllItems() { + return this.items; + } + + /** + * 获取背包的统计信息 + * @returns {Object} 统计信息 + */ + getStats() { + return { + width: this.width, + height: this.height, + totalItems: this.items.size, + totalWeight: this.totalWeight, + totalVolume: this.totalVolume, + emptySlots: this.calculateEmptySlots() + }; + } + + /** + * 计算空位数 + * @returns {number} 空位数 + */ + calculateEmptySlots() { + let count = 0; + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + if (this.grid[y][x] === null) { + count++; + } + } + } + return count; + } + + /** + * 清空背包 + */ + clear() { + this.grid = this.createGrid(); + this.items.clear(); + this.totalWeight = 0; + this.totalVolume = 0; + } + + /** + * 导出背包数据 + * @returns {Object} 背包数据 + */ + exportData() { + const items = []; + this.items.forEach(item => { + items.push({ + ...item.getInfo(), + position: item.position + }); + }); + + return { + width: this.width, + height: this.height, + items: items + }; + } + + /** + * 导入背包数据 + * @param {Object} data - 背包数据 + */ + importData(data) { + this.clear(); + this.width = data.width; + this.height = data.height; + this.grid = this.createGrid(); + + data.items.forEach(itemData => { + // 这里需要重新创建物品实例 + // 假设Item类有一个静态方法createFromData + const item = Item.createFromData(itemData); + if (item) { + this.placeItem(item, itemData.position.x, itemData.position.y); + } + }); + } +} \ No newline at end of file diff --git a/items/Item.js b/items/Item.js index d5c8468..86884df 100644 --- a/items/Item.js +++ b/items/Item.js @@ -3,7 +3,7 @@ export default class Item extends Phaser.GameObjects.Sprite { constructor(scene, x, y, itemData) { // 从itemData中提取精灵属性 - const { id, name, description, type, weight, volume, value, sprite, properties } = itemData; + const { id, name, description, type, weight, volume, value, gridSize, sprite, properties } = itemData; // 创建一个简单的图形纹理作为物品精灵 if (!scene.textures.exists(id)) { @@ -36,15 +36,22 @@ export default class Item extends Phaser.GameObjects.Sprite { super(scene, x, y, id); // 设置物品属性 - this.id = id; + this.itemTypeId = id; // 物品类型ID,用于识别物品类型 + this.id = `${id}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 生成唯一ID this.name = name; this.description = description; this.type = type; this.weight = weight; this.volume = volume; this.value = value; + this.gridSize = gridSize || { width: 1, height: 1 }; // 默认1x1网格 this.properties = properties; + // 背包相关属性 + this.position = null; // 在背包中的位置 + this.positionInInventory = null; // 在背包中的位置(保持向后兼容) + this.inInventory = false; + // 添加到场景中 scene.add.existing(this); scene.physics.add.existing(this, true); // 静态物体,不响应物理碰撞 @@ -95,9 +102,47 @@ export default class Item extends Phaser.GameObjects.Sprite { */ pickup(user) { console.log(`${user.name} 拾取了 ${this.name}`); - // 这里可以添加物品到背包的逻辑 - // 暂时简单移除物品 - this.destroy(); + + // 检查用户是否有背包 + if (user.inventory) { + // 尝试将物品添加到背包 + const success = user.inventory.addItem(this); + if (success) { + this.inInventory = true; + this.visible = false; // 在场景中隐藏物品 + this.scene.events.emit('item-picked-up', this, user); + } else { + console.log('背包空间不足'); + } + } else { + // 如果没有背包系统,简单移除物品 + this.destroy(); + } + } + + /** + * 从背包中使用物品 + * @param {Object} user - 使用物品的单位 + */ + useFromInventory(user) { + // 使用物品 + this.use(user); + + // 从背包中移除物品 + if (user.inventory) { + user.inventory.removeItem(this.id); + } + } + + /** + * 创建物品实例(静态方法) + * @param {Object} itemData - 物品数据 + * @returns {Item} 物品实例 + */ + static createFromData(itemData) { + // 这个方法需要在具体场景中实现 + // 因为需要场景上下文来创建精灵 + return null; } /** @@ -198,6 +243,7 @@ export default class Item extends Phaser.GameObjects.Sprite { weight: this.weight, volume: this.volume, value: this.value, + gridSize: this.gridSize, properties: this.properties }; } diff --git a/items/ItemManager.js b/items/ItemManager.js index 11a4ca8..38027d0 100644 --- a/items/ItemManager.js +++ b/items/ItemManager.js @@ -1,5 +1,8 @@ // 物品管理器:负责加载、管理和创建物品 +// 静态导入Item类 +import Item from './Item.js'; + export class ItemManager { constructor(scene) { this.scene = scene; @@ -198,20 +201,18 @@ export class ItemManager { return null; } - // 动态导入Item类 - import('./Item.js').then(({ default: Item }) => { - const item = new Item(this.scene, x, y, itemData); - - // 将物品添加到活动物品集合中 - this.activeItems.add(item); - - // 监听物品销毁事件,从集合中移除 - item.on('destroy', () => { - this.activeItems.delete(item); - }); - - return item; + // 使用静态导入的Item类创建物品 + const item = new Item(this.scene, x, y, itemData); + + // 将物品添加到活动物品集合中 + this.activeItems.add(item); + + // 监听物品销毁事件,从集合中移除 + item.on('destroy', () => { + this.activeItems.delete(item); }); + + return item; } /** @@ -254,20 +255,18 @@ export class ItemManager { // 随机选择一个该类型的物品 const randomItemData = typeItems[Math.floor(Math.random() * typeItems.length)]; - // 动态导入Item类 - import('./Item.js').then(({ default: Item }) => { - const item = new Item(this.scene, x, y, randomItemData); - - // 将物品添加到活动物品集合中 - this.activeItems.add(item); - - // 监听物品销毁事件,从集合中移除 - item.on('destroy', () => { - this.activeItems.delete(item); - }); - - return item; + // 使用静态导入的Item类创建物品 + const item = new Item(this.scene, x, y, randomItemData); + + // 将物品添加到活动物品集合中 + this.activeItems.add(item); + + // 监听物品销毁事件,从集合中移除 + item.on('destroy', () => { + this.activeItems.delete(item); }); + + return item; } /** diff --git a/main.js b/main.js index 024570d..7679219 100644 --- a/main.js +++ b/main.js @@ -14,6 +14,8 @@ import MovementTestScene5 from './scenes/MovementTestScene5.js'; import MovementTestScene6 from './scenes/MovementTestScene6.js'; // 导入物品系统测试场景 import ItemTestScene from './scenes/ItemTestScene.js'; +// 导入背包系统测试场景 +import InventoryTestScene from './scenes/InventoryTestScene.js'; // 创建游戏的最终配置对象 const config = { @@ -30,7 +32,8 @@ const config = { MovementTestScene4, MovementTestScene5, MovementTestScene6, - ItemTestScene + ItemTestScene, + InventoryTestScene ] }; @@ -39,10 +42,10 @@ const config = { window.game = new Phaser.Game(config); // 添加一个函数,用于从控制台切换到测试场景 -window.startMovementTest = function(sceneIndex = 1) { - const sceneKey = `MovementTestScene${sceneIndex}`; - if (window.game.scene.getIndex(sceneKey) !== -1) { - window.game.scene.start(sceneKey); +window.startTest = function(sceneIndex = 1) { + const scenes = window.game.scene.scenes; + if (sceneIndex >= 0 && sceneIndex < scenes.length) { + window.game.scene.start(scenes[sceneIndex]); console.log(`已切换到测试场景${sceneIndex}`); } else { console.error(`测试场景${sceneIndex}不存在`); diff --git a/scenes/BootScene.js b/scenes/BootScene.js index face159..2730a5a 100644 --- a/scenes/BootScene.js +++ b/scenes/BootScene.js @@ -19,12 +19,13 @@ export default class BootScene extends Phaser.Scene { // 测试场景列表 const testScenes = [ + { 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: 'MovementTestScene6', name: '测试场景6:小地图测试' }, { key: 'ItemTestScene', name: '测试场景7:物品系统测试' }, { key: 'GameScene', name: '返回主游戏场景' } ]; diff --git a/scenes/InventoryTestScene.js b/scenes/InventoryTestScene.js new file mode 100644 index 0000000..0d52d6c --- /dev/null +++ b/scenes/InventoryTestScene.js @@ -0,0 +1,631 @@ +// 背包系统测试场景 + +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'; + +export default class InventoryTestScene extends Phaser.Scene { + constructor() { + super({ key: 'InventoryTestScene' }); + } + + 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.cameras.main.centerOn(0, 0); + + // 初始化系统 + 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(); + + // 创建测试物品 + this.createTestItems(); + + // 设置输入处理 + this.setupInput(); + + // 创建侧边栏UI + this.createSidebar(); + + // 显示提示信息 + this.showInstructions(); + + // 显示单位状态 + this.updateUnitStatus(this.player); + + // 显示默认提示 + this.itemInfoText.setText('未选择任何物品'); + this.interactionText.setText('点击物品查看详细信息,交互范围50像素'); + 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); + } + + /** + * 创建测试物品 + */ + createTestItems() { + // 等待物品数据加载完成 + this.itemManager.onDataLoaded(() => { + // 创建初始物品 + this.createInitialItems(); + + // 绑定物品点击事件 + this.events.on('item-clicked', (item) => { + this.showItemInfo(item); + }); + }); + } + + /** + * 创建初始物品 + */ + createInitialItems() { + // 在不同位置创建各种类型的物品 + const itemTypes = ['food', 'tool', 'medical', 'weapon']; + + for (let i = 0; i < itemTypes.length; i++) { + const x = this.player.x + (i - 2) * 200; + const y = this.player.y - 200; + this.itemManager.createItemByType(itemTypes[i], x, y); + } + + // 在场景中随机位置创建更多物品 + for (let i = 0; i < 6; 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(); + + // 背包快捷键(I键) + this.input.keyboard.on('keydown-I', () => { + this.inventoryUI.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('所有物品已清理'); + }); + + 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('所有物品已清理'); + }); + } + + /** + * 创建按钮 + * @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 + this.inventoryUI.updateUI(); + this.interactionText.setText(''); + this.itemInfoText.setText('未选择任何物品'); + }); + // 注册Q键快捷使用物品 + this.input.keyboard.once('keydown-Q', () => { + item.use(unit); + 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, '点击物品查看信息,右键移动到物品位置', { + 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 keyIText = this.add.text(10, 220, '按I键:打开/关闭背包', { + fontSize: '16px', + fill: '#ffffff' + }); + keyIText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动 + keyIText.setDepth(10); // 设置深度,确保显示在侧边栏上方 + + const spaceText = this.add.text(10, 250, '按空格键返回启动场景', { + fontSize: '16px', + fill: '#ffffff' + }); + spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动 + spaceText.setDepth(10); // 设置深度,确保显示在侧边栏上方 + + // 显示背包操作说明 + const inventoryText = this.add.text(10, 280, '背包操作:点击物品选择,再次点击取消选择,点击空位移动物品', { + fontSize: '14px', + fill: '#ffff00' + }); + inventoryText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动 + inventoryText.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(); + + // 更新所有单位 + this.unitGroup.getChildren().forEach(unit => { + if (unit.update) { + unit.update(); + } + unit.updateMovement(); + // 更新单位状态显示 + this.updateUnitStatus(unit); + }); + } +} \ No newline at end of file diff --git a/ui/InventoryUI.js b/ui/InventoryUI.js new file mode 100644 index 0000000..9584d3f --- /dev/null +++ b/ui/InventoryUI.js @@ -0,0 +1,721 @@ +// 背包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(); + } +} \ No newline at end of file