/** * Control 场景类 * * 允许用户控制角色,NPC 自动搜索并收集小草 */ import BreatheLife from './BreatheLife.js' import { generateUUID } from '../utils/String.js' export default class Control extends BreatheLife { constructor(sceneKey = 'Control') { // 场景相关 super(sceneKey) this.nextScene = 'InfoBar' } async preload() { super.preload() // 加载仙人掌图片 this.load.image('cactus', `${this.TilesPath}/tile_0063.png`) // 加载物品配置 await Item.loadItemsConfig(this) } create() { super.create() // 场景切换条件 this.initSceneTransition() // 输入事件 this.initInput() // 添加随机小草 this.addRandomSmallGrass() // 添加随机NPC this.addRandomNPCs() // 添加NPC之间的碰撞 this.physics.add.collider(this.NPCObjects, this.NPCObjects) } initSceneTransition() { // 调用父类的initSceneTransition方法,确保所有必要的数组都被初始化 super.initSceneTransition() // 初始化仙人掌集合 this.cactusObjects = [] // 维护仙人掌集合 // 初始化受控队伍 this.controlledTeam = [] // 维护受控队伍 // 初始化团队目标点 this.teamTargetPoint = null // 团队目标移动点 } initInput() { // 移除父类的输入事件监听器,避免重复监听 super.removeListener() // 监听鼠标点击事件,同时处理左右键 this.input.on('pointerdown', (pointer) => { const { x, y } = this.input.activePointer if (pointer.rightButtonDown()) { // 右键点击 this.handleRightClick(x, y) } else { // 左键点击 this.handleClick(x, y, pointer) } }) } update() { super.update() // 检查小草是否被收集完 if (this.smallGrassObjects.length === 0) { console.log("所有小草已被收集") // 切换到下一个场景 this.scene.start(this.nextScene) } // 更新所有NPC this.NPCObjects.forEach(npc => { if (npc.update) { npc.update(); } }); } /** * 处理点击事件 * @param {number} x - 点击的 x 坐标 * @param {number} y - 点击的 y 坐标 * @param {Phaser.Input.Pointer} pointer - 鼠标指针对象 */ handleClick(x, y, pointer) { // 获取点击位置的精灵 const clickedSprite = this.getClickedSprite(x, y) console.log("点击到的精灵:", clickedSprite) if (clickedSprite && this.NPCObjects.includes(clickedSprite)) { // 如果点击到了NPC // 左键点击NPC后加入受控队伍 if (!this.controlledTeam.includes(clickedSprite)) { this.controlledTeam.push(clickedSprite) clickedSprite.setState(NPCState.CONTROLLED) clickedSprite.setIndicatorColor(IndicatorColor.GREEN) console.log("NPC已加入受控队伍,当前队伍数量:", this.controlledTeam.length) } } else { // 点击空地 // 左键点击空地清空受控队伍,恢复待机 if (this.controlledTeam.length > 0) { this.controlledTeam.forEach(npc => { npc.setState(NPCState.IDLE) npc.setIndicatorColor(IndicatorColor.GRAY) }) this.controlledTeam = [] this.teamTargetPoint = null console.log("受控队伍已清空") } } } /** * 处理右键点击事件 * @param {number} x - 点击的 x 坐标 * @param {number} y - 点击的 y 坐标 */ handleRightClick(x, y) { // 右键点击空地为受控队伍设置目标移动点 if (this.controlledTeam.length > 0) { this.teamTargetPoint = { x, y } console.log("为受控队伍设置目标点:", x, y) } } /** * 重写随机小草添加方法 */ addRandomSmallGrass(count = 5) { // 随机生成小草 for (let i = 0; i < count; i++) { const randomX = Phaser.Math.Between(100, this.sys.game.config.width - 100) const randomY = Phaser.Math.Between(100, this.sys.game.config.height - 100) const smallGrass = new Item(this, randomX, randomY, 'smallGrass', 'smallGrass') this.smallGrassObjects.push(smallGrass) } } addRandomNPCs(count = 3) { // 随机生成NPC for (let i = 0; i < count; i++) { const randomX = Phaser.Math.Between(100, this.sys.game.config.width - 100) const randomY = Phaser.Math.Between(100, this.sys.game.config.height - 100) const npc = new NPC(this, randomX, randomY, 'player1-0', 'npc') npc.setState(NPCState.SEARCHING); // 初始化为搜索状态 this.NPCObjects.push(npc) } } } /** * 物品类 * 用于表示游戏中的可收集物品 * @param {Phaser.Scene} scene - 场景实例 * @param {number} x - 物品的 x 坐标 * @param {number} y - 物品的 y 坐标 * @param {string} texture - 物品的纹理键 */ export class Item extends Phaser.GameObjects.Sprite { // 静态属性,存储所有物品的配置数据 static itemsConfig = null // 静态方法,加载物品配置 static loadItemsConfig(scene) { return new Promise((resolve, reject) => { scene.load.json('itemsConfig', 'src/data/items.json') scene.load.once('complete', () => { Item.itemsConfig = scene.cache.json.get('itemsConfig') resolve() }) scene.load.start() }) } constructor(scene, x, y, texture, itemInfoName = null, frame = 0) { // 调用父类 Sprite 构造函数 super(scene, x, y, texture, frame) // 渲染属性 this.setOrigin(0.5, 0.5) // 设置渲染原点,中心 this.scene.add.existing(this) // 设置自定义额外属性 this.type = [] // 物品类型标签 this.weight = 0 // 物品重量 this.volume = 0 // 物品体积 this.hardness = 0 // 硬度 this.elasticity = 0 // 弹性 this.physicalState = 'solid' // 物理状态,solid 为固体,liquid 为液体,gas 为气体 this.name = '' // 物品名称 this.description = '' // 物品描述 // 是否被AI锁定,用于防止AI重复拾取 this.locked = false // 如果指定了物品类型,从配置中加载属性 if (itemInfoName && Item.itemsConfig) { this.loadFromConfig(itemInfoName) } } // 从配置中加载物品属性 loadFromConfig(itemInfoName) { if (Item.itemsConfig && Item.itemsConfig.items[itemInfoName]) { const config = Item.itemsConfig.items[itemInfoName] this.name = config.name || '' this.type = config.type || [] this.weight = config.weight || 0 this.volume = config.volume || 0 this.hardness = config.hardness || 0 this.elasticity = config.elasticity || 0 this.physicalState = config.physicalState || 'solid' this.description = config.description || '' } } setLocked(lockState) { // 设置物品是否被AI锁定 this.locked = lockState } } // 指示器颜色枚举 const IndicatorColor = { GRAY: 0x808080, GREEN: 0x00FF00, RED: 0xFF0000, BLUE: 0x0000FF, YELLOW: 0xFFFF00 }; /** * 单位类 * 用于表示游戏中的角色和敌人 * @param {Phaser.Scene} scene - 场景实例 * @param {number} x - 单位的 x 坐标 * @param {number} y - 单位的 y 坐标 * @param {string} texture - 单位的纹理键 * @param {string|number} frame - 单位的初始帧 */ export class Unit extends Item { constructor(scene, x, y, texture, frame) { // 调用父类 Sprite 构造函数 super(scene, x, y, texture, frame) this.direction = 1 // 渲染方向,1 为向右,-1 为向左 this.setOrigin(0.5, 1) // 设置渲染原点,中心底部 // 添加物品标签为单位 this.type.push('unit') scene.physics.add.existing(this) const radius = Math.min(this.width, this.height) / 2 this.body.setCircle(radius) // 设置单位为圆形碰撞体,半径为宽度或高度的一半 this.body.setBounce(0.2) this.body.setCollideWorldBounds(true) this.body.setMaxVelocity(200) this.body.setFriction(0.95, 0.95) this.body.setDrag(0.95, 0.95) this.uuid = generateUUID() // 单位唯一标识符 this.mass = 0 // 单位质量 this.velocity = new Phaser.Math.Vector2(0, 0) // 单位速度向量 this.acceleration = new Phaser.Math.Vector2(0, 0) // 单位加速度向量 this.force = 0 // 单位力量 this.status = ['alive'] // 单位状态 this.target = null // 搜索目标 this.targetPoint = null // 移动目标点 // 状态管理 this.state = null; // 状态 this.currentBehavior = null; // 当前行为组件 // 椭圆指示光圈 this.indicator = null this.indicatorColor = IndicatorColor.GRAY this.speed = 200 this.createIndicator() } createIndicator() { const graphics = this.scene.add.graphics() // 创建 Graphics 对象 this.indicator = graphics // 保存 Graphics 对象 this.scene.children.moveTo(this.indicator, this.scene.children.getIndex(this) - 1) // 将 Graphics 对象移动到单位下方 this.updateIndicator() // 更新指示器 } updateIndicator() { if (!this.indicator) return // 如果单位未添加到场景中,不更新指示器 this.indicator.clear() // 清除 Graphics 对象的绘制内容 const radius = this.width / 2 // 单位半径 const ellipseWidth = radius * 1 // 椭圆宽度 const ellipseHeight = radius * 0.5 // 椭圆高度 const posX = this.body ? this.body.x + this.width / 2 : this.x // 椭圆中心 x 坐标,取单位中心 x 坐标 const posY = this.body ? this.body.y + this.height : this.y // 椭圆中心 y 坐标,取单位底部 y 坐标 this.indicator.fillStyle(0x000000, 0.8) // 填充颜色为黑色,透明度为1 this.indicator.fillEllipse(posX, posY, ellipseWidth, ellipseHeight) // 填充椭圆 // 绘制椭圆边框 this.indicator.lineStyle(1, this.indicatorColor, 1) // 线宽为1,颜色为 indicatorColor,透明度为1 this.indicator.strokeEllipse(posX, posY, ellipseWidth + 2, ellipseHeight + 2) // 根据宽高绘制椭圆边框 } setIndicatorColor(color) { this.indicatorColor = color this.updateIndicator() } destroy(fromScene) { if (this.indicator) { this.indicator.destroy() } super.destroy(fromScene) } hasStatus(status) { // 单位状态检查 return this.status.includes(status) } addStatus(status) { // 单位状态添加 if (!this.hasStatus(status)) { this.status.push(status) } } removeStatus(status) { // 单位状态移除 if (this.hasStatus(status)) { this.status = this.status.filter(s => s !== status) } } // 设置状态 setState(state) { this.state = state; // 切换行为组件 this.switchBehavior(state) } // 子类实现切换行为组件,当前类不实现该方法 switchBehavior(state) { // 空实现 } // 更新方法 update() { if (this.currentBehavior) { this.currentBehavior.update(); } this.updateIndicator() } } // NPC状态枚举 const NPCState = { IDLE: 'idle', // 待机状态 SEARCHING: 'searching', // 搜索状态 COLLECTING: 'collecting', // 采集状态 CONTROLLED: 'controlled' // 受玩家控制状态 }; // NPC类 export class NPC extends Unit { constructor(scene, x, y, texture, frame) { super(scene, x, y, texture, frame) // 添加物品标签为NPC this.type.push('npc') this.setIndicatorColor(IndicatorColor.RED) } // 根据状态切换行为组件 switchBehavior(state) { this.body.setAcceleration(0, 0); this.body.setVelocity(0, 0); switch (state) { case NPCState.IDLE: this.currentBehavior = new IdleBehavior(this); break; case NPCState.SEARCHING: this.currentBehavior = new SearchBehavior(this); break; case NPCState.COLLECTING: this.currentBehavior = new CollectBehavior(this); break; case NPCState.CONTROLLED: this.currentBehavior = new ControlledBehavior(this); break; } } } // 待机行为组件 class IdleBehavior { constructor(npc) { this.npc = npc; // 关联的NPC实例,用于访问NPC的属性和方法 } update() { this.npc.body.setAcceleration(0, 0); this.npc.body.setVelocity(0, 0); this.npc.velocity.set(0, 0); // 重置速度 this.npc.acceleration.set(0, 0); // 重置加速度 this.npc.target = null; // 重置目标 this.npc.targetPoint = null; // 重置移动目标点 // 重置动画 this.npc.anims.stop(); // 停止当前动画 this.npc.setTexture(this.npc.texture.key); // 重置纹理为原始状态 } } // 搜索行为组件 class SearchBehavior { constructor(npc, targetType = 'smallGrass') { this.npc = npc; // 关联的NPC实例,用于访问NPC的属性和方法 this.targetType = targetType; // 目标类型,用于指定搜索的物品类型 } update() { // 如果没有目标,寻找新目标 if (!this.npc.target) { this.findTarget(); } else { // 向目标移动 this.moveToTarget(); } } findTarget() { // 简单实现:随机选择一个目标 const scene = this.npc.scene; switch (this.targetType) { case 'smallGrass': if (scene.smallGrassObjects.length > 0) { const randomIndex = Phaser.Math.Between(0, scene.smallGrassObjects.length - 1); this.npc.target = scene.smallGrassObjects[randomIndex]; }else{ // 如果没有小草,切换到待机状态 this.npc.setState(NPCState.IDLE); } break; case 'npc': if (scene.NPCObjects.length > 1) { // 确保有其他NPC // 随机选择一个除自己以外的NPC const randomIndex = Phaser.Math.Between(0, scene.NPCObjects.length - 1); this.npc.target = scene.NPCObjects[randomIndex]; // 确保选择的NPC不是自己 if (this.npc.target === this.npc) { this.findTarget(); // 如果是自己,重新选择 } } break; } } moveToTarget() { // 检查目标是否已被移除 const scene = this.npc.scene; let targetExists = false; switch (this.targetType) { case 'smallGrass': targetExists = scene.smallGrassObjects.includes(this.npc.target); break; } // 如果目标不存在,切换到搜索状态 if (!targetExists) { this.npc.target = null; this.npc.setState(NPCState.SEARCHING); return; } if (this.npc.target) { const direction = new Phaser.Math.Vector2( this.npc.target.x - this.npc.x, this.npc.target.y - this.npc.y ).normalize(); const currentVelocity = this.npc.body.velocity.clone(); const targetVelocity = direction.clone().scale(this.npc.speed); const blendedVelocity = currentVelocity.lerp(targetVelocity, 0.1); this.npc.body.setVelocity( blendedVelocity.x, blendedVelocity.y ); const distance = Phaser.Math.Distance.Between( this.npc.x, this.npc.y, this.npc.target.x, this.npc.target.y ); if (distance < 30) { this.npc.body.setAcceleration(0, 0); this.npc.body.setVelocity(0, 0); this.npc.setState(NPCState.COLLECTING); } } } } // 采集行为组件 class CollectBehavior { constructor(npc, targetType = 'smallGrass') { this.npc = npc; this.targetType = targetType; this.collectTime = 500; // 采集时间(毫秒) this.collectStartTime = 0; this.isCollecting = false; } update() { if (!this.npc.target) { // 如果没有目标,切换回搜索状态 this.npc.setState(NPCState.SEARCHING); return; } // 检查目标是否还存在 const scene = this.npc.scene; let targetExists = false; switch (this.targetType) { case 'smallGrass': targetExists = scene.smallGrassObjects.includes(this.npc.target); break; } if (!targetExists) { // 目标不存在,切换回搜索状态 this.npc.target = null; this.npc.setState(NPCState.SEARCHING); return; } // 开始采集 if (!this.isCollecting) { this.isCollecting = true; this.collectStartTime = Date.now(); } // 检查采集是否完成 if (Date.now() - this.collectStartTime >= this.collectTime) { // 采集完成,移除目标 this.removeTarget(); this.isCollecting = false; // 切换回搜索状态 this.npc.setState(NPCState.SEARCHING); } } removeTarget() { const scene = this.npc.scene; if (this.npc.target) { switch (this.targetType) { case 'smallGrass': const index = scene.smallGrassObjects.indexOf(this.npc.target); if (index > -1) { scene.smallGrassObjects.splice(index, 1); this.npc.target.destroy(); } break; } this.npc.target = null; } } } // 受控行为组件 class ControlledBehavior { constructor(npc) { this.npc = npc; } update() { const scene = this.npc.scene; if (scene.teamTargetPoint) { const distance = Phaser.Math.Distance.Between( this.npc.x, this.npc.y, scene.teamTargetPoint.x, scene.teamTargetPoint.y ); if (distance >= 4) { this.moveToTarget(scene.teamTargetPoint); } else { this.npc.body.setVelocity(0, 0); this.npc.x = scene.teamTargetPoint.x; this.npc.y = scene.teamTargetPoint.y; } } else { this.npc.body.setVelocity(0, 0); } } moveToTarget(targetPoint) { const direction = new Phaser.Math.Vector2( targetPoint.x - this.npc.x, targetPoint.y - this.npc.y ).normalize(); const currentVelocity = this.npc.body.velocity.clone(); const targetVelocity = direction.clone().scale(this.npc.speed); const blendedVelocity = currentVelocity.lerp(targetVelocity, 0.1); this.npc.body.setVelocity( blendedVelocity.x, blendedVelocity.y ); } }