最近去Unity进修去了,首先感谢TEngine,事件系统的实现借鉴了TEngine。本文只展示了我的实现,如果看懂了可以自己实现出来。实现虽然有点繁琐,但是一次实现,受益终生。
先看效果
事件定义
interface IGame {
onCoinChange(coin: number): void;
}
事件自动化生成
import { RegisterEvent } from "db://assets/startup/framework/Event/Event";
import { EventBase } from "db://assets/startup/framework/Event/EventBase";
import { EventDecorator } from "db://assets/startup/framework/Event/EventDecorator";
export class Game_Name {
public static readonly onCoinChange = RegisterEvent("Game_onCoinChange");
}
@EventDecorator
export class Game extends EventBase {
onCoinChange(coin: number): void {
this.event.dispatch(Game_Name.onCoinChange, coin);
}
}
如何注册,发送事件
import { Event } from "../../startup/framework/Event/Event";
import { GameEntryBase } from "../../startup/framework/GameEntry/GameEntryBase";
import { GameEntryDecorator } from "../../startup/framework/GameEntry/GameEntryDecorator";
import { GameModule } from "../../startup/framework/Module/GameModule";
import { Game, Game_Name } from "./event/generate/Game";
@GameEntryDecorator
export class GameEntry extends GameEntryBase {
public async OnGameStart() {
console.log("GameEntry OnGameStart");
let event = GameModule.Instance().getModule<Event>(Event);
//注册
event.addListener(Game_Name.onCoinChange, this.onCoinChange, this);
//发送
let game = event.getEventInterface<Game>(Game);
game.onCoinChange(100);
}
onCoinChange(coin: number) {
console.log("GameEntry onCoinChange", coin);
}
}
interface名字作为事件模块名,方法来描述这个事件要干什么,实在是巧妙,完美的诠释了这个事件是用来干什么的,你甚至注释都不用写,也不用告诉别人参数。
如何实现
首先来讲一个ts范式,如何收集一系列指定模块的相关对象
- 定义模块功能基类
import { Event } from "./Event";
export abstract class EventBase {
protected event: Event;
constructor(event: Event) {
this.event = event;
}
}
2.定义装饰器收集所有实现过基类的子类
import { Event } from "./Event";
import { EventBase } from "./EventBase";
export interface EventBaseConstructor {
new(event: Event): EventBase;
}
export const eventList: Array<EventBaseConstructor> = [];
export function EventDecorator(target: EventBaseConstructor) {
eventList.push(target);
}
3.在实现的子类上标记
@EventDecorator
export class XXXX extends EventBase {
}
在Event里根据收集到的构造函数,实例化所有的对象,并且提供获取接口,所以Event的第一个接口出来了
export class Event extends ModuleBase {
private eventInterfaceMap: Map<EventBaseConstructor, EventBase> = new Map();
public init(): void {
for (let i = 0; i < eventList.length; i++) {
let eventDecorator = eventList[i];
let eventBase = new eventDecorator(this);
this.eventInterfaceMap.set(eventDecorator, eventBase);
}
}
public getEventInterface<T extends EventBase>(type: EventBaseConstructor): T {
if (!this.eventInterfaceMap.has(type)) {
throw new Error(`Event ${type} not found`);
}
return this.eventInterfaceMap.get(type) as T;
}
}
我们需要一个类来帮助我们记录事件类别,方法,方法的指针
export class EventHandle implements IReferencePoolObject {
public eventType: number;
public object: object;
public method: Function;
public dispose(): void {
this.eventType = 0;
this.object = null;
this.method = null;
}
}
为什么eventType是number,在map里找一个number,肯定比找一个string要快,如何定义这个number,当然是自动生成了。可以看到,每次调用RegisterEvent都会自动生成一个idx,并且在自动化生成的代码中自动调用。
let eventIndex = 1;
let idx2name = new Map<number, string>();
let name2idx = new Map<string, number>();
export function RegisterEvent(name: string) {
if (name2idx.has(name)) {
throw new Error(`Event ${name} already registered`);
}
let idx = eventIndex++;
idx2name.set(idx, name);
name2idx.set(name, idx);
return idx;
}
// 这里是生成出来的代码
export class Game_Name {
public static readonly onCoinChange = RegisterEvent("Game_onCoinChange");
}
好,让我们来补齐,Event剩下来的接口,包括添加、移除、发送
public addListener(eventType: number, method: Function, object: object) {
let handle = this.referencePool.getReference<EventHandle>(EventHandle);
handle.object = object;
handle.method = method;
handle.eventType = eventType;
if (!this.eventMap.has(eventType)) {
this.eventMap.set(eventType, []);
}
this.eventMap.get(eventType).push(handle);
}
public removeListener(eventType: number, method: Function, object: object) {
if (!this.eventMap.has(eventType)) {
return;
}
let handles = this.eventMap.get(eventType);
for (let i = 0; i < handles.length; i++) {
if (handles[i].object === object && handles[i].method === method) {
handles.splice(i, 1);
this.referencePool.returnReference(handles[0]);
break;
}
}
if (handles.length === 0) {
this.eventMap.delete(eventType);
}
}
public dispatch(eventType: number, ...args: any[]) {
if (!this.eventMap.has(eventType)) {
return;
}
let handles = this.eventMap.get(eventType);
for (let i = 0; i < handles.length; i++) {
handles[i].method.apply(handles[i].object, args);
}
}
剩下来就是自动化生成相关了,我们需要导入装饰器,需要继承父类,需要调用RegisterEvent去生成idx,那么首先就要import这三个,然后IModule里面import的部分,我们自动化生成类也要import对应部分。生成出2个类,一个用来记录事件名,一个用来发送事件。这部分代码十分枯燥,直接让ai生成即可,我放出我的ai实现,各位自己生成一个插件来调用这段代码即可。
import * as fs from 'fs';
import * as path from 'path';
export async function OnEventGenerate() {
let projectPath = Editor.Project.path;
let eventInterfacePath = `${projectPath}/assets/scripts/game/event/interface`;
let generatePath = `${projectPath}/assets/scripts/game/event/generate`;
// 删除生成目录下的所有文件
if (fs.existsSync(generatePath)) {
const files = fs.readdirSync(generatePath);
for (const file of files) {
if(file.endsWith('.ts')){
const filePath = path.join(generatePath, file);
await Editor.Message.request('asset-db', 'delete-asset', filePath);
}
}
}
let tsFiles = readDir(eventInterfacePath);
for (let i = 0; i < tsFiles.length; i++) {
let tsFile = tsFiles[i];
let newFileContent = '';
let content = fs.readFileSync(tsFile, 'utf-8');
let contents = content.split('\n');
// 保留原有的import语句
for (let j = 0; j < contents.length; j++) {
let item = contents[j];
if (item.includes('import')) {
newFileContent += item + '\n';
}
}
// 添加Event导入
newFileContent += 'import { RegisterEvent } from "db://assets/startup/framework/Event/Event";\n';
newFileContent += 'import { EventBase } from "db://assets/startup/framework/Event/EventBase";\n';
newFileContent += 'import { EventDecorator } from "db://assets/startup/framework/Event/EventDecorator";\n\n';
// 获取文件名(不含扩展名)
let fileName = path.basename(tsFile, '.ts');
let interfaceName = fileName.replace('I', '');
// 生成事件名称类
newFileContent += `export class ${interfaceName}_Name {\n`;
// 收集所有方法名
let methodNames: string[] = [];
for (let j = 0; j < contents.length; j++) {
let item = contents[j];
if (item.includes('(') && item.includes(')')) {
let methodMatch = item.match(/(\w+)\s*\((.*?)\)/);
if (methodMatch) {
let methodName = methodMatch[1];
methodNames.push(methodName);
// 为每个方法生成事件名
newFileContent += ` public static readonly ${methodName} = RegisterEvent("${interfaceName}_${methodName}");\n`;
}
}
}
newFileContent += '}\n\n';
// 生成事件类
newFileContent += `@EventDecorator\n`;
newFileContent += `export class ${interfaceName} extends EventBase {\n\n`;
// 处理接口方法
for (let j = 0; j < contents.length; j++) {
let item = contents[j];
if (item.includes('interface')) {
continue;
}
if (item.includes('(') && item.includes(')')) {
let methodMatch = item.match(/(\w+)\s*\((.*?)\)/);
if (methodMatch) {
let methodName = methodMatch[1];
let params = methodMatch[2];
newFileContent += ` ${methodName}(${params}): void {\n`;
// 提取参数名列表
let paramNames = params.split(',').map(p => p.trim().split(':')[0].trim()).filter(p => p);
newFileContent += ` this.event.dispatch(${interfaceName}_Name.${methodName}${paramNames.length > 0 ? ', ' + paramNames.join(', ') : ''});\n`;
newFileContent += ' }\n\n';
}
}
}
newFileContent += '}\n';
// 确保目标目录存在
if (!fs.existsSync(generatePath)) {
fs.mkdirSync(generatePath, { recursive: true });
}
// 写入新文件
let newFilePath = path.join(generatePath, `${interfaceName}.ts`);
fs.writeFileSync(newFilePath, newFileContent);
await Editor.Message.request('asset-db', 'refresh-asset', newFilePath);
}
}
function readDir(dirPath: string): string[] {
let tsFiles: string[] = [];
let files = fs.readdirSync(dirPath);
files.forEach(file => {
let fullPath = path.join(dirPath, file);
let stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
readDir(fullPath);
} else if (file.endsWith('.ts')) {
tsFiles.push(fullPath);
}
});
return tsFiles;
}
最后,如何防止用户只监听却不注销,在你的UIBase里这么做
export abstract class UIBase implements IReferencePoolObject {
protected event: Event;
private eventHandles: Array<EventHandle>;
constructor() {
this.event = GameModule.Instance().getModule(Event);
this.eventHandles = [];
}
protected addEventHandle(eventType: number, method: Function, object?: object) {
if (!object) {
object = this;
}
let handle = this.referencePool.getReference<EventHandle>(EventHandle);
handle.object = object;
handle.method = method;
this.eventHandles.push(handle);
this.event.addListener(eventType, method, object);
}
public dispose(): void {
this.node = null;
this.info = null;
this.userData = null;
for (let i = this.eventHandles.length - 1; i >= 0; i--) {
this.event.removeListener(this.eventHandles[i].eventType, this.eventHandles[i].method, this.eventHandles[i].object);
this.referencePool.returnReference(this.eventHandles[i]);
}
this.eventHandles.length = 0;
}
}











请求事件方法非常好用

