import { Scene } from "phaser"; import * as Phaser from "phaser"; import { LevelManager } from "../levels/LevelManager"; import { SettingsManager } from "./GameUI/SettingsManager"; import { ThemeManager } from "./GameUI/ThemeManager"; import { clearActiveRunContext } from "../run/RunContextRuntime"; import { GameConfig } from "../GameConfig"; // 导入音频资源 import click from "assets/sounds/click.mp3"; import arrowMoveError from "assets/sounds/arrow_move_error.mp3"; import winAnim from "assets/sounds/win_anim.mp3"; import mistake from "assets/sounds/mistake.mp3"; import successSound from "assets/sounds/success.mp3"; import loseSound from "assets/sounds/lose.mp3"; import failSound from "assets/sounds/fail.mp3"; import appearSound from "assets/sounds/appear.mp3"; import buttonClick from "assets/sounds/button_click.mp3"; import bgm from "assets/sounds/bg.mp3"; // 导入图片资源 import nomoveHint from "assets/nomove_hint.webp"; import winDot from "assets/win_dot.webp"; import normalDot from "assets/normal_dot.webp"; import losePanel from "assets/lose.webp"; import winPanel from "assets/win.webp"; import figer from "assets/figer.webp"; import time from "assets/time.webp"; import logoImage from "assets/logo.webp"; const LoadingConfig = { // 文案配置 LOGO_IMAGE: logoImage, // Logo 图片 APP_TITLE: "Arrows - Puzzle Escape", // 应用标题 APP_STARS: "★★★★★", // 评分星星 APP_DESC: "Solve puzzles and escape!\nSwipe arrows to complete levels!", // 应用描述 LOADING_TEXT: "Loading Playable Ads", // 加载文字 GAMEPAD_ICON: "🎮", // 游戏手柄图标 // 颜色配置(暗色主题) BG_COLOR: "#1a1a1a", // 背景色(深灰黑) TITLE_COLOR: "#ffffff", // 标题颜色(白色) STARS_COLOR: "#ffc107", // 星星颜色(金色) DESC_COLOR: "#aaaaaa", // 描述文字颜色(浅灰) LOADING_COLOR: "#888888", // Loading 文字颜色(中灰) // 时间配置 SCENE_DURATION: 300, // Loading 页面最小显示时长(毫秒) DOT_ANIM_DELAY: 200, // 点号动画间隔(毫秒) // 跳跃动画配置 BOUNCE_HEIGHT_LANDSCAPE: 8, // 横屏跳跃高度 BOUNCE_HEIGHT_PORTRAIT: 10, // 竖屏跳跃高度 BOUNCE_DURATION: 300, // 跳跃动画时长(毫秒) BOUNCE_REPEAT_DELAY: 100, // 跳跃重复延迟(毫秒) }; export class Preloader extends Scene { // UI 元素 private logoElement!: HTMLImageElement; private loadingText!: Phaser.GameObjects.Text; private gamepadIcon!: Phaser.GameObjects.Text; // 动画相关 private dotCount: number = 0; private dotTimer!: Phaser.Time.TimerEvent; // 加载状态 private loadStartTime: number = 0; constructor() { super("Preloader"); } init() { const width = this.scale.width; const height = this.scale.height; const isLandscape = width > height; // 记录加载开始时间 this.loadStartTime = Date.now(); const centerX = width / 2; // 设置背景色为暗色(使用 LoadingConfig) this.cameras.main.setBackgroundColor(LoadingConfig.BG_COLOR); // 根据画布尺寸计算缩放比例(基于设计尺寸) const baseWidth = isLandscape ? 667 : 375; const baseHeight = isLandscape ? 375 : 667; const scaleX = width / baseWidth; const scaleY = height / baseHeight; const scale = Math.min(scaleX, scaleY, 2); // 限制最大缩放 // 根据横竖屏获取基础布局配置,再乘以缩放比例 const logoSize = Math.round((isLandscape ? 80 : 120) * scale); const titleFontSize = Math.round((isLandscape ? 24 : 32) * scale); const starsFontSize = Math.round((isLandscape ? 18 : 24) * scale); const descFontSize = Math.round((isLandscape ? 12 : 14) * scale); const descMaxWidth = Math.round((isLandscape ? 300 : 280) * scale); const iconFontSize = Math.round((isLandscape ? 18 : 24) * scale); const loadingFontSize = Math.round((isLandscape ? 14 : 18) * scale); const gap = Math.round((isLandscape ? 8 : 12) * scale); // 位置计算 const logoY = height * (isLandscape ? 0.35 : 0.28); const titleY = logoY + logoSize / 2 + Math.round((isLandscape ? 15 : 25) * scale); const starsY = titleY + Math.round((isLandscape ? 25 : 35) * scale); const descY = starsY + Math.round((isLandscape ? 20 : 30) * scale); // const progressBarY = height * (isLandscape ? 0.70 : 0.65); // 进度条位置 const loadingY = height * (isLandscape ? 0.85 : 0.88); // Logo - 使用 DOM 元素显示,避免 canvas 缩放锯齿 this.createDomLogo(centerX, logoY, logoSize); // 标题 this.add .text(centerX, titleY, LoadingConfig.APP_TITLE, { fontFamily: "Arial, sans-serif", fontSize: `${titleFontSize}px`, color: LoadingConfig.TITLE_COLOR, fontStyle: "bold", }) .setOrigin(0.5); // 五星评分 this.add .text(centerX, starsY, LoadingConfig.APP_STARS, { fontFamily: "Arial, sans-serif", fontSize: `${starsFontSize}px`, color: LoadingConfig.STARS_COLOR, }) .setOrigin(0.5); // 描述文字 this.add .text(centerX, descY, LoadingConfig.APP_DESC, { fontFamily: "Arial, sans-serif", fontSize: `${descFontSize}px`, color: LoadingConfig.DESC_COLOR, align: "center", wordWrap: { width: descMaxWidth }, }) .setOrigin(0.5, 0); // 游戏手柄图标 + Loading 文字(组合居中) this.loadingText = this.add.text(0, 0, `${LoadingConfig.LOADING_TEXT}...`, { fontFamily: "Arial, sans-serif", fontSize: `${loadingFontSize}px`, color: LoadingConfig.LOADING_COLOR, }); // 计算组合总宽度并居中 const textWidth = this.loadingText.width; const iconWidth = iconFontSize; const totalWidth = iconWidth + gap + textWidth; const startX = centerX - totalWidth / 2; // 放置图标 this.gamepadIcon = this.add .text(startX + iconWidth / 2, loadingY, LoadingConfig.GAMEPAD_ICON, { fontFamily: "Arial, sans-serif", fontSize: `${iconFontSize}px`, }) .setOrigin(0.5); // 添加跳跃动画 this.startBounceAnimation(loadingY, isLandscape, scale); // 放置文字(紧跟图标后面) this.loadingText.setPosition(startX + iconWidth + gap, loadingY); this.loadingText.setOrigin(0, 0.5); // 启动点号动画 this.startDotAnimation(); } preload() { // 使用 import 导入的资源路径加载 // 音频资源 this.load.audio("click", click); this.load.audio("arrow_move_error", arrowMoveError); this.load.audio("win_anim", winAnim); this.load.audio("success", successSound); this.load.audio("lose", loseSound); this.load.audio("fail", failSound); this.load.audio("appear", appearSound); this.load.audio("button_click", buttonClick); this.load.audio("mistake", mistake); this.load.audio("bg", bgm); // 图片资源 this.load.image("nomove_hint", nomoveHint); this.load.image("win_dot", winDot); this.load.image("normal_dot", normalDot); this.load.image("lose_panel", losePanel); this.load.image("win_panel", winPanel); this.load.image("figer", figer); this.load.image("time", time); } create() { this.tryShowGame(); } // 资源加载完成后,根据实际加载时间决定是否立即切换 private tryShowGame(): void { const loadDuration = Date.now() - this.loadStartTime; const minDuration = LoadingConfig.SCENE_DURATION; if (loadDuration >= minDuration) { // 已经超过最小显示时间,立即切换 this.showGame(); } else { // 加载太快,等待剩余时间后切换(避免一闪而过) const remainingTime = minDuration - loadDuration; this.time.delayedCall(remainingTime, () => { this.showGame(); }); } } // 真正切换到游戏场景 private showGame(): void { console.log("All conditions met, showing game"); // 移除 DOM logo 元素 this.removeDomLogo(); // 停留在Loading场景(调试用) if (GameConfig.debugScene === "loading") { return; } // 基础数据 const profile = GameConfig; const settings = SettingsManager.instance; settings.musicOn = profile.settings.musicOn; settings.soundOn = profile.settings.soundOn; if (typeof profile.settings.dragSpeed === "number") { settings.dragSpeed = Phaser.Math.Clamp(profile.settings.dragSpeed, 0, 1); } // 同步主题 const themeManager = ThemeManager.instance; themeManager.syncFromProfile(profile.themes); // 进入编辑器场景 if (GameConfig.debugScene === "editor") { // 清理并启动 Editor 场景,Editor 场景会自动打开编辑器 clearActiveRunContext(); this.scene.start("Editor"); return; } // 进入游戏模式 const levelManager = LevelManager.instance; levelManager.setCurrentIndex(1); clearActiveRunContext(); // 清理 this.scene.start("Game"); } private startDotAnimation(): void { this.dotTimer = this.time.addEvent({ delay: LoadingConfig.DOT_ANIM_DELAY, callback: () => { this.dotCount = (this.dotCount + 1) % 4; const dots = ".".repeat(this.dotCount); this.loadingText.setText(LoadingConfig.LOADING_TEXT + dots); }, loop: true, }); } // 图标跳跃动画 private startBounceAnimation( baseY: number, isLandscape: boolean, scale: number, ): void { const bounceHeight = Math.round( (isLandscape ? LoadingConfig.BOUNCE_HEIGHT_LANDSCAPE : LoadingConfig.BOUNCE_HEIGHT_PORTRAIT) * scale, ); this.tweens.add({ targets: this.gamepadIcon, y: baseY - bounceHeight, duration: LoadingConfig.BOUNCE_DURATION, ease: "Quad.easeOut", yoyo: true, repeat: -1, repeatDelay: LoadingConfig.BOUNCE_REPEAT_DELAY, }); } shutdown(): void { if (this.dotTimer) { this.dotTimer.destroy(); } this.removeDomLogo(); } // 移除 DOM logo 元素 private removeDomLogo(): void { if (this.logoElement && this.logoElement.parentNode) { this.logoElement.parentNode.removeChild(this.logoElement); } } // 创建 DOM logo 元素 private createDomLogo(centerX: number, centerY: number, size: number): void { // 获取 canvas 元素和其在页面中的位置 const canvas = this.game.canvas; const canvasRect = canvas.getBoundingClientRect(); // 计算缩放比例(canvas 实际显示大小 vs 游戏内部尺寸) const scaleX = canvasRect.width / this.scale.width; const scaleY = canvasRect.height / this.scale.height; // 计算 logo 在页面中的实际位置和大小 const actualSize = size * scaleX; const actualX = canvasRect.left + centerX * scaleX - actualSize / 2; const actualY = canvasRect.top + centerY * scaleY - actualSize / 2; // 创建 img 元素 this.logoElement = document.createElement("img"); this.logoElement.src = LoadingConfig.LOGO_IMAGE; this.logoElement.style.cssText = ` position: absolute; left: ${actualX}px; top: ${actualY}px; width: ${actualSize}px; height: ${actualSize}px; pointer-events: none; z-index: 1000; `; document.body.appendChild(this.logoElement); } }