diff --git a/data/items.json b/data/items.json new file mode 100644 index 0000000..a3dd5d8 --- /dev/null +++ b/data/items.json @@ -0,0 +1,114 @@ +{ + "items": [ + { + "id": "water_bottle", + "name": "瓶装水", + "description": "一瓶干净的饮用水,可以解渴。", + "type": "food", + "weight": 1.0, + "volume": 1.0, + "value": 5, + "sprite": { + "width": 16, + "height": 16, + "color": 255 + }, + "properties": { + "drinkable": true, + "nutrition": 0, + "hydration": 50 + } + }, + { + "id": "canned_food", + "name": "罐头食品", + "description": "一罐保质期很长的食品,可以提供营养。", + "type": "food", + "weight": 1.5, + "volume": 1.0, + "value": 10, + "sprite": { + "width": 16, + "height": 16, + "color": 16711680 + }, + "properties": { + "edible": true, + "nutrition": 100, + "hydration": 20 + } + }, + { + "id": "crowbar", + "name": "撬棍", + "description": "多功能工具,可以撬开门锁或作为武器。", + "type": "tool", + "weight": 3.0, + "volume": 2.0, + "value": 20, + "sprite": { + "width": 16, + "height": 16, + "color": 8421504 + }, + "properties": { + "tool_quality": 2, + "weapon_damage": 15 + } + }, + { + "id": "first_aid_kit", + "name": "急救包", + "description": "包含基本医疗用品,可以治疗伤口。", + "type": "medical", + "weight": 1.0, + "volume": 1.5, + "value": 30, + "sprite": { + "width": 16, + "height": 16, + "color": 16777215 + }, + "properties": { + "heal_amount": 50, + "usable": true + } + }, + { + "id": "flashlight", + "name": "手电筒", + "description": "可以在黑暗中提供照明。", + "type": "tool", + "weight": 0.5, + "volume": 0.5, + "value": 15, + "sprite": { + "width": 16, + "height": 16, + "color": 16776960 + }, + "properties": { + "light_range": 10, + "battery_life": 100 + } + }, + { + "id": "knife", + "name": "小刀", + "description": "一把锋利的小刀,可以用作工具或武器。", + "type": "weapon", + "weight": 0.8, + "volume": 1.0, + "value": 12, + "sprite": { + "width": 16, + "height": 16, + "color": 4194304 + }, + "properties": { + "weapon_damage": 10, + "tool_quality": 1 + } + } + ] +} \ No newline at end of file diff --git a/items/Item.js b/items/Item.js new file mode 100644 index 0000000..d5c8468 --- /dev/null +++ b/items/Item.js @@ -0,0 +1,204 @@ +// 物品类:表示游戏中的单个物品实体 + +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; + + // 创建一个简单的图形纹理作为物品精灵 + if (!scene.textures.exists(id)) { + const canvas = document.createElement('canvas'); + canvas.width = sprite.width; + canvas.height = sprite.height; + const ctx = canvas.getContext('2d'); + + // 设置物品颜色 + ctx.fillStyle = `#${sprite.color.toString(16).padStart(6, '0')}`; + ctx.fillRect(0, 0, sprite.width, sprite.height); + + // 添加物品边框 + ctx.strokeStyle = '#000000'; + ctx.lineWidth = 1; + ctx.strokeRect(0, 0, sprite.width, sprite.height); + + // 添加物品ID的第一个字符作为图标 + ctx.fillStyle = '#ffffff'; + ctx.font = `${sprite.width * 0.8}px Arial`; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(id.charAt(0).toUpperCase(), sprite.width / 2, sprite.height / 2); + + // 将canvas添加为纹理 + scene.textures.addCanvas(id, canvas); + } + + // 调用父类构造函数创建精灵 + super(scene, x, y, id); + + // 设置物品属性 + this.id = id; + this.name = name; + this.description = description; + this.type = type; + this.weight = weight; + this.volume = volume; + this.value = value; + this.properties = properties; + + // 添加到场景中 + scene.add.existing(this); + scene.physics.add.existing(this, true); // 静态物体,不响应物理碰撞 + + // 设置物品的碰撞体 + this.setSize(sprite.width, sprite.height); + + // 设置物品的交互性 + this.setInteractive({ + useHandCursor: true + }); + + // 绑定点击事件 + this.on('pointerdown', () => { + // 添加视觉反馈:缩放物品 + this.scene.tweens.add({ + targets: this, + scale: { from: 1, to: 1.2, duration: 100, yoyo: true, repeat: 1 } + }); + this.onItemClicked(); + }); + } + + /** + * 检查单位是否在交互距离内 + * @param {Object} unit - 要检查的单位 + * @returns {boolean} - 是否在交互距离内 + */ + isInInteractionRange(unit) { + const maxDistance = 50; // 最大交互距离 + const dx = this.x - unit.x; + const dy = this.y - unit.y; + const distance = Math.sqrt(dx * dx + dy * dy); + return distance <= maxDistance; + } + + /** + * 物品被点击时的处理函数 + */ + onItemClicked() { + // 触发物品点击事件 + this.scene.events.emit('item-clicked', this); + } + + /** + * 拾取物品 + * @param {Object} user - 拾取物品的单位 + */ + pickup(user) { + console.log(`${user.name} 拾取了 ${this.name}`); + // 这里可以添加物品到背包的逻辑 + // 暂时简单移除物品 + this.destroy(); + } + + /** + * 使用物品 + * @param {Object} user - 使用物品的单位 + */ + use(user) { + // 根据物品类型和属性执行不同的使用逻辑 + switch (this.type) { + case 'food': + this.useFood(user); + break; + case 'medical': + this.useMedical(user); + break; + case 'tool': + this.useTool(user); + break; + case 'weapon': + this.equipWeapon(user); + break; + default: + console.log(`无法使用物品:${this.name}`); + } + } + + /** + * 使用食物物品 + * @param {Object} user - 使用物品的单位 + */ + useFood(user) { + if (this.properties.drinkable) { + // 处理饮水逻辑 + console.log(`${user.name} 喝了 ${this.name}`); + if (user.hydration !== undefined) { + user.hydration = Math.min(100, user.hydration + this.properties.hydration); + } + } else if (this.properties.edible) { + // 处理进食逻辑 + console.log(`${user.name} 吃了 ${this.name}`); + if (user.nutrition !== undefined) { + user.nutrition = Math.min(100, user.nutrition + this.properties.nutrition); + } + if (user.hydration !== undefined) { + user.hydration = Math.min(100, user.hydration + (this.properties.hydration || 0)); + } + } + + // 使用后移除物品 + this.destroy(); + } + + /** + * 使用医疗物品 + * @param {Object} user - 使用物品的单位 + */ + useMedical(user) { + if (this.properties.heal_amount && user.health !== undefined) { + console.log(`${user.name} 使用了 ${this.name}`); + user.health = Math.min(100, user.health + this.properties.heal_amount); + // 使用后移除物品 + this.destroy(); + } + } + + /** + * 使用工具物品 + * @param {Object} user - 使用物品的单位 + */ + useTool(user) { + console.log(`${user.name} 使用了工具:${this.name}`); + // 工具使用逻辑可以根据具体工具类型扩展 + } + + /** + * 装备武器 + * @param {Object} user - 装备武器的单位 + */ + equipWeapon(user) { + console.log(`${user.name} 装备了武器:${this.name}`); + // 装备武器逻辑 + if (user.equipWeapon) { + user.equipWeapon(this); + } + // 装备后可以选择保留物品或移除,这里选择保留 + } + + /** + * 获取物品的详细信息 + * @returns {Object} 物品信息对象 + */ + getInfo() { + return { + id: this.id, + name: this.name, + description: this.description, + type: this.type, + weight: this.weight, + volume: this.volume, + value: this.value, + properties: this.properties + }; + } +} \ No newline at end of file diff --git a/items/ItemManager.js b/items/ItemManager.js new file mode 100644 index 0000000..11a4ca8 --- /dev/null +++ b/items/ItemManager.js @@ -0,0 +1,315 @@ +// 物品管理器:负责加载、管理和创建物品 + +export class ItemManager { + constructor(scene) { + this.scene = scene; + this.itemTemplates = new Map(); // 存储物品模板 + this.activeItems = new Set(); // 存储当前场景中的物品实例 + this.isDataLoaded = false; // 标记物品数据是否已加载 + this.loadCallbacks = []; // 存储数据加载完成后的回调函数 + + // 加载物品数据 + this.loadItemData(); + } + + /** + * 注册数据加载完成回调 + * @param {Function} callback - 回调函数 + */ + onDataLoaded(callback) { + if (this.isDataLoaded) { + callback(); + } else { + this.loadCallbacks.push(callback); + } + } + + /** + * 加载物品数据 + */ + async loadItemData() { + try { + // 从JSON文件加载物品数据 + const response = await fetch('./data/items.json'); + const data = await response.json(); + + // 存储物品模板 + data.items.forEach(itemData => { + this.itemTemplates.set(itemData.id, itemData); + }); + + console.log(`成功加载 ${this.itemTemplates.size} 种物品模板`); + + // 设置数据已加载标记 + this.isDataLoaded = true; + + // 调用所有数据加载完成回调 + this.loadCallbacks.forEach(callback => callback()); + this.loadCallbacks = []; + } catch (error) { + console.error('加载物品数据失败:', error); + // 加载失败时,使用默认物品数据 + this.loadDefaultItemData(); + } + } + + /** + * 加载默认物品数据(当JSON文件加载失败时使用) + */ + loadDefaultItemData() { + const defaultItems = [ + { + id: "water_bottle", + name: "瓶装水", + description: "一瓶干净的饮用水,可以解渴。", + type: "food", + weight: 1.0, + volume: 1.0, + value: 5, + sprite: { + width: 16, + height: 16, + color: 0x0000ff + }, + properties: { + drinkable: true, + nutrition: 0, + hydration: 50 + } + }, + { + id: "canned_food", + name: "罐头食品", + description: "一罐保质期很长的食品,可以提供营养。", + type: "food", + weight: 1.5, + volume: 1.0, + value: 10, + sprite: { + width: 16, + height: 16, + color: 0xff0000 + }, + properties: { + edible: true, + nutrition: 100, + hydration: 20 + } + }, + { + id: "crowbar", + name: "撬棍", + description: "多功能工具,可以撬开门锁或作为武器。", + type: "tool", + weight: 3.0, + volume: 2.0, + value: 20, + sprite: { + width: 16, + height: 16, + color: 0x808080 + }, + properties: { + tool_quality: 2, + weapon_damage: 15 + } + }, + { + id: "first_aid_kit", + name: "急救包", + description: "包含基本医疗用品,可以治疗伤口。", + type: "medical", + weight: 1.0, + volume: 1.5, + value: 30, + sprite: { + width: 16, + height: 16, + color: 0xffffff + }, + properties: { + heal_amount: 50, + usable: true + } + }, + { + id: "flashlight", + name: "手电筒", + description: "可以在黑暗中提供照明。", + type: "tool", + weight: 0.5, + volume: 0.5, + value: 15, + sprite: { + width: 16, + height: 16, + color: 0xffff00 + }, + properties: { + light_range: 10, + battery_life: 100 + } + }, + { + id: "knife", + name: "小刀", + description: "一把锋利的小刀,可以用作工具或武器。", + type: "weapon", + weight: 0.8, + volume: 1.0, + value: 12, + sprite: { + width: 16, + height: 16, + color: 0x404040 + }, + properties: { + weapon_damage: 10, + tool_quality: 1 + } + } + ]; + + defaultItems.forEach(itemData => { + this.itemTemplates.set(itemData.id, itemData); + }); + + console.log(`使用默认物品数据,加载了 ${this.itemTemplates.size} 种物品模板`); + + // 设置数据已加载标记 + this.isDataLoaded = true; + + // 调用所有数据加载完成回调 + this.loadCallbacks.forEach(callback => callback()); + this.loadCallbacks = []; + } + + /** + * 根据物品ID创建物品实例 + * @param {string} itemId - 物品ID + * @param {number} x - 物品位置X坐标 + * @param {number} y - 物品位置Y坐标 + * @returns {Item|null} - 创建的物品实例,或null如果物品ID不存在 + */ + createItem(itemId, x, y) { + const itemData = this.itemTemplates.get(itemId); + if (!itemData) { + console.error(`未知物品ID: ${itemId}`); + 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; + }); + } + + /** + * 在随机位置创建随机物品 + * @returns {Item|null} - 创建的物品实例,或null如果没有物品模板 + */ + createRandomItem() { + if (this.itemTemplates.size === 0) { + return null; + } + + // 随机选择一个物品模板 + const itemIds = Array.from(this.itemTemplates.keys()); + const randomItemId = itemIds[Math.floor(Math.random() * itemIds.length)]; + + // 随机选择一个位置 + const worldBounds = this.scene.physics.world.bounds; + const x = Phaser.Math.Between(worldBounds.x, worldBounds.right); + const y = Phaser.Math.Between(worldBounds.y, worldBounds.bottom); + + // 创建物品 + return this.createItem(randomItemId, x, y); + } + + /** + * 根据物品类型创建物品 + * @param {string} type - 物品类型 + * @param {number} x - 物品位置X坐标 + * @param {number} y - 物品位置Y坐标 + * @returns {Item|null} - 创建的物品实例,或null如果没有对应类型的物品 + */ + createItemByType(type, x, y) { + // 筛选出指定类型的物品 + const typeItems = Array.from(this.itemTemplates.values()).filter(item => item.type === type); + if (typeItems.length === 0) { + console.error(`没有找到类型为 ${type} 的物品`); + return null; + } + + // 随机选择一个该类型的物品 + 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; + }); + } + + /** + * 获取物品模板 + * @param {string} itemId - 物品ID + * @returns {Object|null} - 物品模板数据,或null如果物品ID不存在 + */ + getItemTemplate(itemId) { + return this.itemTemplates.get(itemId) || null; + } + + /** + * 获取所有物品模板 + * @returns {Map} - 所有物品模板的Map对象 + */ + getAllItemTemplates() { + return this.itemTemplates; + } + + /** + * 获取当前场景中的所有物品实例 + * @returns {Set} - 当前场景中的所有物品实例的Set对象 + */ + getActiveItems() { + return this.activeItems; + } + + /** + * 清理所有物品实例 + */ + clearAllItems() { + this.activeItems.forEach(item => { + item.destroy(); + }); + this.activeItems.clear(); + } + + /** + * 销毁物品管理器 + */ + destroy() { + this.clearAllItems(); + this.itemTemplates.clear(); + } +} \ No newline at end of file diff --git a/scenes/ItemTestScene.js b/scenes/ItemTestScene.js new file mode 100644 index 0000000..2ab898e --- /dev/null +++ b/scenes/ItemTestScene.js @@ -0,0 +1,494 @@ +// 物品系统测试场景 + +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'; + +export default class ItemTestScene extends Phaser.Scene { + constructor() { + super({ key: 'ItemTestScene' }); + } + + 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(); + + // 创建一个测试单位 + const unit = new Unit(this, 0, 0, 'player'); + // 为单位添加一些属性,用于测试物品效果 + unit.name = '测试单位'; + unit.health = 100; + unit.nutrition = 50; + unit.hydration = 50; + unit.equipWeapon = function(weapon) { + this.weapon = weapon; + console.log(`${this.name} 装备了 ${weapon.name}`); + }; + this.unitGroup.add(unit); + + // 自动选中单位 + this.events.emit('select-unit', unit); + + // 添加碰撞检测 + this.physics.add.collider(this.unitGroup, this.unitGroup); + + // 初始化物品管理器 + this.itemManager = new ItemManager(this); + + // 当物品数据加载完成后创建初始物品 + this.itemManager.onDataLoaded(() => { + this.createInitialItems(); + }); + + // 创建侧边栏UI(先创建,确保它在最底层) + this.createSidebar(); + + // 添加场景说明文本 + 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 spaceText = this.add.text(10, 220, '按空格键返回启动场景', { + fontSize: '16px', + fill: '#ffffff' + }); + spaceText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动 + spaceText.setDepth(10); // 设置深度,确保显示在侧边栏上方 + + // 显示单位状态 + this.updateUnitStatus(unit); + + // 显示默认提示 + this.itemInfoText.setText('未选择任何物品'); + this.interactionText.setText('点击物品查看详细信息,交互范围50像素'); + this.generalInfoText.setText('欢迎使用物品系统测试场景'); + + // 添加场景提示信息 + const interactionHintText = this.add.text(10, 250, '点击物品查看详细信息,交互范围50像素', { + fontSize: '16px', + fill: '#ffffff' + }); + interactionHintText.setScrollFactor(0, 0); // 固定在屏幕上,不随相机移动 + interactionHintText.setDepth(10); // 设置深度,确保显示在侧边栏上方 + + // 注册键盘事件 + this.input.keyboard.on('keydown-ONE', () => { + this.itemManager.createRandomItem(); + }); + + this.input.keyboard.on('keydown-TWO', () => { + // 在单位附近创建食物类物品 + const x = unit.x + Phaser.Math.Between(-100, 100); + const y = unit.y + Phaser.Math.Between(-100, 100); + this.itemManager.createItemByType('food', x, y); + }); + + this.input.keyboard.on('keydown-THREE', () => { + // 在单位附近创建工具类物品 + const x = unit.x + Phaser.Math.Between(-100, 100); + const y = unit.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.events.on('item-clicked', (item) => { + this.showItemInfo(item); + }); + + // 创建一些交互按钮 + this.createButtons(); + } + + /** + * 创建初始物品 + */ + createInitialItems() { + // 在不同位置创建各种类型的物品 + const itemTypes = ['food', 'tool', 'medical', 'weapon']; + const unit = this.unitGroup.getFirstAlive(); + + for (let i = 0; i < itemTypes.length; i++) { + const x = unit.x + (i - 2) * 200; + const y = unit.y - 200; + this.itemManager.createItemByType(itemTypes[i], x, y); + } + } + + /** + * 创建交互按钮 + */ + 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.unitGroup.getFirstAlive(); + 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 += `距离: ${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); + 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('距离太远,无法交互'); + } + + // 移除5秒后自动清除的逻辑,让信息保持显示 + } + + /** + * 创建侧边栏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); + } + + 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