import * as Phaser from "phaser"; import { getCanvasBgColorOverride, getUiBarBgColorOverride, } from "../../LevelDataManager"; /** 主题配置接口 */ export interface ThemeConfig { id: string; textureKey: string; price: number; bgColor: string; // 例如 "#FFFFFF" arrowType: string; // 'draw' | 'snake' | 'snake2' | 'heart' | 'diamond' | 'wave' | 'striped' | 'caterpillar' | 'emoji' arrowColor: string; // 例如 "#000000" 或 "Random" arrowTextureKey: string; previewScale?: number; uiTextColor?: string; // UI 文本主颜色(时钟、进度等),默认 "#50526B" uiLevelTextColor?: string; // 关卡标题颜色,默认 "#6471FA" } /** 默认主题列表 */ const DEFAULT_THEMES: ThemeConfig[] = [ { id: "black", textureKey: "theme_black", price: 0, bgColor: "#FFFFFF", arrowType: "draw", arrowColor: "#000000", arrowTextureKey: "none", previewScale: 1, }, ]; /** * 主题管理器(单例)。 * 负责从缓存中读取主题配置、维护当前主题和拥有状态。 * Game / ThemePanel 都应通过该单例来获取主题信息。 */ export class ThemeManager { private static _instance: ThemeManager | null = null; static get instance(): ThemeManager { if (!this._instance) { this._instance = new ThemeManager(); } return this._instance; } private themes: ThemeConfig[] = []; private ownedIds = new Set(); private currentId: string = "black"; private constructor() {} /** * 从指定场景的缓存中初始化主题配置(只会执行一次)。 * @param scene 当前场景 */ init(scene: Phaser.Scene) { const raw = scene.cache.json.get("themes") as ThemeConfig[] | undefined; if (Array.isArray(raw) && raw.length > 0) { this.themes = raw; } else if (this.themes.length === 0) { this.themes = DEFAULT_THEMES; } // 价格为 0 的主题默认视为已拥有 if (this.ownedIds.size === 0) { for (const t of this.themes) { if (t.price === 0) this.ownedIds.add(t.id); } } // 当前主题必须存在于列表中 if (!this.themes.find((t) => t.id === this.currentId)) { this.currentId = this.themes[0]?.id ?? "black"; } } /** * 从用户档案同步已解锁的主题和当前选中主题。 * @param themes 用户档案中的主题数据 */ syncFromProfile(themes: { unlockedIds: string[]; currentId: string }) { if (!themes) return; if (Array.isArray(themes.unlockedIds)) { for (const id of themes.unlockedIds) { this.ownedIds.add(id); } } if (themes.currentId) { this.currentId = themes.currentId; } } /** 获取当前选中的主题配置 */ get currentTheme(): ThemeConfig { const found = this.themes.find((t) => t.id === this.currentId); if (found !== undefined) { return found; } if (this.themes.length > 0) { return this.themes[0]!; } return DEFAULT_THEMES[0]!; } /** * 获取画布背景颜色(数值格式)。 * 优先使用变体配置的 canvasBgColor 覆盖。 */ getBackgroundColorNumber(): number { const override = getCanvasBgColorOverride(); if (override !== undefined) { return override; } return this.parseColorToNumber(this.currentTheme.bgColor, 0xffffff); } /** * 获取 UI 菜单栏背景颜色(用于顶部和底部菜单栏)。 * 优先使用变体配置的 uiBarBgColor 覆盖。 */ getUiBarBackgroundColorNumber(): number { const override = getUiBarBgColorOverride(); if (override !== undefined) { return override; } return this.parseColorToNumber(this.currentTheme.bgColor, 0xffffff); } /** * 获取 UI 关卡标题颜色。 * 如果主题未配置则返回默认值 "#6471FA"。 */ getUiLevelTextColor(): string { return this.currentTheme.uiLevelTextColor || "#6471FA"; } /** * 获取固定箭头颜色(数值格式)。 */ getFixedArrowColorNumber(): number { const spec = this.currentTheme.arrowColor; return this.parseColorToNumber(spec, 0x000000); } /** * 将颜色字符串解析为数值。 * 支持 "#RRGGBB" 和 "0xRRGGBB" 格式。 * @param spec 颜色字符串 * @param fallback 解析失败时的回退值 * @returns 颜色数值 */ private parseColorToNumber( spec: string | undefined, fallback: number, ): number { if (!spec) return fallback; const s = spec.trim(); if (/^#?[0-9a-fA-F]{6}$/.test(s)) { const hex = s.startsWith("#") ? s.slice(1) : s; return parseInt(hex, 16); } if (/^0x[0-9a-fA-F]{6}$/.test(s)) { return parseInt(s, 16); } return fallback; } }