初始化仓库:友乐/gameabc 房卡游戏平台脚手架(client + server + docs)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* 事件总线 (独立模块)
|
||||
* 职责: 提供模块间的事件通信机制,解耦模块依赖
|
||||
* 遵循原则: 发布-订阅模式,支持事件监听和触发
|
||||
*
|
||||
* ============================================================================
|
||||
* @guide 开发指南 - EventBus 使用规范
|
||||
* ============================================================================
|
||||
*
|
||||
* 【事件命名规范】
|
||||
* 使用 "模块:动作" 两段式命名,所有名称定义在 EventBus.Events 常量中:
|
||||
* 'game:started' 游戏开始
|
||||
* 'game:finished' 游戏结束
|
||||
* 'player:discarded' 玩家出牌
|
||||
* 说明:框架只内置通用事件(game:/player:/room:/ui:/network: 等);具体玩法的
|
||||
* 专属事件(如发牌/摸牌/吃碰杠)由子游戏自行追加到 EventBus.Events,框架不内置。
|
||||
* ⚠️ 不要硬编码字符串,统一用 EventBus.Events.XXX 引用
|
||||
*
|
||||
* 【订阅事件(在组件 init 中注册)】
|
||||
* // 在 BaseComponent.init 或 Object.create 的 init 方法中:
|
||||
* var self = this;
|
||||
* var unsubscribe = EventBus.on(EventBus.Events.GAME_STARTED, function(data) {
|
||||
* self._onGameStarted(data);
|
||||
* }, self);
|
||||
* // 保存引用用于 destroy 时清理
|
||||
* this.eventListeners['game:started'] = unsubscribe;
|
||||
*
|
||||
* 【发布事件】
|
||||
* // 在控制器或业务逻辑中发布:
|
||||
* EventBus.emit(EventBus.Events.GAME_STARTED, { playerCount: 4 });
|
||||
* EventBus.emit('player:discarded', { seat: 0, card: cardData });
|
||||
*
|
||||
* 【一次性事件(触发后自动取消)】
|
||||
* EventBus.once(EventBus.Events.GAME_FINISHED, function(data) {
|
||||
* self._showSettleUI(data);
|
||||
* });
|
||||
*
|
||||
* 【在 destroy 中清理(防内存泄漏)】
|
||||
* // 如果用 BaseComponent 的 addEventListener,destroy 时自动清理
|
||||
* // 如果手动保存了 unsubscribe 函数:
|
||||
* if (this.eventListeners['game:started']) {
|
||||
* this.eventListeners['game:started'](); // 调用取消函数
|
||||
* }
|
||||
* // 或者 BaseComponent.destroy() 会自动处理 this.eventListeners 中的所有项
|
||||
*
|
||||
* 【推荐:通过 BaseComponent.addEventListener 代替手动管理】
|
||||
* // BaseComponent 子类中:
|
||||
* this.addEventListener(EventBus.Events.PLAYER_DISCARDED, function(data) {
|
||||
* self._onPlayerDiscarded(data);
|
||||
* });
|
||||
* // destroy() 时 BaseComponent 会自动调用所有 off
|
||||
*/
|
||||
var EventBus = {
|
||||
/**
|
||||
* 私有数据存储
|
||||
* @private
|
||||
*/
|
||||
_listeners: {}, // 事件监听器列表 {eventName: [callbacks]}
|
||||
|
||||
/**
|
||||
* 订阅事件
|
||||
* @param {String} eventName - 事件名称
|
||||
* @param {Function} callback - 回调函数
|
||||
* @param {Object} context - 回调函数的上下文(this)
|
||||
* @return {Function} 取消订阅函数
|
||||
*/
|
||||
on: function(eventName, callback, context) {
|
||||
if (!eventName || typeof callback !== 'function') {
|
||||
console.error('❌ EventBus.on: 无效的参数');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this._listeners[eventName]) {
|
||||
this._listeners[eventName] = [];
|
||||
}
|
||||
|
||||
var listener = {
|
||||
callback: callback,
|
||||
context: context || null
|
||||
};
|
||||
|
||||
this._listeners[eventName].push(listener);
|
||||
console.log('✅ EventBus: 订阅事件', eventName);
|
||||
|
||||
// 返回取消订阅函数
|
||||
var self = this;
|
||||
return function() {
|
||||
self.off(eventName, callback);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 订阅一次性事件(触发后自动取消订阅)
|
||||
* @param {String} eventName - 事件名称
|
||||
* @param {Function} callback - 回调函数
|
||||
* @param {Object} context - 回调函数的上下文
|
||||
* @return {Function} 取消订阅函数
|
||||
*/
|
||||
once: function(eventName, callback, context) {
|
||||
var self = this;
|
||||
|
||||
var onceCallback = function() {
|
||||
callback.apply(context || null, arguments);
|
||||
self.off(eventName, onceCallback);
|
||||
};
|
||||
|
||||
return this.on(eventName, onceCallback, context);
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消订阅事件
|
||||
* @param {String} eventName - 事件名称
|
||||
* @param {Function} callback - 要取消的回调函数(可选,不传则取消所有)
|
||||
* @return {Boolean} 是否取消成功
|
||||
*/
|
||||
off: function(eventName, callback) {
|
||||
if (!this._listeners[eventName]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果没有指定callback,取消该事件的所有监听器
|
||||
if (!callback) {
|
||||
delete this._listeners[eventName];
|
||||
console.log('✅ EventBus: 取消所有', eventName, '监听器');
|
||||
return true;
|
||||
}
|
||||
|
||||
// 取消指定的callback
|
||||
var listeners = this._listeners[eventName];
|
||||
for (var i = listeners.length - 1; i >= 0; i--) {
|
||||
if (listeners[i].callback === callback) {
|
||||
listeners.splice(i, 1);
|
||||
console.log('✅ EventBus: 取消', eventName, '监听器');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param {String} eventName - 事件名称
|
||||
* @param {*} data - 事件数据(可选)
|
||||
* @return {Number} 触发的监听器数量
|
||||
*/
|
||||
emit: function(eventName, data) {
|
||||
if (!this._listeners[eventName]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var listeners = this._listeners[eventName].slice(); // 复制数组,避免迭代中修改
|
||||
var count = 0;
|
||||
|
||||
console.log('📡 EventBus: 触发事件', eventName, data);
|
||||
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
try {
|
||||
var listener = listeners[i];
|
||||
listener.callback.call(listener.context, data);
|
||||
count++;
|
||||
} catch (error) {
|
||||
console.error('❌ EventBus: 事件回调异常', eventName, error);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查事件是否有监听器
|
||||
* @param {String} eventName - 事件名称
|
||||
* @return {Boolean} 是否有监听器
|
||||
*/
|
||||
hasListener: function(eventName) {
|
||||
return !!(this._listeners[eventName] && this._listeners[eventName].length > 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取事件的监听器数量
|
||||
* @param {String} eventName - 事件名称
|
||||
* @return {Number} 监听器数量
|
||||
*/
|
||||
getListenerCount: function(eventName) {
|
||||
return this._listeners[eventName] ? this._listeners[eventName].length : 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取所有事件名称
|
||||
* @return {Array} 事件名称数组
|
||||
*/
|
||||
getEventNames: function() {
|
||||
var names = [];
|
||||
for (var name in this._listeners) {
|
||||
if (this._listeners.hasOwnProperty(name)) {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空所有事件监听器
|
||||
*/
|
||||
clear: function() {
|
||||
this._listeners = {};
|
||||
console.log('✅ EventBus: 已清空所有监听器');
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空指定事件的所有监听器
|
||||
* @param {String} eventName - 事件名称
|
||||
*/
|
||||
clearEvent: function(eventName) {
|
||||
if (this._listeners[eventName]) {
|
||||
delete this._listeners[eventName];
|
||||
console.log('✅ EventBus: 已清空', eventName, '的所有监听器');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 事件名称常量容器(框架不预置任何具体事件名)
|
||||
*
|
||||
* 框架保持「游戏中立」:只提供发布-订阅机制与一个空的 EventBus.Events 容器,
|
||||
* 不预定义任何事件名称常量。各子游戏在自身事件常量文件(如
|
||||
* game/constants/GameEvents.js)中向 EventBus.Events 注册自己用到的【全部】事件,
|
||||
* 业务侧统一通过 EventBus.Events.XXX 引用。
|
||||
*
|
||||
* 命名规范:两段式 "模块:动作"。例如通用语义 'game:started' / 'player:discarded' /
|
||||
* 'room:updated' / 'ui:sceneChanged' / 'network:connected';玩法专属自定前缀(如
|
||||
* 'mahjong:tilesDealt')。统一用常量引用,不在业务中硬编码字符串。
|
||||
*/
|
||||
EventBus.Events = {};
|
||||
Reference in New Issue
Block a user