初始化仓库:友乐/gameabc 房卡游戏平台脚手架(client + server + docs)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:13:26 +08:00
co-authored by Claude Opus 5
commit 594820d393
655 changed files with 310861 additions and 0 deletions
+607
View File
@@ -0,0 +1,607 @@
/**
* ============================================================================
* 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;
}