/** * ============================================================================ * UIManager.js - UI 管理器(gameabc 框架层) * ============================================================================ * * 职责: * - UI 组件的注册、注销、查询和生命周期管理 * - 场景定义与切换(BaseComponent 组合的具名快照) * - 全局公共 UI:Loading / Message / Confirm 对话框 * * 设计原则: * 1. 精灵由 gameabc 编辑器预先创建,UIManager 只控制显示/隐藏状态 * 2. 本文件不含任何游戏专属逻辑,可用于任何基于 gameabc 的子游戏 * 3. 游戏层通过挂载方法(UIManager.xxx = function(){})扩展专属功能 * * 全局 UI 配置(由子游戏注入,框架不读取任何子游戏全局名): * 子游戏在初始化时通过 init(config) 或 configureGlobalUI(config) 传入精灵常量: * config.loadingUI = { LAYER, GROUP_ID, SPRITES: { BACKGROUND, LOADING_ICON, LOADING_TEXT } } * config.messageUI = { LAYER, GROUP_ID, SPRITES: { BACKGROUND, TEXT } } * config.confirmUI = { LAYER, GROUP_ID, SPRITES: { BACKGROUND, TITLE_TEXT, MESSAGE_TEXT, BTN_CONFIRM, BTN_CANCEL } } * 不传则跳过 Loading/Message/Confirm 全局 UI(场景/组件等其余功能不受影响)。 * * 依赖(必须在本文件之前加载): * EventBus (gameabc-framework/system/EventBus.js) * SpriteManager (gameabc-framework/core/SpriteManager.js) * * ============================================================================ * @guide 游戏侧扩展模式 * ============================================================================ * * 游戏层通过独立 JS 文件向 UIManager 挂载专属方法: * * // CreateRoomUI.js(示例) * (function() { * 'use strict'; * var CREATE_ROOM_UI = SpriteConstants.CREATE_ROOM_UI; * * UIManager.showCreateRoom = function(config) { * // 游戏专属逻辑... * SpriteManager.showGroup(CREATE_ROOM_UI.GROUP_ID); * }; * UIManager.hideCreateRoom = function() { * SpriteManager.hideGroup(CREATE_ROOM_UI.GROUP_ID); * }; * })(); * * ============================================================================ * @guide 场景管理完整用法 * ============================================================================ * * // 1. 初始化(在 UIBootstrap 中调用);若有全局 UI,注入其精灵常量(框架不读子游戏全局名): * UIManager.init({ loadingUI: SpriteConstants.LOADING_UI, messageUI: SpriteConstants.MESSAGE_UI, confirmUI: SpriteConstants.CONFIRM_UI }); * * // 2. 注册组件(BaseComponent 实例) * UIManager.registerComponent('RoomView', roomView); * UIManager.registerComponent('GameView', gameView); * * // 3. 注册场景(关联组件名列表) * UIManager.registerScene(UIManager.SCENES.ROOM, ['RoomView']); * UIManager.registerScene(UIManager.SCENES.GAME, ['GameView', 'JingCardView']); * * // 4. 切换场景(自动隐藏旧场景,显示新场景,发射 SCENE_CHANGED 事件) * UIManager.switchToScene(UIManager.SCENES.GAME); * * // 5. 全局 UI * UIManager.showLoading('正在加载...'); * UIManager.showMessage('操作成功', 2000); * UIManager.showConfirm('确认', '是否退出?', onOK, onCancel); * * @version 1.0.0 */ var UIManager = (function () { 'use strict'; // ==================== 私有变量 ==================== /** * 全局 UI 精灵常量(由子游戏注入:init(config) / configureGlobalUI) * 框架不读取任何子游戏全局名;未注入时三者为 null,对应全局 UI 不可用(优雅跳过)。 */ var LOADING_UI = null; var MESSAGE_UI = null; var CONFIRM_UI = null; /** * 已注册的 UI 组件 * { componentName: BaseComponent 实例 } */ var _components = {}; /** * 已注册的场景 * { sceneName: [componentName, ...] } */ var _scenes = {}; /** * 当前活跃场景名 * @type {string|null} */ var _currentScene = null; /** * 初始化状态 */ var _isInitialized = false; // ==================== 场景枚举 ==================== var SCENES = { ROOM: 'ROOM', GAME: 'GAME', LOADING: 'LOADING' }; // ==================== 私有方法 ==================== /** * 应用子游戏注入的全局 UI 配置(Loading/Message/Confirm),并隐藏所有全局弹层。 * 框架不认识任何子游戏全局名,配置由子游戏在 init()/configureGlobalUI() 时传入。 * @param {Object} config - { loadingUI, messageUI, confirmUI } * @returns {boolean} 是否成功配置(缺省或不完整则跳过,返回 false) * @private */ function _applyGlobalUIConfig(config) { if (!config || !config.loadingUI || !config.messageUI || !config.confirmUI) { console.warn('[UIManager] 未提供完整的全局 UI 配置(loadingUI/messageUI/confirmUI),跳过全局 UI 初始化'); return false; } LOADING_UI = config.loadingUI; MESSAGE_UI = config.messageUI; CONFIRM_UI = config.confirmUI; _initLoadingUI(); _initMessageUI(); _initConfirmUI(); return true; } /** @private */ function _initLoadingUI() { SpriteManager.hide(LOADING_UI.SPRITES.BACKGROUND); SpriteManager.hide(LOADING_UI.SPRITES.LOADING_ICON); SpriteManager.hide(LOADING_UI.SPRITES.LOADING_TEXT); SpriteManager.hideGroup(LOADING_UI.GROUP_ID); // console.log('[UIManager] Loading UI 初始化 (Layer ' + LOADING_UI.LAYER + ')'); } /** @private */ function _initMessageUI() { SpriteManager.hide(MESSAGE_UI.SPRITES.BACKGROUND); SpriteManager.hide(MESSAGE_UI.SPRITES.TEXT); SpriteManager.hideGroup(MESSAGE_UI.GROUP_ID); // console.log('[UIManager] Message UI 初始化 (Layer ' + MESSAGE_UI.LAYER + ')'); } /** @private */ function _initConfirmUI() { SpriteManager.hide(CONFIRM_UI.SPRITES.BACKGROUND); SpriteManager.hide(CONFIRM_UI.SPRITES.TITLE_TEXT); SpriteManager.hide(CONFIRM_UI.SPRITES.MESSAGE_TEXT); SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CONFIRM); SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CANCEL); SpriteManager.hideGroup(CONFIRM_UI.GROUP_ID); // console.log('[UIManager] Confirm UI 初始化 (Layer ' + CONFIRM_UI.LAYER + ')'); } /** * 隐藏当前场景所有可见组件 * @private */ function _hideCurrentScene() { if (!_currentScene || !_scenes[_currentScene]) { return; } var names = _scenes[_currentScene]; for (var i = 0; i < names.length; i++) { var c = _components[names[i]]; if (c && c.isVisible) { c.hide(); } } } /** * 显示指定场景的所有组件 * @private */ function _showScene(sceneName) { if (!_scenes[sceneName]) { console.error('[UIManager] 场景不存在:', sceneName); return; } var names = _scenes[sceneName]; for (var i = 0; i < names.length; i++) { var c = _components[names[i]]; if (c && !c.isVisible) { c.show(); } } } // ==================== 公共 API ==================== var publicAPI = { /** * 场景枚举常量 */ SCENES: SCENES, /** * 是否已初始化(外部只读) */ initialized: false, // ============================================================ // 初始化 / 销毁 // ============================================================ /** * 初始化 UIManager * @param {Object} [globalUIConfig] - 可选的全局 UI 配置 { loadingUI, messageUI, confirmUI }, * 由子游戏传入(如各自的精灵常量);框架不主动读取任何子游戏全局名。 * 不传则跳过 Loading/Message/Confirm 全局 UI(可后续用 configureGlobalUI 配置)。 * @returns {boolean} */ init: function (globalUIConfig) { if (_isInitialized) { console.warn('[UIManager] 已初始化,跳过'); return true; } // console.log('[UIManager] 初始化 (v1.0.0)'); try { _components = {}; _scenes = {}; _currentScene = null; // 全局 UI 为可选能力:未注入配置则跳过,不阻断 UIManager 初始化 if (globalUIConfig) { _applyGlobalUIConfig(globalUIConfig); } _isInitialized = true; this.initialized = true; // console.log('[UIManager] 初始化完成'); return true; } catch (err) { console.error('[UIManager] 初始化失败:', err); return false; } }, /** * 配置全局 UI(Loading/Message/Confirm)的精灵常量。供子游戏注入,可在 init 之后单独调用。 * @param {Object} config - { loadingUI, messageUI, confirmUI } * @returns {boolean} */ configureGlobalUI: function (config) { return _applyGlobalUIConfig(config); }, // ============================================================ // 组件管理 // ============================================================ /** * 注册 UI 组件 * @param {string} name - 组件名称 * @param {Object} component - BaseComponent 实例(必须已 init) * @returns {boolean} */ registerComponent: function (name, component) { if (!name || typeof name !== 'string') { console.error('[UIManager] registerComponent: name 必须是字符串'); return false; } if (!component || !component.isInitialized) { console.error('[UIManager] registerComponent: 组件未初始化:', name); return false; } if (_components[name]) { console.warn('[UIManager] registerComponent: 覆盖已存在组件:', name); } _components[name] = component; // console.log('[UIManager] 注册组件 "' + name + '"'); return true; }, /** * 获取已注册组件 * @param {string} name * @returns {Object|null} */ getComponent: function (name) { return _components[name] || null; }, /** * 注销组件(不销毁,只从注册表移除) * @param {string} name * @returns {boolean} */ unregisterComponent: function (name) { if (!_components[name]) { console.warn('[UIManager] unregisterComponent: 组件不存在:', name); return false; } var c = _components[name]; if (c.isVisible) { c.hide(); } delete _components[name]; // console.log('[UIManager] 注销组件 "' + name + '"'); return true; }, /** * 销毁组件(调用 component.destroy() 并移除) * @param {string} name * @returns {boolean} */ destroyComponent: function (name) { if (!_components[name]) { console.warn('[UIManager] destroyComponent: 组件不存在:', name); return false; } _components[name].destroy(); delete _components[name]; // console.log('[UIManager] 销毁组件 "' + name + '"'); return true; }, /** * 显示指定视图(便捷方法) * @param {string} name */ showView: function (name) { var c = _components[name]; if (!c) { console.warn('[UIManager] showView: 组件不存在:', name); return; } c.show(); }, /** * 隐藏指定视图(便捷方法) * @param {string} name */ hideView: function (name) { var c = _components[name]; if (!c) { console.warn('[UIManager] hideView: 组件不存在:', name); return; } c.hide(); }, // ============================================================ // 场景管理 // ============================================================ /** * 注册场景 * @param {string} sceneName * @param {string[]} componentNames - 该场景包含的组件名列表 * @returns {boolean} */ registerScene: function (sceneName, componentNames) { if (!sceneName || typeof sceneName !== 'string') { console.error('[UIManager] registerScene: sceneName 必须是字符串'); return false; } if (!Array.isArray(componentNames)) { console.error('[UIManager] registerScene: componentNames 必须是数组'); return false; } for (var i = 0; i < componentNames.length; i++) { if (!_components[componentNames[i]]) { console.error('[UIManager] registerScene: 组件不存在:', componentNames[i]); return false; } } _scenes[sceneName] = componentNames; // console.log('[UIManager] 注册场景 "' + sceneName + '" (' + componentNames.length + ' 个组件)'); return true; }, /** * 切换到指定场景 * 自动隐藏当前场景,显示新场景,发射 EventBus.Events.SCENE_CHANGED * @param {string} sceneName * @returns {boolean} */ switchToScene: function (sceneName) { if (!_scenes[sceneName]) { console.error('[UIManager] switchToScene: 场景不存在:', sceneName); return false; } if (_currentScene === sceneName) { return true; } // console.log('[UIManager] 切换场景 "' + (_currentScene || 'null') + '" → "' + sceneName + '"'); _hideCurrentScene(); _showScene(sceneName); var oldScene = _currentScene; _currentScene = sceneName; EventBus.emit(EventBus.Events.SCENE_CHANGED, { from: oldScene, to: sceneName }); return true; }, /** * 获取当前场景名 * @returns {string|null} */ getCurrentScene: function () { return _currentScene; }, // ============================================================ // 全局 UI - Loading // ============================================================ /** * 显示加载界面 * @param {string} [message='加载中...'] */ showLoading: function (message) { if (!LOADING_UI) { return; } message = message || '加载中...'; SpriteManager.setText(LOADING_UI.SPRITES.LOADING_TEXT, message); SpriteManager.show(LOADING_UI.SPRITES.BACKGROUND); SpriteManager.show(LOADING_UI.SPRITES.LOADING_ICON); SpriteManager.show(LOADING_UI.SPRITES.LOADING_TEXT); SpriteManager.showGroup(LOADING_UI.GROUP_ID); }, /** * 隐藏加载界面 */ hideLoading: function () { if (!LOADING_UI) { return; } SpriteManager.hide(LOADING_UI.SPRITES.BACKGROUND); SpriteManager.hide(LOADING_UI.SPRITES.LOADING_ICON); SpriteManager.hide(LOADING_UI.SPRITES.LOADING_TEXT); SpriteManager.hideGroup(LOADING_UI.GROUP_ID); }, // ============================================================ // 全局 UI - Message // ============================================================ /** * 显示轻提示(Toast,无精灵,仅 console) * @param {string} message */ showToast: function (message) { // console.log('[UIManager] Toast:', message); }, /** * 显示一个带标签的提示效果(Toast 形式,通用,不含任何玩法语义) * @param {string} tag - 效果标签(仅用于日志区分,由调用方自定义) * @param {string} title - 提示标题 * @param {string} [description] - 补充说明 */ showEffectToast: function (tag, title, description) { var msg = description ? (title + ':' + description) : title; // console.log('[UIManager] EffectToast [' + tag + ']:', msg); this.showToast(msg); }, /** * 显示消息提示(自动隐藏) * @param {string} message * @param {number} [duration=2000] 毫秒 */ showMessage: function (message, duration) { if (!MESSAGE_UI || !message) { return; } duration = duration || 2000; SpriteManager.setText(MESSAGE_UI.SPRITES.TEXT, message); SpriteManager.show(MESSAGE_UI.SPRITES.BACKGROUND); SpriteManager.show(MESSAGE_UI.SPRITES.TEXT); SpriteManager.showGroup(MESSAGE_UI.GROUP_ID); var self = this; setTimeout(function () { self.hideMessage(); }, duration); }, /** * 隐藏消息提示 */ hideMessage: function () { if (!MESSAGE_UI) { return; } SpriteManager.hide(MESSAGE_UI.SPRITES.BACKGROUND); SpriteManager.hide(MESSAGE_UI.SPRITES.TEXT); SpriteManager.hideGroup(MESSAGE_UI.GROUP_ID); }, // ============================================================ // 全局 UI - Confirm // ============================================================ /** * 显示确认对话框 * @param {string} title * @param {string} message * @param {Function} [onConfirm] * @param {Function} [onCancel] */ showConfirm: function (title, message, onConfirm, onCancel) { if (!CONFIRM_UI || !message) { return; } title = title || '确认'; SpriteManager.setText(CONFIRM_UI.SPRITES.TITLE_TEXT, title); SpriteManager.setText(CONFIRM_UI.SPRITES.MESSAGE_TEXT, message); SpriteManager.show(CONFIRM_UI.SPRITES.BACKGROUND); SpriteManager.show(CONFIRM_UI.SPRITES.TITLE_TEXT); SpriteManager.show(CONFIRM_UI.SPRITES.MESSAGE_TEXT); SpriteManager.show(CONFIRM_UI.SPRITES.BTN_CONFIRM); SpriteManager.show(CONFIRM_UI.SPRITES.BTN_CANCEL); SpriteManager.showGroup(CONFIRM_UI.GROUP_ID); // 回调通过全局变量传递(gameabc 引擎通过点击事件触发) window._confirmCallback = { onConfirm: onConfirm, onCancel: onCancel }; }, /** * 隐藏确认对话框 */ hideConfirm: function () { if (!CONFIRM_UI) { return; } SpriteManager.hide(CONFIRM_UI.SPRITES.BACKGROUND); SpriteManager.hide(CONFIRM_UI.SPRITES.TITLE_TEXT); SpriteManager.hide(CONFIRM_UI.SPRITES.MESSAGE_TEXT); SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CONFIRM); SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CANCEL); SpriteManager.hideGroup(CONFIRM_UI.GROUP_ID); if (window._confirmCallback) { delete window._confirmCallback; } }, // ============================================================ // 工具方法 // ============================================================ /** * 获取所有已注册组件名称 * @returns {string[]} */ getRegisteredComponents: function () { var names = []; for (var n in _components) { if (_components.hasOwnProperty(n)) { names.push(n); } } return names; }, /** * 获取所有已注册场景名称 * @returns {string[]} */ getRegisteredScenes: function () { var names = []; for (var n in _scenes) { if (_scenes.hasOwnProperty(n)) { names.push(n); } } return names; }, /** * 获取当前状态快照 * @returns {Object} */ getState: function () { return { isInitialized: _isInitialized, currentScene: _currentScene, componentCount: Object.keys(_components).length, sceneCount: Object.keys(_scenes).length, components: this.getRegisteredComponents(), scenes: this.getRegisteredScenes() }; }, /** * 打印状态(调试用) */ printState: function () { // console.log('[UIManager] 状态:', JSON.stringify(this.getState(), null, 2)); }, /** * 销毁 UIManager(销毁所有组件并重置状态) */ destroy: function () { // console.log('[UIManager] 开始销毁'); for (var name in _components) { if (_components.hasOwnProperty(name)) { this.destroyComponent(name); } } this.hideLoading(); this.hideMessage(); this.hideConfirm(); _components = {}; _scenes = {}; _currentScene = null; _isInitialized = false; this.initialized = false; // console.log('[UIManager] 销毁完成'); } }; return publicAPI; })(); if (typeof module !== 'undefined' && module.exports) { module.exports = UIManager; }