/** * BreatheLife 场景类 * * 交互场景,允许用户通过点击屏幕创建角色 * 第二次点击设置纹理 * 第三次点击赋予动画 * 第四次点击移动角色 * 当移动角色超过3个后再次点击,切换到下一个场景 */ import WorldLand from './WorldLand.js' export default class BreatheLife extends WorldLand { constructor(sceneKey = 'BreatheLife') { // 场景相关 super(sceneKey) this.nextScene = 'Control' // 层级相关 this.SpriteDepth = 10 // 角色层级 } preload() { super.preload() // 加载角色动画和图片资源 this.load.image(`player1-0`, `assets/PNG/Players/Tiles/tile_${String(8).padStart(4, '0')}.png`) // 批量加载图片用于动画播放 for (let i = 1; i < 8; i++) { this.load.image(`player0-${i}`, `assets/PNG/Players/Tiles/tile_${String(i).padStart(4, '0')}.png`); } for (let i = 9; i < 16; i++) { this.load.image(`player1-${i-8}`, `assets/PNG/Players/Tiles/tile_${String(i).padStart(4, '0')}.png`); } // 加载音效资源 this.load.audio('coin-a', 'assets/Sounds/move-a.ogg'); this.load.audio('coin-b', 'assets/Sounds/coin-b.ogg'); } create() { super.create() // 覆盖场景切换条件 this.initSceneTransition() // 覆盖输入 this.initInput() // 添加随机小草 this.addRandomSmallGrass() } initSceneTransition() { // 初始化角色计数器,用于跟踪创建的角色数量 this.targetCount = 3 // 目标角色数量 this.NPCObjects = [] // 维护角色集合 this.smallGrassObjects = [] // 维护小草集合 } initInput() { // 移除父类的输入事件监听器,避免重复监听 super.removeListener() // 监听鼠标点击事件,点击后创建一个新的角色 this.input.on('pointerdown', () => { // 获取点击位置的坐标 const { x, y } = this.input.activePointer this.handleClick(x, y) }) // 监听自定义收集完成事件,当所有小草都被收集时触发 this.events.on('collectAllSmallGrass', () => { this.scene.start(this.nextScene) }) } removeListener() { // 移除鼠标点击事件监听器 this.input.off('pointerdown'); // 移除自定义收集完成事件监听器 this.events.off('collectAllSmallGrass') } /** * 处理点击事件 * @param {number} x - 点击的 x 坐标 * @param {number} y - 点击的 y 坐标 */ handleClick(x, y) { // 获取点击位置的精灵 const clickedSprite = this.getClickedSprite(x, y) console.log("点击到的精灵:", clickedSprite) // 如果满足条件则切换到下一场景 if (clickedSprite && this.NPCObjects.includes(clickedSprite)) { // 如果点击到了角色 this.handleSpriteClick(clickedSprite) }else if (this.NPCObjects.length >= this.targetCount) { // 如果角色列表中角色数量超过目标数量,且动态效果的数量也超过目标数量 let moveCount = 0 for (const sprite of this.NPCObjects) { // 如果角色当前的纹理在player-move的动画帧中,计数加一 const moveAnimationFrames = ['player1-1', 'player1-2'] if (moveAnimationFrames.includes(sprite.texture.key)) { moveCount++ } } if (moveCount >= this.targetCount) { this.scene.start(this.nextScene) } }else if (this.NPCObjects.length < this.targetCount) { // 如果角色数量不足目标数量 // 创建一个新的角色 sprite,缩放为1倍,并设置原点为中心底部 const sprite = this.physics.add.sprite(x, y, 'player').setScale(1).setOrigin(0.5, 1) sprite.setDepth(this.SpriteDepth) // 设置角色层级 this.NPCObjects.push(sprite) // 加入角色集合 // // 设置角色碰撞世界边界 // sprite.setCollideWorldBounds(true) } } /** * 获取鼠标点击位置的精灵 * @param {number} x - 点击位置的 x 坐标 * @param {number} y - 点击位置的 y 坐标 * @returns {Phaser.GameObjects.Sprite|null} - 点击位置的精灵,如果没有则返回 null */ getClickedSprite(x, y) { // 获取场景中所有的精灵 const sprites = this.children.getChildren().filter(child => child instanceof Phaser.GameObjects.Sprite) // 检查每个精灵是否被点击 for (const sprite of sprites) { // 检查点击位置是否在精灵的边界内 if (sprite.getBounds().contains(x, y)) { return sprite } } return null } handleSpriteClick(clickedSprite) { console.log("当前精灵的纹理:", clickedSprite.texture.key) // 查看当前精灵的纹理 // 如果纹理为默认值或纹理缺失 if (clickedSprite.texture.key === 'player' || clickedSprite.texture.key === '__MISSING') { clickedSprite.setTexture(`player1-0`).setScale(1) // 设置指定的纹理 this.spriteTextureCount++ }else if (clickedSprite.texture.key === 'player1-0'){ // 如果点击的角色纹理已经是player1-0,则添加动画 this.addSpriteAnimation(clickedSprite) // 添加角色动画 }else if (['player1-1', 'player1-2'].includes(clickedSprite.texture.key)){ // 如果点击的角色纹理已经是player0-0,则添加音效 this.sound.play('coin-b') // 播放移动音效 // 添加搜索逻辑,自动检测最近的小草 this.searchAndAddSmallGrass(clickedSprite) } } /** * 为角色添加动画 * @param {Phaser.GameObjects.Sprite} sprite - 要添加动画的角色精灵 */ addSpriteAnimation(sprite) { // 检查角色是否已添加动画 if (sprite.anims.exists('player-move')) { return } // 创建移动动画 this.anims.create({ key: 'player-move', frames: [ { key: 'player1-1' }, { key: 'player1-2' } ], frameRate: 10, repeat: -1 }); // 播放角色动画 sprite.anims.play('player-move') this.spriteAnimCount++ } // 重写创建小草方法,移除条件判断和计数器 createSmallGrass(x, y) { // 创建 const smallGrass = this.add.image(x, y, 'smallGrass'); smallGrass.setDepth(this.mapDepth); // 设置深度,确保在UI层级之下 smallGrass.setScrollFactor(0); // 固定,不随相机移动 return smallGrass } // 新增方法:随机添加50个小草 addRandomSmallGrass() { for (let i = 0; i < 50; i++) { // 屏幕范围内随机生成小草位置 const x = Phaser.Math.Between(0, this.cameras.main.width) const y = Phaser.Math.Between(0, this.cameras.main.height) const smallGrass = this.createSmallGrass(x, y) // 保存小草对象到数组 this.smallGrassObjects.push(smallGrass) } } /** * 搜索并添加小草 * @param {Phaser.GameObjects.Sprite} clickedSprite - 点击的角色精灵 */ searchAndAddSmallGrass(clickedSprite) { // 搜索所有小草 // const smallGrass = this.children.getChildren().filter(child => // child instanceof Phaser.GameObjects.Image && child.texture.key === 'smallGrass' // ) const smallGrass = this.smallGrassObjects // 过滤未标记的小草 const unmarkedGrass = smallGrass.filter(grass => grass.alpha === 1) // 如果没有小草,直接返回 if (smallGrass.length === 0) { // 所有小草都已收集,触发事件 this.events.emit('collectAllSmallGrass') return } // 查找最近的小草 let nearestGrass = null // 最近的小草 let minDistance = Infinity // 最近距离,初始为无穷大 // 遍历所有小草,找到最近的一个 for (const grass of unmarkedGrass) { // 计算当前小草与角色的距离 const distance = Phaser.Math.Distance.Between( clickedSprite.x, clickedSprite.y, grass.x, grass.y ) // 如果当前小草距离更近,更新最近的小草 if (distance < minDistance) { minDistance = distance nearestGrass = grass } } // 如果找到最近的小草,移动角色到小草位置 if (nearestGrass) { // 标记小草为已收集 nearestGrass.setAlpha(0.5) // 角色移动到小草位置 this.tweens.add({ targets: clickedSprite, // 角色精灵 x: nearestGrass.x, // 目标小草位置x y: nearestGrass.y, // 目标小草位置y duration: minDistance * 10, // 移动时间根据距离计算 ease: 'Linear', // 匀速移动 onComplete: () => { // 从数组中移除已收集的小草 this.smallGrassObjects = this.smallGrassObjects.filter( obj => obj !== nearestGrass ) nearestGrass.destroy() // 移动完成后销毁小草 this.sound.play('coin-a') // 播放收集小草音效 // 等待随机时间后,继续搜索下一个小草 const delay = Phaser.Math.Between(500, 1000) this.time.delayedCall(delay, () => { this.searchAndAddSmallGrass(clickedSprite) // 递归调用,继续搜索下一个小草 }) } }) } } }