Files
erqiwang_youle/client/js/gameabc-framework/ui/BaseComponent.js
T
2026-08-11 22:17:54 +08:00

641 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;
}