diff --git a/lang/LanguageManager.js b/lang/LanguageManager.js new file mode 100644 index 0000000..81c8978 --- /dev/null +++ b/lang/LanguageManager.js @@ -0,0 +1,51 @@ +// lang/LanguageManager.js +class LanguageManager { + constructor() { + this.translations = {}; + this.currentLanguage = '#^'; // 默认语言 + } + + /** + * 加载翻译文件 + * @param {Object} translations - 翻译对象 + */ + loadTranslations(translations) { + this.translations = translations; + } + + /** + * 设置当前语言 + * @param {string} lang - 语言代码,如 'en', 'zh' + */ + setLanguage(lang) { + if (this.translations[lang]) { + this.currentLanguage = lang; + return true; + } + return false; + } + + /** + * 获取翻译文本 + * @param {string} key - 文本键值 + * @param {string} lang - 可选,指定语言,默认使用当前语言 + * @returns {string} 翻译后的文本 + */ + t(key, lang = this.currentLanguage) { + if (this.translations[lang] && this.translations[lang][key]) { + return this.translations[lang][key]; + } + return key; // 如果没有找到翻译,返回原键值 + } + + /** + * 获取可用语言列表 + * @returns {string[]} 语言代码数组 + */ + getAvailableLanguages() { + return Object.keys(this.translations); + } +} + +// 导出单例 +export default new LanguageManager(); \ No newline at end of file diff --git a/lang/translations.js b/lang/translations.js new file mode 100644 index 0000000..5378daa --- /dev/null +++ b/lang/translations.js @@ -0,0 +1,18 @@ + +export default { + "#^": { + "title": "#*^#", + 'start': "@%!$%", + "exit": "#@*%" + }, + "en": { + "title": "CINC", + "start": "start", + "exit": "exit" + }, + "中文": { + "title": "游戏", + "start": "开始", + "exit": "退出" + } +} diff --git a/main.js b/main.js index a6014ee..62d11a4 100644 --- a/main.js +++ b/main.js @@ -42,8 +42,8 @@ const config = { ...gameConfig, // 指定游戏使用的场景列表,包括主场景和所有测试场景 scene: [ - BootScene, MenuScene, + BootScene, GameScene, KeyboardMovementTestScene, TargetMovementTestScene, diff --git a/scenes/GameScene.js b/scenes/GameScene.js index 884bdd7..a1597f3 100644 --- a/scenes/GameScene.js +++ b/scenes/GameScene.js @@ -27,6 +27,8 @@ export default class GameScene extends Phaser.Scene { // create方法:当场景创建时自动调用,用于初始化游戏元素 create() { + // 设置背景颜色 + this.cameras.main.setBackgroundColor('#333333') // === 0. 添加窗口大小变化监听 === // 监听浏览器窗口大小变化事件,当窗口大小改变时调用resizeGame方法 window.addEventListener('resize', this.resizeGame.bind(this)); @@ -102,6 +104,63 @@ export default class GameScene extends Phaser.Scene { x: this.sys.game.config.width - 200 - 10, // 小地图在屏幕上的x坐标 y: 10 // 小地图在屏幕上的y坐标 }); + + // === 7. 添加菜单按钮 === + // 添加返回菜单按钮 + const menuButton = this.add.text( + 20, // x坐标 + 20, // y坐标 + '返回菜单', // 文本内容 + { fontSize: '16px', color: '#ffffff' } // 样式 + ).setOrigin(0, 0); + + // 为返回菜单按钮添加点击区域 + const menuClickArea = this.add.rectangle( + menuButton.x + menuButton.width / 2, + menuButton.y + menuButton.height / 2, + menuButton.width + 20, + menuButton.height + 10, + 0x000000, + 0 // 透明 + ).setOrigin(0.5, 0.5); + + // 设置返回菜单按钮可交互 + menuClickArea.setInteractive({ useHandCursor: true }); + + // 添加返回菜单按钮点击事件 + menuClickArea.on('pointerdown', () => { + console.log('Return to menu clicked'); + // 直接切换到菜单场景 + this.scene.start('MenuScene'); + }); + + // 添加进入boot菜单按钮 + const bootButton = this.add.text( + 20, // x坐标 + 60, // y坐标 + '进入Boot菜单', // 文本内容 + { fontSize: '16px', color: '#ffffff' } // 样式 + ).setOrigin(0, 0); + + // 为进入boot菜单按钮添加点击区域 + const bootClickArea = this.add.rectangle( + bootButton.x + bootButton.width / 2, + bootButton.y + bootButton.height / 2, + bootButton.width + 20, + bootButton.height + 10, + 0x000000, + 0 // 透明 + ).setOrigin(0.5, 0.5); + + // 设置进入boot菜单按钮可交互 + bootClickArea.setInteractive({ useHandCursor: true }); + + // 添加进入boot菜单按钮点击事件 + bootClickArea.on('pointerdown', () => { + console.log('Enter boot menu clicked'); + // 直接切换到boot场景 + this.scene.start('BootScene'); + }); } // update方法:每帧自动调用(约60次/秒),用于更新游戏状态 diff --git a/scenes/MenuScene.js b/scenes/MenuScene.js index efe592f..cb789af 100644 --- a/scenes/MenuScene.js +++ b/scenes/MenuScene.js @@ -1,3 +1,6 @@ +import LanguageManager from '../lang/LanguageManager.js'; +import translations from '../lang/translations.js'; + export default class MenuScene extends Phaser.Scene { constructor() { super('MenuScene') @@ -9,20 +12,343 @@ export default class MenuScene extends Phaser.Scene { // 获取主相机对象 const cam = this.cameras.main; - // 添加菜单文本 - this.add.text( - cam.centerX/5, // 文本在x轴上的位置,相机中心的1/5 - cam.centerY/5, // 文本在y轴上的位置,相机中心的1/5 - 'CINC', - { fontSize: '64px', color: '#ffffff' } + // 添加落雨落效果 + this.createRainEffect(); + + // 初始化语言管理器 - 支持多语言 + LanguageManager.loadTranslations(translations); + + // 添加菜单文本 - 使用多语言 + this.titleText = this.add.text( + cam.centerX, // 文本在x轴上的位置,相机中心 + cam.centerY - 80, // 文本在y轴上的位置,相机中心上方 + LanguageManager.t('title'), + { fontSize: '24px', color: '#ffffff' } ).setOrigin(0.5, 0.5); - this.add.rectangle( - cam.centerX/5, // 矩形在x轴上的位置,相机中心的1/5 - cam.centerY, // 矩形在y轴上的位置,相机中心的1/5 - 10, // 矩形的宽度 - 10, // 矩形的高度 - 0xffffff // 矩形的颜色,这里是白色 + // 添加菜单文本 - 使用多语言 + this.startText = this.add.text( + cam.centerX, // 文本在x轴上的位置,相机中心 + cam.centerY, // 文本在y轴上的位置,相机中心 + LanguageManager.t('start'), + { fontSize: '32px', color: '#ffffff' } ).setOrigin(0.5, 0.5); + + // 添加退出按钮文本 - 使用多语言 + this.exitText = this.add.text( + cam.centerX, // 文本在x轴上的位置,相机中心 + cam.centerY + 50, // 文本在y轴上的位置,开始按钮下方 + LanguageManager.t('exit'), + { fontSize: '24px', color: '#ffffff' } + ).setOrigin(0.5, 0.5); + + // 为开始和退出按钮添加点击功能 + this.addButtonClickHandlers(); + + // 添加语言切换按钮(仅用于测试) + this.createLanguageToggle(); + } + + /** + * 创建语言切换按钮(使用文本倒影效果) + */ + createLanguageToggle() { + const cam = this.cameras.main; + const languages = LanguageManager.getAvailableLanguages(); + let currentLangIndex = languages.indexOf(LanguageManager.currentLanguage); + + // 按钮基础位置(在退出按钮下方) + const baseY = cam.centerY + 110; + + // 创建主按钮文本 + const toggleText = this.add.text( + cam.centerX, + baseY, + `${LanguageManager.currentLanguage}`, + { fontSize: '16px', color: '#ffffff' } + ).setOrigin(0.5, 0.5); + + // 创建倒影文本 + const reflectionText = this.add.text( + cam.centerX, + baseY + 20, // 倒影位置在主文本下方 + `${LanguageManager.currentLanguage}`, + { fontSize: '16px', 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, + toggleText.width, + toggleText.height, + 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')); + }); + } + + /** + * 为开始和退出按钮添加点击功能 + */ + 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 - 5, // 字体上移 + duration: 100, + ease: 'Sine.easeOut' + }); + }); + + // 添加开始按钮鼠标离开事件 + startClickArea.on('pointerout', () => { + this.tweens.add({ + targets: this.startText, + y: this.startText.y + 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 - 5, // 字体上移 + duration: 100, + ease: 'Sine.easeOut' + }); + }); + + // 添加退出按钮鼠标离开事件 + exitClickArea.on('pointerout', () => { + this.tweens.add({ + targets: this.exitText, + y: this.exitText.y + 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 + 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(); + + // 雨的参数 + 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), // 随机x位置 + Phaser.Math.Between(-screenHeight, 0), // 初始y位置在屏幕上方 + 2, // 雨滴宽度 + 10, // 雨滴高度 + 0xffffff, // 白色 + 0.3 // 半透明 + ); + + // 添加到组 + this.rainGroup.add(raindrop); + + // 设置雨滴动画 + this.tweens.add({ + targets: raindrop, + y: screenHeight + 20, // 下落到底部 + duration: Phaser.Math.Between(1000, 2000), // 随机下落速度 + ease: 'Linear', + repeat: -1, // 无限重复 + delay: Phaser.Math.Between(0, 2000), // 随机延迟,使雨滴分布更均匀 + onRepeat: (tween, targets) => { + // 检查目标是否存在,防止场景重启时的错误 + if (targets[0] && targets[0].active) { + // 重置雨滴位置 + targets[0].x = Phaser.Math.Between(0, screenWidth); + targets[0].y = -20; + } + } + }); + } + + // 添加雨滴涟漪效果 + this.createRainRipples(screenWidth, screenHeight); + } + + /** + * 创建雨滴落下的涟漪效果 + * @param {number} screenWidth - 屏幕宽度 + * @param {number} screenHeight - 屏幕高度 + */ + createRainRipples(screenWidth, screenHeight) { + // 涟漪数量 + const rippleCount = 15; + + for (let i = 0; i < rippleCount; i++) { + // 随机延迟开始 + setTimeout(() => { + this.createSingleRipple(screenWidth, screenHeight); + }, Phaser.Math.Between(0, 2000)); + } + } + + /** + * 创建单个雨滴涟漪 + * @param {number} screenWidth - 屏幕宽度 + * @param {number} screenHeight - 屏幕高度 + */ + createSingleRipple(screenWidth, screenHeight) { + // 随机位置 + const x = Phaser.Math.Between(0, screenWidth); + const y = Phaser.Math.Between(0, screenHeight); // 整个屏幕高度 + + // 创建涟漪圆圈(只保留边框) + const ripple = this.add.circle( + x, + y, + 0, // 初始半径 + 0xffffff, // 白色 + 0 // 透明填充 + ).setStrokeStyle(1, 0xffffff, 0.2); // 1像素白色边框,半透明 + + // 创建扩散动画 + this.tweens.add({ + targets: ripple, + radius: 20, // 最大半径 + alpha: 0, // 逐渐消失 + duration: 1000, // 动画持续时间 + ease: 'Sine.easeOut', + onComplete: (tween, targets) => { + // 动画完成后销毁涟漪 + if (targets[0] && targets[0].active) { + targets[0].destroy(); + } + + // 递归创建新的涟漪 + this.createSingleRipple(screenWidth, screenHeight); + } + }); } } \ No newline at end of file