Skip to content
事件系统(EventSystem)

事件系统(EventSystem)

阅读本文预计 20 分钟

本文概述了编辑器中,脚本之间的事件传递规则及注意事项

什么是事件系统

事件系统由派发器(Dispatcher)和监听器(Listener)构成。

主要处理用户输入状态传递、客户端与服务器的数据传递、以及本地脚本之间的数据传递。

事件系统都包含什么

客户端监听器

系统事件

用户键盘按下事件(onKeyDown)

当用户在终端有按下操作时,系统会通知客户端事件触发

此事件只在客户端触发

例如当用户按下了'K'按键:

ts
InputUtil.onKeyDown(Keys.K,()=>{

    console.log(`The user presses the K key`);
            
});
InputUtil.onKeyDown(Keys.K,()=>{

    console.log(`The user presses the K key`);
            
});
用户键盘抬起事件(onKeyUp)

当用户在终端有按键抬起操作时,系统会通知客户端事件触发

此事件只在客户端触发

例如当用户抬起了'K'按键:

ts
InputUtil.onKeyUp(Keys.K,()=>{

    console.log(`The user lifted the K key`);
            
});
InputUtil.onKeyUp(Keys.K,()=>{

    console.log(`The user lifted the K key`);
            
});
用户键盘按住事件(onKeyPress)

当用户在终端按住某个按键时,系统会通知客户端事件触发

此事件只在客户端触发

例如当用户按住了'K'按键:

ts
InputUtil.onKeyPress(Keys.K,()=>{

    console.log(`The user holds down the K key`);
    console.log(`Current time ${Global.ElapsedTime()`);
            
});
InputUtil.onKeyPress(Keys.K,()=>{

    console.log(`The user holds down the K key`);
    console.log(`Current time ${Global.ElapsedTime()`);
            
});

自定义事件

来自本地事件(LocalListener)

此事件只在本地触发,适用于脚本间的数据传递

客户端 ---> 客户端

服务端 ---> 服务端

例如当用户按下了某个按钮,在其他脚本中需要监听其事件

ts
Event.addLocalListener("onXXXButtonClick",()=>{

    console.log(`The user pressed the XXX button`);
            
});
Event.addLocalListener("onXXXButtonClick",()=>{

    console.log(`The user pressed the XXX button`);
            
});
来自服务端事件(ServerListener)

当服务端向客户端传递数据时,客户端可以通过此函数来监听事件

此事件只在客户端触发

例如当服务端通知客户端玩家升级

ts
Event.addServerListener("LevelUp",(lv:number)=>{

    console.log(`User upgraded to level ===> ${lv}`);      

});
Event.addServerListener("LevelUp",(lv:number)=>{

    console.log(`User upgraded to level ===> ${lv}`);      

});

服务端监听器

来自客户端事件(ClientListener)

当客户端向服务端传递数据时,服务器可以通过此函数来监听事件

此事件只在服务端触发

例如当玩家发动攻击

ts
Event.addClientListener("Attack",(player:Player,skills:number)=>{

    console.log(`The player(${player.playerId}) uses skill ${skills} to launch an attack.`);

});
Event.addClientListener("Attack",(player:Player,skills:number)=>{

    console.log(`The player(${player.playerId}) uses skill ${skills} to launch an attack.`);

});

房间事件

玩家进入房间事件(PlayerJoinedListener)

当玩家进入房间时,服务器会通知该服务端脚本

此事件只在服务端触发

例如服务端需要广播玩家进入房间的信息时

ts
Player.onPlayerJoin.add((player:Player)=>{

    console.log(`The player(${player.playerId}) enters the room`);

});
Player.onPlayerJoin.add((player:Player)=>{

    console.log(`The player(${player.playerId}) enters the room`);

});
玩家离开房间事件(PlayerLeftListener)

当玩家离开房间时,服务器会通知该服务端脚本

此事件只在服务端触发

例如服务端需要广播玩家离开房间的信息时

ts
Player.onPlayerLeave.add((player:Player)=>{

    console.log(`Players(${player.playerId}) leave the room`);

});
Player.onPlayerLeave.add((player:Player)=>{

    console.log(`Players(${player.playerId}) leave the room`);

});

客户端派发器

派发事件到本地(dispatchToLocal)

对应 2.1.2 - 1,当用户按下了某个按钮,在其他脚本中需要监听此事件

此脚本可编写如下,此事件只能在本地派发

ts
Event.dispatchToLocal("onXXXButtonClick");
Event.dispatchToLocal("onXXXButtonClick");

派发事件到服务端(dispatchToServer)

对应 2.2.1,当用户使用某技能发动了攻击,需要通知服务端进行同步

此脚本可编写如下,此事件只能在客户端派发

ts
let skills:number = 6; 
Event.dispatchToServer("Attack",skills);
let skills:number = 6; 
Event.dispatchToServer("Attack",skills);

服务端派发器

派发事件到指定客户端(dispatchToClient)

对应 2.1.2 - 2,结合 2.2.1

当玩家发动攻击,服务器收到消息后需要通知玩家升级的时候

此脚本可编写如下,此事件只能在服务端派发

ts
Event.addClientListener("Attack",(player:Player,skills:number)=>{

    console.log(`The player(${player.playerId}) uses skill ${skills} to launch an attack.`);

    let level:number = 66;

    //通知player接收LevelUp事件,事件数据为level
    Event.dispatchToClient(player,"LevelUp",level);
    
});
Event.addClientListener("Attack",(player:Player,skills:number)=>{

    console.log(`The player(${player.playerId}) uses skill ${skills} to launch an attack.`);

    let level:number = 66;

    //通知player接收LevelUp事件,事件数据为level
    Event.dispatchToClient(player,"LevelUp",level);
    
});

派发事件到所有客户端(dispatchToAllClient)

同上情形,若想将该玩家的升级消息同步至所有客户端

此脚本可编写如下,此事件只能在服务端派发

ts
Event.addClientListener("Attack",(player:Player,skills:number)=>{

    console.log(`The player(${player.playerId}) uses skill ${skills} to launch an attack.`);

    let level:number = 66;
    
    //通知所有玩家接收LevelUp事件,扩散范围以player为中心,事件数据为player升级到level
    Event.dispatchToAllClient("LevelUp",player,level);
    
});
Event.addClientListener("Attack",(player:Player,skills:number)=>{

    console.log(`The player(${player.playerId}) uses skill ${skills} to launch an attack.`);

    let level:number = 66;
    
    //通知所有玩家接收LevelUp事件,扩散范围以player为中心,事件数据为player升级到level
    Event.dispatchToAllClient("LevelUp",player,level);
    
});

派发事件到本地(dispatchToLocal)

对应 2.1.2 - 1此事件只能在本地派发

ts
Event.dispatchToLocal("eventName");
Event.dispatchToLocal("eventName");

使用事件系统的注意事项与建议

区分脚本的运行环境(客户端 or 服务端)

派发器与监听器有严格的运行环境要求

在相对应的运行环境执行代码才可生效

建议在使用事件系统时可在外层做脚本运行环境的判断

this.isRunningClient() ---> 是否为客户端

SystemUtil.isClient() ---> 是否为客户端

SystemUtil.isServer() ---> 是否为服务端

服务端相关代码写在单独脚本中

由于编辑器的独特性,服务端与客户端可写在同一工程文件中

为防止在同一脚本中,属性的访问会因为客户端与服务端的环境不同导致逻辑冲突

建议将服务端的代码单独写在一个脚本中,便于快速定位问题

对象销毁后关闭监听器与派发器

事件系统中的派发器与监听器不会根据脚本的生命周期一起销毁

建议在脚本生命周期的onDestroy中关闭派发器与监听器的连接

示例如下:

ts
@Class
export default class TestEvents extends Script {

    //声明事件数组
    myEvents = new Array<EventListener>();

    //声明一个计数变量
    public temp:number;

    protected async onStart(): Promise`<void>` {

        //初始化计数变量为0
        this.temp = 0;

        //根据GUID持有cube对象
        let cube = await GameObject.asyncFindGameObjectById(`48A8055A40BBA143D723B19BDB2D21ED`);


        //添加本地事件监听,并将监听器对象保存到事件数组
        this.myEvent.push(InputUtil.addLocalListener("TestEvent1",()=>{
            console.log("========================>");
            console.log(`this.temp ===> ${this.temp}`);
        }));

        this.myEvent.push(InputUtil.onKeyDown(Keys.one,()=>{
            this.temp ++;
            Event.dispatchToLocal("TestEvent1");
        }));

        this.myEvent.push(InputUtil.onKeyDown(Keys.Two,()=>{
            cube.Destroy();
            Event.dispatchToLocal("TestEvent1");
        }));
    }


    protected onDestroy(): void {

        console.log(`Into onDestroy()`);

        //在对象被销毁时,遍历所有事件对象,关闭所有事件监听
        this.myEvent.forEach(element => {
            element.disconnect();
        });
    }
}
@Class
export default class TestEvents extends Script {

    //声明事件数组
    myEvents = new Array<EventListener>();

    //声明一个计数变量
    public temp:number;

    protected async onStart(): Promise`<void>` {

        //初始化计数变量为0
        this.temp = 0;

        //根据GUID持有cube对象
        let cube = await GameObject.asyncFindGameObjectById(`48A8055A40BBA143D723B19BDB2D21ED`);


        //添加本地事件监听,并将监听器对象保存到事件数组
        this.myEvent.push(InputUtil.addLocalListener("TestEvent1",()=>{
            console.log("========================>");
            console.log(`this.temp ===> ${this.temp}`);
        }));

        this.myEvent.push(InputUtil.onKeyDown(Keys.one,()=>{
            this.temp ++;
            Event.dispatchToLocal("TestEvent1");
        }));

        this.myEvent.push(InputUtil.onKeyDown(Keys.Two,()=>{
            cube.Destroy();
            Event.dispatchToLocal("TestEvent1");
        }));
    }


    protected onDestroy(): void {

        console.log(`Into onDestroy()`);

        //在对象被销毁时,遍历所有事件对象,关闭所有事件监听
        this.myEvent.forEach(element => {
            element.disconnect();
        });
    }
}