Files
CINC/scenes/MenuScene.js
T

747 lines
28 KiB
JavaScript

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 = [];
// 创建文本container
this.textContainer = this.add.container(0, 0);
// 添加落雨落效果
this.createRainEffect();
// 初始化语言管理器 - 支持多语言
LanguageManager.loadTranslations(translations);
// 添加菜单文本 - 使用多语言
this.titleText = this.add.text(
cam.centerX, // 文本在x轴上的位置,相机中心
cam.centerY - this.unit * 12, // 文本在y轴上的位置,相机中心上方
LanguageManager.t('title'),
{ fontSize: this.unit * 2.4, color: '#ffffff', fontFamily: 'monospace'}
).setOrigin(0.5, 0.5);
// 为标题文本添加倒影
this.createTextReflection(this.titleText);
// 添加菜单文本 - 使用多语言
this.startText = this.add.text(
cam.centerX, // 文本在x轴上的位置,相机中心
cam.centerY, // 文本在y轴上的位置,相机中心
LanguageManager.t('start'),
{ fontSize: this.unit * 3.2, color: '#ffffff', fontFamily: 'monospace'}
).setOrigin(0.5, 0.5);
// 为开始文本添加倒影
this.createTextReflection(this.startText);
// 添加退出按钮文本 - 使用多语言
this.exitText = this.add.text(
cam.centerX, // 文本在x轴上的位置,相机中心
cam.centerY + this.unit * 10, // 文本在y轴上的位置,开始按钮下方
LanguageManager.t('exit'),
{ fontSize: this.unit * 2.4, color: '#ffffff', fontFamily: 'monospace'}
).setOrigin(0.5, 0.5);
// 为退出文本添加倒影
this.createTextReflection(this.exitText);
// 为开始和退出按钮添加点击功能
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 * 18;
// 创建主按钮文本
const toggleText = this.add.text(
cam.centerX,
baseY,
`${LanguageManager.currentLanguage}`,
{ fontSize: this.unit * 1.6, color: '#ffffff', fontFamily: 'monospace'}
).setOrigin(0.5, 0.5);
// 使用createTextReflection方法创建倒影
const reflectionText = this.createTextReflection(toggleText);
// 创建透明的点击区域(与文本大小匹配)
const clickArea = this.add.rectangle(
cam.centerX,
baseY + this.unit * 0.5,
toggleText.width,
toggleText.height + this.unit,
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 + 20;
// 直接更新菜单文本以显示新语言
this.titleText.setText(LanguageManager.t('title'));
this.startText.setText(LanguageManager.t('start'));
this.exitText.setText(LanguageManager.t('exit'));
// 同步更新倒影文本
if (this.titleText.reflection) {
this.titleText.reflection.setText(LanguageManager.t('title'));
}
if (this.startText.reflection) {
this.startText.reflection.setText(LanguageManager.t('start'));
}
if (this.exitText.reflection) {
this.exitText.reflection.setText(LanguageManager.t('exit'));
}
// 更新雨天控制按钮文本
const rainText = LanguageManager.t('rain');
const stateText = this.isRainEnabled ? LanguageManager.t('on') : LanguageManager.t('off');
this.rainToggleText.setText(`${rainText} ${stateText}`);
// 同步更新雨天控制按钮的倒影文本
if (this.rainToggleText.reflection) {
this.rainToggleText.reflection.setText(`${rainText} ${stateText}`);
}
});
}
/**
* 创建雨天效果控制按钮
*/
createRainControlButton() {
const cam = this.cameras.main;
// 按钮基础位置(在语言切换按钮下方)
const baseY = cam.centerY + this.unit * 26;
// 创建主按钮文本
this.rainToggleText = this.add.text(
cam.centerX,
baseY,
LanguageManager.t('rain')+" "+LanguageManager.t('on'),
{ fontSize: this.unit * 1.6, color: '#ffffff', fontFamily: 'monospace'}
).setOrigin(0.5, 0.5);
// 使用createTextReflection方法创建倒影
this.createTextReflection(this.rainToggleText);
// 创建透明的点击区域(与文本大小匹配)
this.rainClickArea = this.add.rectangle(
cam.centerX,
baseY + this.unit * 0.5,
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')}`);
// 同步更新倒影文本
if (this.rainToggleText.reflection) {
this.rainToggleText.reflection.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() {
// 控制雨滴container的显示/隐藏
this.rainContainer.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.rippleTimeouts.forEach(timeoutId => {
clearTimeout(timeoutId);
});
this.rippleTimeouts = [];
// 添加进入场景前的过渡动画
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 // 无限重复
});
}
/**
* 为文本创建倒影效果
* @param {Phaser.GameObjects.Text} text - 要创建倒影的文本
*/
createTextReflection(text) {
// 创建倒影文本
const reflectionText = this.add.text(
text.x,
text.y + text.height, // 倒影位置在主文本下方,间隔为0
text.text,
{ fontSize: text.style.fontSize, color: '#ffffff', fontFamily: text.style.fontFamily}
).setOrigin(0.5, 0.5);
// 设置倒影效果
reflectionText.setScale(1, -1); // 垂直翻转
reflectionText.setAlpha(0.3); // 半透明效果
// 将主文本和倒影文本添加到container中
this.textContainer.add(text);
this.textContainer.add(reflectionText);
// 将倒影文本引用存储在主文本对象中,便于后续同步更新
text.reflection = reflectionText;
// 添加动态波纹效果
this.createRippleEffect(reflectionText);
return reflectionText;
}
/**
* 创建雨落下效果
*/
createRainEffect() {
// 创建雨滴container
this.rainContainer = this.add.container(0, 0);
// 创建雨滴精灵组
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 // 半透明
);
// 将雨滴添加到container中
this.rainContainer.add(raindrop);
// 添加到组
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(() => {
// 确保场景仍然存在且 rippleGroup 存在
if (this.scene && this.rippleGroup) {
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 && this.rippleGroup) {
// 创建涟漪圆圈(只保留边框)
const ripple = this.add.circle(
x,
y,
0, // 初始半径
0xffffff, // 白色
0 // 透明填充
).setStrokeStyle(this.unit * 0.08, 0xffffff, 0.3); // 更细的边框,更高的透明度
// 将涟漪添加到雨滴container中
this.rainContainer.add(ripple);
// 将涟漪添加到涟漪组中
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);
}
}
/**
* 场景关闭时的清理工作
*/
shutdown() {
// 清除所有与涟漪相关的延迟调用
this.rippleTimeouts.forEach(timeoutId => {
clearTimeout(timeoutId);
});
// 清空timeout数组
this.rippleTimeouts = [];
// 调用父类的shutdown方法
super.shutdown();
}
}