641 lines
21 KiB
JavaScript
641 lines
21 KiB
JavaScript
/**
|
||
* 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;
|
||
}
|