初始化仓库:友乐/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
@@ -0,0 +1,640 @@
/**
* BaseComponent - UI组件基类
*
* 职责:
* 1. 提供统一的组件生命周期管理 (init/show/hide/destroy)
* 2. 自动管理EventBus事件监听的注册和注销
* 3. 自动管理组件内精灵的创建和销毁
* 4. 提供钩子方法供子类覆盖
*
* 使用方式:
* ```javascript
* var MyComponent = Object.create(BaseComponent);
*
* MyComponent.init = function(config) {
* this.layer = config.layer;
* // 创建精灵
* var spriteId = SpriteManager.createSprite({...});
* this.addSprite(spriteId);
* // 注册事件
* this.addEventListener('eventName', this.onEvent);
* };
*
* var instance = BaseComponent.create({ name: 'MyComponent', layer: 101 });
* instance.show();
* ```
*
* 依赖:
* - EventBus (必须先加载)
* - SpriteManager (必须先加载)
*
* ============================================================================
* @guide 开发指南 - BaseComponent 两种继承模式
* ============================================================================
*
* 【模式一:Object.create(推荐用于复杂视图,多图层组件)】
*
* var GameView = Object.create(BaseComponent);
*
* GameView.init = function(config) {
* // ⚠️ 必须手动重新声明所有实例属性(避免原型污染)
* this.sprites = [];
* this.eventListeners = {};
* this.isVisible = false;
* this.isInitialized = false;
* this.isDestroyed = false;
*
* this.name = config.name || 'GameView';
* this.layer = config.layer || 102; // BaseComponent.show() 需要此属性
* this.baseLayers = [102, 103, 104, 105]; // 多图层组件:show/hide 会操作所有图层
*
* // 注册 EventBus 监听(使用 this.addEventListener 自动管理清理)
* var self = this;
* this.addEventListener(EventBus.Events.GAME_STARTED, function(data) {
* self._onGameStarted(data);
* });
*
* // 注册精灵交互事件(通过 SpriteEventController)
* SpriteEventController.registerMouseUp(sprites.CLOSE_BTN, function(event) {
* self.hide();
* });
*
* this.isInitialized = true;
* };
*
* // 在 UIBootstrap 中创建并使用(注意:直接调用 init,不用 create)
* GameView.init({ name: 'GameView', layer: 102 });
*
* 【模式二:BaseComponent.create(推荐用于简单单图层组件)】
*
* // create 内部会调用 Object.create(BaseComponent) + 调用 init
* var myComp = BaseComponent.create({ name: 'MyComp', layer: 101 });
* myComp.show();
*
* 【何时用 Object.create vs BaseComponent.create】
* - 需要多图层(baseLayers) → Object.create
* - 有大量自定义数据属性(this.data = {}) → Object.create
* - 需要自定义 show/hide 逻辑 → Object.create
* - 简单显隐控制,无额外逻辑 → BaseComponent.create
*
* 【生命周期钩子】
* GameView.onShow = function() { ... }; // show() 时调用
* GameView.onHide = function() { ... }; // hide() 时调用
* GameView.onDestroy = function() { ... }; // destroy() 时调用(清理timer等)
*
* 【destroy 行为说明】
* - 自动调用 EventBus.off 清理所有通过 addEventListener 注册的监听
* - 精灵只隐藏(hide),不删除(gameabc 不支持运行时删除静态精灵)
* - 动态复制的精灵(SpriteCopyUtils)需在 onDestroy 中手动 remove
*
* 【模式三:IIFE 包裹 + 自定义 create 工厂(RoomView 模式)】
*
* var RoomView = (function() {
* 'use strict';
* var RoomView = Object.create(BaseComponent);
*
* RoomView.init = function(config) {
* // 同样需要手动重声明实例属性
* this.sprites = [];
* this.eventListeners = {};
* this.isVisible = false;
* this.isInitialized = false;
* this.isDestroyed = false;
* this.name = config.name;
* this.layer = config.layer;
* };
*
* // 自定义工厂方法(代替 BaseComponent.create,用于多步配置前创建实例)
* RoomView.create = function(config) {
* var instance = Object.create(RoomView);
* config = config || {};
* config.layer = ROOM_VIEW.LAYER; // 固定图层,外部无需传
* // 显式调用 BaseComponent._init,内部会自动调用 instance.init(config)
* BaseComponent._init.call(instance, config);
* return instance;
* };
*
* return RoomView;
* })();
*
* // 使用:通过工厂创建实例
* var roomView = RoomView.create({ name: 'RoomView' });
* roomView.show();
*
* 【super 方法调用(扩展父类方法时)】
*
* RoomView.getState = function() {
* // 调用 BaseComponent.getState 获取基础状态,再添加子类特有字段
* var state = BaseComponent.getState.call(this);
* state.hasRoomData = !!this.roomData;
* state.playerCount = this._getPlayerCount();
* return state;
* };
*
* 【show/hide 钩子(GameView 中使用的扩展钩子)】
*
* // 除了 onShow/onHide/onDestroy,GameView 还使用了更细粒度的钩子:
* GameView.onBeforeShow = function() { ... }; // show() 执行群组操作之前
* GameView.onAfterShow = function() { ... }; // show() 执行群组操作之后
* GameView.onBeforeHide = function() { ... }; // hide() 执行群组操作之前
* GameView.onAfterHide = function() { ... }; // hide() 执行群组操作之后
* // 这些钩子在 show()/hide() 内部通过 if (this.onAfterShow) this.onAfterShow() 调用
*
* @version 1.0.0
* @date 2025-11-19
*/
var BaseComponent = (function() {
'use strict';
// ==================== 组件基类定义 ====================
var BaseComponentProto = {
/**
* 组件名称
* @type {String}
*/
name: '',
/**
* 组件所在图层
* @type {Number}
*/
layer: 0,
/**
* 组件拥有的精灵ID列表
* @type {Array<Number>}
*/
sprites: null,
/**
* 组件注册的事件监听器
* 结构: { eventName: wrappedHandler }
* @type {Object}
*/
eventListeners: null,
/**
* 组件是否可见
* @type {Boolean}
*/
isVisible: false,
/**
* 组件是否已初始化
* @type {Boolean}
*/
isInitialized: false,
/**
* 组件是否已销毁
* @type {Boolean}
*/
isDestroyed: false,
/**
* 组件配置
* @type {Object}
*/
config: null,
// ==================== 内部方法 ====================
/**
* 内部初始化方法
* @param {Object} config - 配置对象
* @param {String} config.name - 组件名称
* @param {Number} config.layer - 图层ID
* @private
*/
_init: function(config) {
if (this.isInitialized) {
console.warn('⚠️ BaseComponent: 组件已初始化,跳过重复初始化:', this.name);
return;
}
// 验证必需参数
if (!config || typeof config !== 'object') {
console.error('❌ BaseComponent._init: config必须是对象');
return;
}
if (!config.name) {
console.error('❌ BaseComponent._init: 缺少name参数');
return;
}
if (typeof config.layer !== 'number') {
console.error('❌ BaseComponent._init: 缺少layer参数或类型错误');
return;
}
// 初始化属性
this.name = config.name;
this.layer = config.layer;
this.config = config;
this.sprites = [];
this.eventListeners = {};
this.isVisible = false;
this.isDestroyed = false;
this.isInitialized = true;
console.log('🎨 BaseComponent: 初始化组件 "' + this.name + '" (图层: ' + this.layer + ')');
// 调用子类的init方法
try {
this.init(config);
} catch (error) {
console.error('❌ BaseComponent: 组件初始化时发生异常:', error);
this.isInitialized = false;
}
},
/**
* 移除所有事件监听器
* @private
*/
_removeAllEventListeners: function() {
for (var eventName in this.eventListeners) {
if (this.eventListeners.hasOwnProperty(eventName)) {
this.removeEventListener(eventName);
}
}
},
/**
* 隐藏所有精灵
* 注意: 精灵由编辑器创建,组件销毁时只隐藏精灵,不删除精灵
* @private
*/
_hideAllSprites: function() {
if (!this.sprites || this.sprites.length === 0) {
return;
}
console.log('🙈 BaseComponent: 隐藏组件 "' + this.name + '" 的所有精灵 (数量: ' + this.sprites.length + ')');
for (var i = 0; i < this.sprites.length; i++) {
var spriteId = this.sprites[i];
SpriteManager.hide(spriteId);
}
this.sprites = [];
},
// ==================== 生命周期方法 ====================
/**
* 初始化方法 - 子类覆盖
* 在此方法中创建精灵、注册事件监听
* @param {Object} config - 配置对象
*/
init: function(config) {
// 子类覆盖此方法
console.log('📝 BaseComponent.init: 请在子类中覆盖此方法');
},
/**
* 显示组件
* @returns {Boolean} 是否成功
*/
show: function() {
if (this.isDestroyed) {
console.error('❌ BaseComponent.show: 组件已销毁,无法显示:', this.name);
return false;
}
if (!this.isInitialized) {
console.error('❌ BaseComponent.show: 组件未初始化,无法显示:', this.name);
return false;
}
if (this.isVisible) {
console.log('ℹ️ BaseComponent.show: 组件已可见,跳过:', this.name);
return true;
}
console.log('👁️ BaseComponent: 显示组件 "' + this.name + '" (图层: ' + this.layer + ')');
// 显示图层
SpriteManager.showGroup(this.layer);
this.isVisible = true;
// 调用钩子方法
try {
this.onShow();
} catch (error) {
console.error('❌ BaseComponent.show: onShow钩子执行时发生异常:', error);
}
return true;
},
/**
* 隐藏组件
* @returns {Boolean} 是否成功
*/
hide: function() {
if (this.isDestroyed) {
console.error('❌ BaseComponent.hide: 组件已销毁:', this.name);
return false;
}
if (!this.isVisible) {
console.log('ℹ️ BaseComponent.hide: 组件已隐藏,跳过:', this.name);
return true;
}
console.log('🙈 BaseComponent: 隐藏组件 "' + this.name + '" (图层: ' + this.layer + ')');
// 隐藏图层
SpriteManager.hideGroup(this.layer);
this.isVisible = false;
// 调用钩子方法
try {
this.onHide();
} catch (error) {
console.error('❌ BaseComponent.hide: onHide钩子执行时发生异常:', error);
}
return true;
},
/**
* 销毁组件
* @returns {Boolean} 是否成功
*/
destroy: function() {
if (this.isDestroyed) {
console.warn('⚠️ BaseComponent.destroy: 组件已销毁,跳过:', this.name);
return false;
}
console.log('💥 BaseComponent: 销毁组件 "' + this.name + '"');
// 移除所有事件监听
this._removeAllEventListeners();
// 隐藏所有精灵(精灵由编辑器创建,不删除)
this._hideAllSprites();
// 隐藏图层
if (this.isVisible) {
SpriteManager.hideGroup(this.layer);
}
// 调用钩子方法
try {
this.onDestroy();
} catch (error) {
console.error('❌ BaseComponent.destroy: onDestroy钩子执行时发生异常:', error);
}
// 标记为已销毁
this.isDestroyed = true;
this.isVisible = false;
this.isInitialized = false;
return true;
},
// ==================== 事件监听管理 ====================
/**
* 添加事件监听器
* @param {String} eventName - 事件名称
* @param {Function} handler - 事件处理函数
* @returns {Boolean} 是否成功
*/
addEventListener: function(eventName, handler) {
if (!eventName || typeof eventName !== 'string') {
console.error('❌ BaseComponent.addEventListener: eventName必须是字符串');
return false;
}
if (typeof handler !== 'function') {
console.error('❌ BaseComponent.addEventListener: handler必须是函数');
return false;
}
if (this.eventListeners[eventName]) {
console.warn('⚠️ BaseComponent.addEventListener: 事件已注册,将覆盖:', eventName);
this.removeEventListener(eventName);
}
// 包装处理函数,绑定this上下文
var self = this;
var wrappedHandler = function(data) {
if (self.isDestroyed) {
console.warn('⚠️ BaseComponent: 组件已销毁,忽略事件:', eventName);
return;
}
try {
handler.call(self, data);
} catch (error) {
console.error('❌ BaseComponent: 事件处理函数执行异常:', eventName, error);
}
};
// 注册到EventBus
EventBus.on(eventName, wrappedHandler);
// 记录监听器
this.eventListeners[eventName] = wrappedHandler;
console.log('📡 BaseComponent: 组件 "' + this.name + '" 注册事件:', eventName);
return true;
},
/**
* 移除事件监听器
* @param {String} eventName - 事件名称
* @returns {Boolean} 是否成功
*/
removeEventListener: function(eventName) {
if (!this.eventListeners[eventName]) {
console.warn('⚠️ BaseComponent.removeEventListener: 事件未注册:', eventName);
return false;
}
var handler = this.eventListeners[eventName];
EventBus.off(eventName, handler);
delete this.eventListeners[eventName];
console.log('📴 BaseComponent: 组件 "' + this.name + '" 移除事件:', eventName);
return true;
},
/**
* 触发事件(发送到EventBus)
* @param {String} eventName - 事件名称
* @param {*} data - 事件数据
*/
emit: function(eventName, data) {
if (!eventName || typeof eventName !== 'string') {
console.error('❌ BaseComponent.emit: eventName必须是字符串');
return;
}
console.log('📤 BaseComponent: 组件 "' + this.name + '" 触发事件:', eventName);
EventBus.emit(eventName, data);
},
// ==================== 精灵管理 ====================
/**
* 添加精灵到组件管理列表
* @param {Number} spriteId - 精灵ID
* @returns {Boolean} 是否成功
*/
addSprite: function(spriteId) {
if (typeof spriteId !== 'number') {
console.error('❌ BaseComponent.addSprite: spriteId必须是数字');
return false;
}
if (!SpriteManager.exists(spriteId)) {
console.error('❌ BaseComponent.addSprite: 精灵不存在:', spriteId);
return false;
}
if (this.sprites.indexOf(spriteId) !== -1) {
console.warn('⚠️ BaseComponent.addSprite: 精灵已在管理列表中:', spriteId);
return false;
}
this.sprites.push(spriteId);
// console.log('➕ BaseComponent: 组件 "' + this.name + '" 添加精灵:', spriteId);
return true;
},
/**
* 从组件管理列表移除精灵(不销毁)
* @param {Number} spriteId - 精灵ID
* @returns {Boolean} 是否成功
*/
removeSprite: function(spriteId) {
var index = this.sprites.indexOf(spriteId);
if (index === -1) {
console.warn('⚠️ BaseComponent.removeSprite: 精灵不在管理列表中:', spriteId);
return false;
}
this.sprites.splice(index, 1);
console.log('➖ BaseComponent: 组件 "' + this.name + '" 移除精灵:', spriteId);
return true;
},
/**
* 获取组件管理的所有精灵ID
* @returns {Array<Number>} 精灵ID数组
*/
getSprites: function() {
return this.sprites.slice(); // 返回副本
},
/**
* 获取组件管理的精灵数量
* @returns {Number} 精灵数量
*/
getSpriteCount: function() {
return this.sprites.length;
},
// ==================== 钩子方法 ====================
/**
* 显示时的钩子方法 - 子类可覆盖
*/
onShow: function() {
// 子类可以覆盖此方法
},
/**
* 隐藏时的钩子方法 - 子类可覆盖
*/
onHide: function() {
// 子类可以覆盖此方法
},
/**
* 销毁时的钩子方法 - 子类可覆盖
*/
onDestroy: function() {
// 子类可以覆盖此方法
},
// ==================== 工具方法 ====================
/**
* 获取组件状态信息
* @returns {Object} 状态对象
*/
getState: function() {
return {
name: this.name,
layer: this.layer,
isVisible: this.isVisible,
isInitialized: this.isInitialized,
isDestroyed: this.isDestroyed,
spriteCount: this.sprites.length,
eventCount: Object.keys(this.eventListeners).length
};
},
/**
* 打印组件状态(调试用)
*/
printState: function() {
var state = this.getState();
console.log('📊 组件状态:', JSON.stringify(state, null, 2));
}
};
// ==================== 工厂方法 ====================
/**
* 创建组件实例
* @param {Object} config - 配置对象
* @param {String} config.name - 组件名称
* @param {Number} config.layer - 图层ID
* @returns {Object} 组件实例
*/
BaseComponentProto.create = function(config) {
if (!config || typeof config !== 'object') {
console.error('❌ BaseComponent.create: config必须是对象');
return null;
}
// 创建实例
var instance = Object.create(this);
// 初始化实例
instance._init(config);
if (!instance.isInitialized) {
console.error('❌ BaseComponent.create: 组件初始化失败');
return null;
}
return instance;
};
// 返回基类原型
return BaseComponentProto;
})();
// 导出到全局(兼容ES5)
if (typeof module !== 'undefined' && module.exports) {
module.exports = BaseComponent;
}