import LanguageManager from '../lang/LanguageManager.js'; import translations from '../lang/translations.js'; export default class MenuScene extends Phaser.Scene { constructor() { super('MenuScene') } create() { // 设置背景颜色 this.cameras.main.setBackgroundColor('#333333') // 获取主相机对象 const cam = this.cameras.main; // 获取屏幕尺寸,用于计算相对单位 this.screenWidth = this.sys.game.config.width; this.screenHeight = this.sys.game.config.height; // 计算相对单位:以屏幕高度为基准 this.unit = this.screenHeight / 100; // 初始化雨天效果状态 this.isRainEnabled = true; // 初始化setTimeout ID数组,用于管理所有与涟漪相关的延迟调用 this.rippleTimeouts = []; // 添加落雨落效果 this.createRainEffect(); // 初始化语言管理器 - 支持多语言 LanguageManager.loadTranslations(translations); // 添加菜单文本 - 使用多语言 this.titleText = this.add.text( cam.centerX, // 文本在x轴上的位置,相机中心 cam.centerY - this.unit * 8, // 文本在y轴上的位置,相机中心上方 LanguageManager.t('title'), { fontSize: this.unit * 2.4, color: '#ffffff' } ).setOrigin(0.5, 0.5); // 添加菜单文本 - 使用多语言 this.startText = this.add.text( cam.centerX, // 文本在x轴上的位置,相机中心 cam.centerY, // 文本在y轴上的位置,相机中心 LanguageManager.t('start'), { fontSize: this.unit * 3.2, color: '#ffffff' } ).setOrigin(0.5, 0.5); // 添加退出按钮文本 - 使用多语言 this.exitText = this.add.text( cam.centerX, // 文本在x轴上的位置,相机中心 cam.centerY + this.unit * 5, // 文本在y轴上的位置,开始按钮下方 LanguageManager.t('exit'), { fontSize: this.unit * 2.4, color: '#ffffff' } ).setOrigin(0.5, 0.5); // 为开始和退出按钮添加点击功能 this.addButtonClickHandlers(); // 添加语言切换按钮(仅用于测试) this.createLanguageToggle(); // 添加雨天效果控制按钮 this.createRainControlButton(); // 添加页面可见性变化监听,处理页面切出和切回的情况 this.setupVisibilityChangeHandler(); } /** * 创建语言切换按钮(使用文本倒影效果) */ createLanguageToggle() { const cam = this.cameras.main; const languages = LanguageManager.getAvailableLanguages(); let currentLangIndex = languages.indexOf(LanguageManager.currentLanguage); // 按钮基础位置(在退出按钮下方) const baseY = cam.centerY + this.unit * 11; // 创建主按钮文本 const toggleText = this.add.text( cam.centerX, baseY, `${LanguageManager.currentLanguage}`, { fontSize: this.unit * 1.6, color: '#ffffff' } ).setOrigin(0.5, 0.5); // 创建倒影文本 const reflectionText = this.add.text( cam.centerX, baseY + this.unit * 2, // 倒影位置在主文本下方 `${LanguageManager.currentLanguage}`, { fontSize: this.unit * 1.6, color: '#ffffff' } ).setOrigin(0.5, 0.5); // 设置倒影效果 reflectionText.setScale(1, -1); // 垂直翻转 reflectionText.setAlpha(0.3); // 半透明效果 // 添加动态波纹效果 this.createRippleEffect(reflectionText); // 创建透明的点击区域(与文本大小匹配) const clickArea = this.add.rectangle( cam.centerX, baseY + this.unit, toggleText.width, toggleText.height + this.unit * 2, 0x000000, 0 // 完全透明 ).setOrigin(0.5, 0.5); // 设置点击区域可交互 clickArea.setInteractive({ useHandCursor: true }); // 添加点击事件 clickArea.on('pointerdown', () => { currentLangIndex = (currentLangIndex + 1) % languages.length; const newLang = languages[currentLangIndex]; LanguageManager.setLanguage(newLang); // 更新文本 const newText = `${newLang}`; toggleText.setText(newText); reflectionText.setText(newText); // 重新计算点击区域大小 clickArea.width = toggleText.width + 20; clickArea.height = toggleText.height + 40; // 直接更新菜单文本以显示新语言 this.titleText.setText(LanguageManager.t('title')); this.startText.setText(LanguageManager.t('start')); this.exitText.setText(LanguageManager.t('exit')); // 更新雨天控制按钮文本 const rainText = LanguageManager.t('rain'); const stateText = this.isRainEnabled ? LanguageManager.t('on') : LanguageManager.t('off'); this.rainToggleText.setText(`${rainText} ${stateText}`); }); } /** * 创建雨天效果控制按钮 */ createRainControlButton() { const cam = this.cameras.main; // 按钮基础位置(在语言切换按钮下方) const baseY = cam.centerY + this.unit * 17; // 创建主按钮文本 this.rainToggleText = this.add.text( cam.centerX, baseY, LanguageManager.t('rain')+" "+LanguageManager.t('on'), { fontSize: this.unit * 1.6, color: '#ffffff' } ).setOrigin(0.5, 0.5); // 创建透明的点击区域(与文本大小匹配) this.rainClickArea = this.add.rectangle( cam.centerX, baseY, this.rainToggleText.width + 40, this.rainToggleText.height + 20, 0x000000, 0 // 完全透明 ).setOrigin(0.5, 0.5); // 设置点击区域可交互 this.rainClickArea.setInteractive({ useHandCursor: true }); // 添加点击事件 this.rainClickArea.on('pointerdown', () => { this.toggleRainEffect(); }); } /** * 切换雨天效果的开启和关闭 */ toggleRainEffect() { // 切换雨天效果状态 this.isRainEnabled = !this.isRainEnabled; // 更新按钮文本 this.rainToggleText.setText(this.isRainEnabled ? `${LanguageManager.t('rain')} ${LanguageManager.t('on')}` : `${LanguageManager.t('rain')} ${LanguageManager.t('off')}`); // 控制雨滴和涟漪的显示/隐藏 this.updateRainEffectVisibility(); // 如果关闭雨天效果,清除所有与涟漪相关的延迟调用、Tween动画和涟漪对象 if (!this.isRainEnabled) { // 清除所有涟漪相关的setTimeout this.rippleTimeouts.forEach(timeoutId => { clearTimeout(timeoutId); }); // 清空timeout ID数组 this.rippleTimeouts = []; // 清除所有与涟漪相关的Tween动画 this.tweens.killTweensOf(this.rippleGroup.getChildren()); // 彻底清除所有涟漪对象 this.rippleGroup.children.each(ripple => { if (ripple && ripple.active) { ripple.destroy(); } }); // 清空涟漪组 this.rippleGroup.clear(true, true); } // 如果重新开启雨天效果,重新启动涟漪生成 else { // 重新启动涟漪生成 this.createRainRipples(this.screenWidth, this.screenHeight); // 检查并重新启动雨滴的Tween动画(如果需要) this.rainGroup.children.each(raindrop => { // 检查雨滴是否存在且激活 if (raindrop && raindrop.active) { // 获取雨滴的当前Tween动画 const tweens = this.tweens.getTweensOf(raindrop); // 如果没有运行中的Tween动画,重新创建雨滴动画 if (tweens.length === 0) { // 水平方向的位移量(相对单位),固定向左下方倾斜 const horizontalOffset = -this.unit * Phaser.Math.Between(15, 17); // 固定向左方向,8-12单位 // 重新设置雨滴动画 this.tweens.add({ targets: raindrop, x: raindrop.x + horizontalOffset, // 水平方向位移 y: this.screenHeight + this.unit * 2, // 垂直方向位移 duration: Phaser.Math.Between(1000, 1050), // 随机下落速度 ease: 'Linear', repeat: -1, // 无限重复 delay: Phaser.Math.Between(0, 2000), // 随机延迟,使雨滴分布更均匀 onRepeat: (tween, targets) => { // 检查目标是否存在,防止场景重启时的错误 if (targets[0] && targets[0].active) { // 重置雨滴位置,考虑到斜向下落,初始x位置可以稍微超出屏幕边缘(相对单位) const initialX = Phaser.Math.Between(-this.unit * 10, this.screenWidth + this.unit * 10); targets[0].x = initialX; targets[0].y = -this.unit * 2; // 更新动画的x目标值 tween.data[0].value = initialX + horizontalOffset; } } }); } } }); } } /** * 设置页面可见性变化处理器 */ setupVisibilityChangeHandler() { // 保存当前场景的引用 const scene = this; // 页面可见性变化事件处理函数 const handleVisibilityChange = () => { if (document.hidden) { // 页面变为不可见时 scene.onPageHidden(); } else { // 页面变为可见时 scene.onPageVisible(); } }; // 添加事件监听器 document.addEventListener('visibilitychange', handleVisibilityChange); // 在场景销毁时移除事件监听器,防止内存泄漏 this.events.on('destroy', () => { document.removeEventListener('visibilitychange', handleVisibilityChange); }); } /** * 更新雨天效果的可见性 */ updateRainEffectVisibility() { // 控制雨滴的显示/隐藏 this.rainGroup.children.each(raindrop => { raindrop.visible = this.isRainEnabled; }); // 控制涟漪的显示/隐藏 this.rippleGroup.children.each(ripple => { ripple.visible = this.isRainEnabled; }); } /** * 页面变为不可见时的处理逻辑 */ onPageHidden() { // 如果雨天效果是开启的,清除所有涟漪相关的延迟调用、Tween动画和涟漪对象 if (this.isRainEnabled) { // 清除所有涟漪相关的setTimeout this.rippleTimeouts.forEach(timeoutId => { clearTimeout(timeoutId); }); // 清空timeout ID数组 this.rippleTimeouts = []; // 清除所有与涟漪相关的Tween动画 this.tweens.killTweensOf(this.rippleGroup.getChildren()); } } /** * 页面重新变为可见时的处理逻辑 */ onPageVisible() { // 如果雨天效果是开启的,重新启动涟漪生成 if (this.isRainEnabled) { // 清理所有残留的涟漪对象 this.rippleGroup.children.each(ripple => { if (ripple && ripple.active) { ripple.destroy(); } }); // 清空涟漪组 this.rippleGroup.clear(true, true); // 重新启动涟漪生成 this.createRainRipples(this.screenWidth, this.screenHeight); // 检查并重新启动雨滴的Tween动画(如果需要) this.rainGroup.children.each(raindrop => { // 检查雨滴是否存在且激活 if (raindrop && raindrop.active) { // 获取雨滴的当前Tween动画 const tweens = this.tweens.getTweensOf(raindrop); // 如果没有运行中的Tween动画,重新创建雨滴动画 if (tweens.length === 0) { // 水平方向的位移量(相对单位),固定向左下方倾斜 const horizontalOffset = -this.unit * Phaser.Math.Between(15, 17); // 固定向左方向,8-12单位 // 重新设置雨滴动画 this.tweens.add({ targets: raindrop, x: raindrop.x + horizontalOffset, // 水平方向位移 y: this.screenHeight + this.unit * 2, // 垂直方向位移 duration: Phaser.Math.Between(1000, 1050), // 随机下落速度 ease: 'Linear', repeat: -1, // 无限重复 delay: Phaser.Math.Between(0, 2000), // 随机延迟,使雨滴分布更均匀 onRepeat: (tween, targets) => { // 检查目标是否存在,防止场景重启时的错误 if (targets[0] && targets[0].active) { // 重置雨滴位置,考虑到斜向下落,初始x位置可以稍微超出屏幕边缘(相对单位) const initialX = Phaser.Math.Between(-this.unit * 10, this.screenWidth + this.unit * 10); targets[0].x = initialX; targets[0].y = -this.unit * 2; // 更新动画的x目标值 tween.data[0].value = initialX + horizontalOffset; } } }); } } }); } } /** * 为开始和退出按钮添加点击功能 */ addButtonClickHandlers() { // 为开始文本添加点击区域 const startClickArea = this.add.rectangle( this.startText.x, this.startText.y, this.startText.width + 40, // 增加点击区域宽度 this.startText.height + 20, // 增加点击区域高度 0x000000, 0 // 透明 ).setOrigin(0.5, 0.5); // 设置开始按钮可交互 startClickArea.setInteractive({ useHandCursor: true }); // 添加开始按钮鼠标悬停事件 startClickArea.on('pointerover', () => { this.tweens.add({ targets: this.startText, y: this.startText.y - this.unit * 0.5, // 字体上移 duration: 100, ease: 'Sine.easeOut' }); }); // 添加开始按钮鼠标离开事件 startClickArea.on('pointerout', () => { this.tweens.add({ targets: this.startText, y: this.startText.y + this.unit * 0.5, // 字体恢复原位 duration: 100, ease: 'Sine.easeOut' }); }); // 添加开始按钮点击事件 startClickArea.on('pointerdown', () => { console.log('Start game clicked'); // 添加进入场景前的过渡动画 this.cameras.main.fadeOut(500, 0, 0, 0); this.cameras.main.once('camerafadeoutcomplete', () => { // 切换到游戏场景 this.scene.start('GameScene'); }); }); // 为退出文本添加点击区域 const exitClickArea = this.add.rectangle( this.exitText.x, this.exitText.y, this.exitText.width + 40, // 增加点击区域宽度 this.exitText.height + 20, // 增加点击区域高度 0x000000, 0 // 透明 ).setOrigin(0.5, 0.5); // 设置退出按钮可交互 exitClickArea.setInteractive({ useHandCursor: true }); // 添加退出按钮鼠标悬停事件 exitClickArea.on('pointerover', () => { this.tweens.add({ targets: this.exitText, y: this.exitText.y - this.unit * 0.5, // 字体上移 duration: 100, ease: 'Sine.easeOut' }); }); // 添加退出按钮鼠标离开事件 exitClickArea.on('pointerout', () => { this.tweens.add({ targets: this.exitText, y: this.exitText.y + this.unit * 0.5, // 字体恢复原位 duration: 100, ease: 'Sine.easeOut' }); }); // 添加退出按钮点击事件 exitClickArea.on('pointerdown', () => { console.log('Exit game clicked'); // 关闭游戏(在浏览器中关闭窗口) window.close(); }); } /** * 为文本添加波纹效果,模拟水下倒影 * @param {Phaser.GameObjects.Text} text - 要添加波纹效果的文本 */ createRippleEffect(text) { // 保存原始位置 const originalX = text.x; const originalY = text.y; // 创建波纹动画 this.tweens.add({ targets: text, y: originalY + this.unit * 0.5, // 上下波动 duration: 1500, ease: 'Sine.easeInOut', yoyo: true, repeat: -1 // 无限重复 }); // 添加轻微的缩放动画 this.tweens.add({ targets: text, scaleY: -1.05, // 垂直方向轻微缩放(保持负值,继续翻转) duration: 2000, ease: 'Sine.easeInOut', yoyo: true, repeat: -1 // 无限重复 }); // 添加透明度变化 this.tweens.add({ targets: text, alpha: 0.2, // 透明度变化 duration: 2500, ease: 'Sine.easeInOut', yoyo: true, repeat: -1 // 无限重复 }); } /** * 创建雨落下效果 */ createRainEffect() { // 创建雨滴精灵组 this.rainGroup = this.add.group(); // 创建涟漪组,用于管理所有涟漪效果 this.rippleGroup = this.add.group(); // 雨的参数 const rainCount = 50; // 雨滴数量 const screenWidth = this.sys.game.config.width; const screenHeight = this.sys.game.config.height; // 创建雨滴 for (let i = 0; i < rainCount; i++) { // 创建雨滴精灵(使用简单的白色矩形) const raindrop = this.add.rectangle( Phaser.Math.Between(0, screenWidth * 1.2), // 随机x位置,从屏幕右侧外开始 Phaser.Math.Between(-screenHeight, 0), // 初始y位置在屏幕上方 this.unit * 0.2, // 雨滴宽度 this.unit * 1, // 雨滴高度 0xffffff, // 白色 0.3 // 半透明 ); // 添加到组 this.rainGroup.add(raindrop); // 设置雨滴动画 - 斜向落下(固定方向) // 水平方向的位移量(相对单位),固定向左下方倾斜 const horizontalOffset = -this.unit * Phaser.Math.Between(15, 17); // 固定向左方向,8-12单位 // 设置雨滴动画 this.tweens.add({ targets: raindrop, x: raindrop.x + horizontalOffset, // 水平方向位移 y: screenHeight + this.unit * 2, // 垂直方向位移 duration: Phaser.Math.Between(1000, 1050), // 随机下落速度 ease: 'Linear', repeat: -1, // 无限重复 delay: Phaser.Math.Between(0, 2000), // 随机延迟,使雨滴分布更均匀 onRepeat: (tween, targets) => { // 检查目标是否存在,防止场景重启时的错误 if (targets[0] && targets[0].active) { // 重置雨滴位置,考虑到斜向下落,初始x位置可以稍微超出屏幕边缘(相对单位) const initialX = Phaser.Math.Between(-this.unit * 10, screenWidth + this.unit * 10); targets[0].x = initialX; targets[0].y = -this.unit * 2; // 更新动画的x目标值 tween.data[0].value = initialX + horizontalOffset; } } }); } // 添加雨滴涟漪效果 this.createRainRipples(screenWidth, screenHeight); } /** * 创建雨滴落下的涟漪效果 * @param {number} screenWidth - 屏幕宽度 * @param {number} screenHeight - 屏幕高度 */ createRainRipples(screenWidth, screenHeight) { // 只有在雨天效果开启时,才创建涟漪 if (this.isRainEnabled) { // 增加涟漪数量 const rippleCount = 25; for (let i = 0; i < rippleCount; i++) { // 随机延迟开始 const timeoutId = setTimeout(() => { this.createSingleRipple(screenWidth, screenHeight); }, Phaser.Math.Between(0, 3000)); // 将timeout ID保存到数组中 this.rippleTimeouts.push(timeoutId); } } } /** * 创建单个雨滴涟漪 * @param {number} screenWidth - 屏幕宽度 * @param {number} screenHeight - 屏幕高度 */ createSingleRipple(screenWidth, screenHeight) { // 随机位置 const x = Phaser.Math.Between(0, screenWidth); const y = Phaser.Math.Between(0, screenHeight); // 整个屏幕高度 // 每个雨滴产生多个同心涟漪 const rippleLayers = 3; const baseDelay = 100; for (let i = 0; i < rippleLayers; i++) { // 延迟创建后续的涟漪层 const timeoutId = setTimeout(() => { // 只有在雨天效果开启时,才创建新的涟漪 if (this.isRainEnabled) { // 创建涟漪圆圈(只保留边框) const ripple = this.add.circle( x, y, 0, // 初始半径 0xffffff, // 白色 0 // 透明填充 ).setStrokeStyle(this.unit * 0.08, 0xffffff, 0.3); // 更细的边框,更高的透明度 // 将涟漪添加到涟漪组中 this.rippleGroup.add(ripple); // 随机化每个涟漪的参数,增加自然感 const maxRadius = this.unit * Phaser.Math.Between(2, 3); const duration = Phaser.Math.Between(800, 1500); const startAlpha = Phaser.Math.Between(0.3, 0.5); // 设置初始透明度 ripple.setAlpha(startAlpha); // 创建扩散动画 this.tweens.add({ targets: ripple, radius: maxRadius, // 最大半径 alpha: 0, // 逐渐消失 duration: duration, // 动画持续时间 ease: 'Sine.easeOut', onComplete: (tween, targets) => { // 动画完成后销毁涟漪 // 确保目标对象仍然存在且未被销毁 if (targets && targets[0] && targets[0].scene && targets[0].active) { targets[0].destroy(); } } }); } }, i * baseDelay); // 将timeout ID保存到数组中 this.rippleTimeouts.push(timeoutId); } // 只有在雨天效果开启时,才递归创建新的涟漪 if (this.isRainEnabled) { const timeoutId = setTimeout(() => { this.createSingleRipple(screenWidth, screenHeight); }, Phaser.Math.Between(500, 1500)); // 将递归调用的timeout ID保存到数组中 this.rippleTimeouts.push(timeoutId); } } }