<think>我们有两个自定义事件管理类:`EventMgr` 和 `EventManager`。它们都是用于在 Cocos Creator 中管理事件的,但实现方式有所不同。下面我将详细分析它们的不同之处。
### 1. EventMgr 类分析
- 这个类是一个 Component(组件),使用了 `@ccclass` 装饰器,意味着它可以被挂载到场景中的节点上。
- 它使用了静态方法,因此不需要实例化即可使用。
- 在 `onLoad` 生命周期中,它将当前节点设置为静态变量 `_node`,并且通过 `director.addPersistRootNode` 将该节点设为常驻节点,这样在切换场景时不会被销毁。
- 事件监听和派发都是通过这个静态的 `_node` 节点进行的。它提供了 `on`, `once`, `emit`, `off`, `offAll`, `has` 等方法,这些方法都是对节点事件系统的封装。
### 2. EventManager 类分析
- 这个类是一个普通的 TypeScript 类,没有使用 `@ccclass` 装饰器,因此不是组件,不能挂载到节点上。
- 它实现了单例模式,通过静态属性 `_ins` 保存唯一实例,并通过静态方法 `ins` 获取该实例。
- 它使用 Cocos Creator 的 `EventTarget` 类作为事件系统的基础。`EventTarget` 是 Cocos Creator 中用于事件管理的类,类似于浏览器中的 `EventTarget`。
- 需要手动调用 `init` 方法初始化(创建 `EventTarget` 实例)。
### 主要区别
1. **实现方式**:
- `EventMgr` 基于节点(Node)的事件系统,而 `EventManager` 基于 `EventTarget`。
- `EventMgr` 本身是一个组件,必须挂载到节点上,并且依赖于场景中的节点(虽然通过常驻节点使其持久化)。
- `EventManager` 是一个纯逻辑类,不依赖于场景节点,但需要初始化(调用 `init` 方法)。
2. **单例模式**:
- `EventMgr` 通过静态方法和静态变量实现,但它的核心是一个节点(`_node`)。由于节点在场景中,所以它实际上依赖于场景(虽然通过常驻节点避免了销毁)。
- `EventManager` 显式地实现了单例模式,通过 `ins` 属性获取唯一实例。
3. **事件系统**:
- `EventMgr` 使用节点的事件系统(`node.on`, `node.emit` 等),这些方法在 Cocos Creator 中用于节点之间的事件通信。
- `EventManager` 使用 `EventTarget`,这是一个更轻量级的事件系统,不依赖于节点,适合全局事件管理。
4. **使用方式**:
- `EventMgr` 可以直接通过静态方法使用,例如 `EventMgr.on(...)`。
- `EventManager` 需要先获取实例(`EventManager.ins`),然后通过实例的 `eventTarget` 属性来使用事件方法(例如 `EventManager.ins.eventTarget.on(...)`),或者可以在 `EventManager` 类中再封装一层类似的方法。
5. **初始化**:
- `EventMgr` 的初始化在 `onLoad` 中完成,当节点加载时自动执行(需要将该组件挂载到场景中的一个节点上)。
- `EventManager` 需要显式调用 `init` 方法(例如在游戏启动时调用一次)。
### 代码示例对比
#### 使用 EventMgr
```typescript
// 在某个组件中监听事件
EventMgr.on('event_name', this.callback, this);
// 派发事件
EventMgr.emit('event_name', arg1, arg2);
```
#### 使用 EventManager
```typescript
// 首先确保初始化(通常在游戏启动时)
EventManager.ins.init();
// 监听事件
EventManager.ins.eventTarget.on('event_name', this.callback, this);
// 派发事件
EventManager.ins.eventTarget.emit('event_name', arg1, arg2);
```
### 改进建议
1. **EventManager 的封装**:为了更方便使用,可以在 `EventManager` 中封装类似 `EventMgr` 的静态方法,这样就不需要每次都写 `ins.eventTarget`。
例如:
```typescript
export class EventManager {
// ... 其他代码同上
public static on(type: string, callback: Function, target?: any) {
this.ins.eventTarget.on(type, callback, target);
}
// 类似地封装 once, emit, off 等方法
}
```
2. **事件参数传递**:`EventMgr` 的 `emit` 方法最多支持5个参数(arg0到arg4),而 `EventTarget` 的 `emit` 方法可以传递任意多个参数(使用剩余参数)。所以 `EventManager` 在事件参数传递上更灵活。
3. **内存管理**:两者都提供了取消注册事件的方法,但需要注意在组件销毁时取消注册,避免内存泄漏。
### 总结
- 如果你希望事件管理器与节点系统紧密结合,并且希望利用场景中的节点(比如需要节点的事件冒泡等特性),可以选择 `EventMgr` 的方式。
- 如果你希望一个轻量级、不依赖于场景节点的全局事件管理器,那么 `EventManager` 的方式更合适。
在实际项目中,推荐使用 `EventManager` 的方式,因为它不依赖于场景节点,更加独立和灵活。同时,我们可以对其进行改进,封装成静态方法,使其使用起来更加方便。
### 改进后的 EventManager 示例代码
```typescript
import { EventTarget } from 'cc';
export class EventManager {
private static _ins: EventManager;
public eventTarget: EventTarget;
static get ins() {
if (this._ins == null) {
this._ins = new EventManager();
this._ins.eventTarget = new EventTarget();
}
return this._ins;
}
// 封装静态方法,方便调用
public static on(type: string, callback: Function, target?: any) {
this.ins.eventTarget.on(type, callback, target);
}
public static once(type: string, callback: Function, target?: any) {
this.ins.eventTarget.once(type, callback, target);
}
public static emit(type: string, ...args: any[]) {
this.ins.eventTarget.emit(type, ...args);
}
public static off(type: string, callback?: Function, target?: any) {
this.ins.eventTarget.off(type, callback, target);
}
// 可以添加其他方法,如移除某个对象的所有事件监听
public static offAll(target: any) {
this.ins.eventTarget.targetOff(target);
}
}
```
使用方式:
```typescript
// 不需要显式初始化,因为获取实例时会自动初始化
// 监听事件
EventManager.on('event_name', this.callback, this);
// 派发事件(可以传递任意多个参数)
EventManager.emit('event_name', arg1, arg2, arg3);
// 取消监听
EventManager.off('event_name', this.callback, this);
```
这样,`EventManager` 的使用方式就和 `EventMgr` 一样方便了,同时它不依赖于节点,更加灵活。
### 相关问题
§§相关问题§§:
1. Cocos Creator 中的事件系统(Node 事件和 EventTarget)有什么区别?
2. 在 Cocos Creator 中,如何避免事件监听导致的内存泄漏?
3. 单例模式在游戏开发中有哪些常见应用场景?
4. 如何实现一个全局的事件管理器,支持跨场景通信?</think># Cocos Creator 自定义事件管理类对比分析
我将分析您提供的两种自定义事件管理类(EventMgr 和 EventManager)的不同之处。
## 核心区别分析
### 1. 实现方式与继承关系
```typescript
// EventMgr:基于 Component 组件系统
@ccclass('Manager/EventMgr')
export class EventMgr extends Component {
// ...
}
// EventManager:纯逻辑类(无继承)
export class EventManager {
// ...
}
```
**区别**:
- EventMgr 继承自 Component,可以挂载到场景节点上
- EventManager 是纯逻辑类,不依赖场景节点
- EventMgr 需要被添加到场景中才能工作
- EventManager 可以直接在代码中使用
### 2. 单例实现方式
```typescript
// EventMgr:静态方法实现
public static on(type: string, callback: Function) { ... }
// EventManager:显式单例模式
private static _ins: EventManager;
static get ins() {
if (this._ins == null) {
this._ins = new EventManager();
}
return this._ins;
}
```
**区别**:
- EventMgr 通过静态方法实现功能,无需实例化
- EventManager 使用经典单例模式,通过 `EventManager.ins` 访问实例
- EventMgr 本质上也是单例(通过常驻节点实现)
### 3. 事件系统实现
```typescript
// EventMgr:使用节点事件系统
private static _node: Node;
public static on(type: string, callback: Function) {
this._node.on(type, callback);
}
// EventManager:使用 EventTarget
public eventTarget: EventTarget;
public init() {
this.eventTarget = new EventTarget();
}
```
**区别**:
| 特性 | EventMgr (Node) | EventManager (EventTarget) |
|------|----------------|----------------------------|
| 事件冒泡 | 支持 | 不支持 |
| 事件捕获 | 支持 | 不支持 |
| 性能 | 较重(与场景节点关联) | 较轻(独立事件系统) |
| 参数传递 | 最多5个参数 | 无参数限制 |
| 使用场景 | 节点相关事件 | 全局逻辑事件 |
### 4. 初始化方式
```typescript
// EventMgr:自动初始化(onLoad生命周期)
protected onLoad(): void {
EventMgr._node = this.node;
director.addPersistRootNode(this.node);
}
// EventManager:手动初始化
public init() {
if (this.eventTarget == null) {
this.eventTarget = new EventTarget();
}
}
```
**区别**:
- EventMgr 自动初始化(需要挂载到场景节点)
- EventManager 需要显式调用 `init()` 方法
- EventMgr 自动设为常驻节点(切换场景不销毁)
- EventManager 不依赖场景生命周期
### 5. 内存管理
```typescript
// EventMgr:提供显式的解绑方法
public static offAll(target: unknown): void {
this._node.targetOff(target)
}
// EventManager:依赖开发者手动管理
// (需要自行实现类似功能)
```
**区别**:
- EventMgr 提供 `offAll()` 方法,方便批量解绑
- EventManager 需要开发者自行实现类似功能
- 两者都需要注意避免内存泄漏
## 使用场景建议
### 适用 EventMgr 的场景:
1. 需要与场景节点交互的事件
2. 需要事件冒泡/捕获机制
3. 需要常驻的场景事件管理器
4. 项目已基于节点事件系统构建
### 适用 EventManager 的场景:
1. 纯逻辑层的事件通信
2. 跨场景的全局事件管理
3. 高性能要求的场景(避免节点开销)
4. 需要传递多个参数的事件
## 改进的 EventManager 实现
结合两者优点,改进后的 EventManager:
```typescript
import { EventTarget } from 'cc';
export class EventManager {
private static _ins: EventManager;
private _eventTarget: EventTarget;
// 私有构造函数确保单例
private constructor() {
this._eventTarget = new EventTarget();
}
static get ins(): EventManager {
if (!this._ins) {
this._ins = new EventManager();
}
return this._ins;
}
// 监听事件
on(type: string, callback: Function, target?: any, once?: boolean) {
if (once) {
this._eventTarget.once(type, callback, target);
} else {
this._eventTarget.on(type, callback, target);
}
}
// 发射事件(支持任意数量参数)
emit(type: string, ...args: any[]) {
this._eventTarget.emit(type, ...args);
}
// 取消监听
off(type: string, callback?: Function, target?: any) {
this._eventTarget.off(type, callback, target);
}
// 取消对象的所有监听
offAll(target: any) {
this._eventTarget.targetOff(target);
}
// 检查是否有监听
has(type: string, callback?: Function, target?: any): boolean {
return this._eventTarget.hasEventListener(type, callback, target);
}
}
```
### 改进点:
1. 内置初始化逻辑(无需手动调用init)
2. 支持任意数量参数的事件传递
3. 提供完整的类型声明
4. 统一事件接口(on/once使用同一方法)
5. 保留批量解绑功能
### 使用示例:
```typescript
// 监听事件
EventManager.ins.on('PLAYER_DEAD', this.onPlayerDead, this);
// 发射事件(任意数量参数)
EventManager.ins.emit('ITEM_COLLECTED', itemType, quantity, playerId);
// 取消监听
EventManager.ins.off('PLAYER_DEAD', this.onPlayerDead, this);
// 取消对象所有监听
EventManager.ins.offAll(this);
```
## 总结
1. **架构差异**:
- EventMgr 基于 Component 和 Node 事件系统
- EventManager 基于独立的 EventTarget
2. **生命周期**:
- EventMgr 依赖场景生命周期
- EventManager 独立于场景
3. **事件能力**:
- EventMgr 支持完整节点事件特性(冒泡/捕获)
- EventManager 更轻量,适合全局通信
4. **参数传递**:
- EventMgr 最多支持5个参数
- EventManager 支持任意数量参数
5. **使用便捷性**:
- EventMgr 静态方法直接调用
- EventManager 需要访问单例实例
根据项目需求选择:
- 需要节点交互 → EventMgr
- 需要全局通信 → EventManager
- 需要最佳实践 → 改进版EventManager