Skip to content

事件系统

这一小节中,我们会对 SDK 中的事件系统进行阐述。

本文内容包含:

  • 事件系统的作用
  • Event 类介绍
  • 各 SDK 实战示例(TS / C# / C++)

事件系统的作用

1. 线程安全性保障

在 SDK 中,初始化引擎模块时,可以选择使用客户端线程,也可以多线程。这使得引擎模块与前端业务执行之间可能存在线程不安全的情况,容易出现问题。为了解决这一问题,所有事件的通讯都通过事件系统进行分发,在事件系统中,把事件消息进行管理,统一到前端业务执行的线程中去。

2. SDK 插件事件和前端渲染表现层业务解耦

解耦是开发时必须谈及的问题,如何正确解耦可以使业务分离,模块之间更加独立,更好进行维护,提高代码重用性。

通过事件系统,业务和表现层以及 SDK 内部可以解耦,使三者相对独立。它们之间通过事件或消息的方式进行分发并完成通讯。

Event 类

该类是维护整个事件系统的核心类,并且是一个静态类。它提供事件注册、注销、激发、清除等方法。

事件方向

方向流向说明
In游戏层 → SDK 插件UI 层点击登录等操作触发,向服务端发送请求
OutSDK 插件 → 游戏层收到服务端消息后,通知 UI / 逻辑层

注册与注销事件

registerIn

bool registerIn(string eventname, object obj, string funcname)

描述: 注册监听由渲染表现层抛出的事件 (in = render → kbe),一般由 SDK 插件层进行注册。例如:UI 层点击登录按钮的事件。

参数:

  • eventname:string,事件名。请确保不要和 SDK 内部事件名冲突,并且全局唯一,否则会发生调用混乱。
  • obj:object,事件激发时的被调对象。
  • funcname:string,事件激发时的被调方法名。请确保该方法的签名与 fireIn 的参数 args 一致。在调用时,通过反射机制 (Unity 的 C# 下) 查找 obj 的该方法,并进行调用。

registerOut

bool registerOut(string eventname, object obj, string funcname)

描述:registerIn 类似,但是注册监听由 SDK 插件抛出的事件 (out = kbe → render),一般由渲染表现层来注册。例如:监听角色血量属性的变化,如果 UI 层注册这个事件,事件触发后就可以根据事件所附带的当前血量值来改变角色头顶的血条值。

参数:

  • eventname:string,事件名。请确保不要和 SDK 内部事件名冲突,并且全局唯一,否则会发生调用混乱。
  • obj:object,事件激发时的被调对象。
  • funcname:string,事件激发时的被调方法名。请确保该方法的签名与 fireOut 的参数 args 一致。在调用时,通过反射机制 (Unity 的 C# 下) 查找 obj 的该方法,并进行调用。

deregisterIn

bool deregisterIn(object obj)

描述: 注销 obj 对象身上的 In 事件。

参数: obj:object,要注销 In 事件的目标对象。

deregisterOut

bool deregisterOut(object obj)

描述: 注销 obj 对象身上的 Out 事件。

参数: obj:object,要注销 Out 事件的目标对象。

激发事件

fireIn

bool fireIn(string eventname, params object[] args)

描述: 渲染表现层抛出事件 (in = render → kbe),由渲染表现层来调用该方法。例如:UI 层点击登录时向 SDK 插件层激发该事件。

参数:

  • eventname:string,事件名。请确保不要和 SDK 内部事件名冲突,并且全局唯一,否则会发生调用混乱。
  • args:object,事件激发时的参数。

fireOut

bool fireOut(string eventname, params object[] args)

描述: SDK 插件层抛出事件 (out = kbe → render),由 SDK 插件层来调用该方法。例如:收到角色血量变化的服务器消息时,向 UI 层激发该事件,使其更改血条值。

参数:

  • eventname:string,事件名。请确保不要和 SDK 内部事件名冲突,并且全局唯一,否则会发生调用混乱。
  • args:object,事件激发时的参数。

暂停和恢复

暂停和恢复事件系统的激发操作,有些时候非常有用。因为客户端是需要有一些延迟操作的,比如场景的加载、角色的加载等需要一定时间才能完成一些初始化操作,而服务端的消息是不会被影响的,导致在资源加载后,这些事件已经被激发完毕了,而事件的注册者都还没来得及进行注册,造成事件丢失。这个时候就可以使用暂停和恢复功能。

pause

描述: 暂停事件系统的激发。暂停后 fireOut 的事件会进入队列而不立即执行。

resume

描述: 恢复事件系统的激发。恢复后积压的队列事件一次性处理。

各 SDK 实战示例

TypeScript(Cocos Creator)

游戏层注册 Out 事件:

typescript
import KBEEvent from './kbe_typescript_plugins/Event';

// 注册监听服务端推送
KBEEvent.registerOut('onEnterWorld', this, this.onEnterWorld.bind(this));
KBEEvent.registerOut('onKicked', this, this.onKicked.bind(this));
KBEEvent.registerOut('onDisconnected', this, this.onDisconnected.bind(this));

// 回调处理
private onEnterWorld(entity: KBEEntity): void {
    console.log('进入世界, entity id:', entity.id);
}

游戏层触发 In 事件:

typescript
// 登录请求
KBEEvent.fireIn('login', account, password, 
    System.Text.Encoding.UTF8.GetBytes('kbengine_all_demo'));

// 创建账号
KBEEvent.fireIn('createAccount', accountName, password, datas);

// 移动操作
KBEEvent.fireIn('jump');

注销事件:

typescript
// 对象销毁时注销所有事件
KBEEvent.deregister(this);

C#(Unity)

游戏层注册 Out 事件:

csharp
using KBEngine;

void Start()
{
    KBEngine.Event.registerOut("onKicked", this, "onKicked");
    KBEngine.Event.registerOut("onDisconnected", this, "onDisconnected");
    KBEngine.Event.registerOut("onLoginSuccessfully", this, "onLoginSuccessfully");
    KBEngine.Event.registerOut("onEnterWorld", this, "onEnterWorld");
}

public void onEnterWorld(KBEngine.Entity entity)
{
    Debug.Log("进入世界: " + entity.id);
}

游戏层触发 In 事件:

csharp
// 登录按钮点击
KBEngine.Event.fireIn("login", account, password,
    System.Text.Encoding.UTF8.GetBytes("kbengine_all_demo"));

// 创建账号
KBEngine.Event.fireIn("createAccount", account, password, datas);

注销事件:

csharp
void OnDestroy()
{
    KBEngine.Event.deregisterOut(this);
}

C++(Unreal Engine)

C++ SDK 区分 In/Out 方向,可直接调用类方法或使用宏:

游戏层注册 Out 事件(监听服务端推送):

cpp
#include "KBEvent.h"

// 注册 Out 事件
KBEvent::registerOut(KBEngine::KBEventTypes::onEnterWorld, "onEnterWorld",
    [this](std::shared_ptr<UKBEventData> pData) { this->onEnterWorld(pData); });

KBEvent::registerOut(KBEngine::KBEventTypes::onLeaveWorld, "onLeaveWorld",
    [this](std::shared_ptr<UKBEventData> pData) { this->onLeaveWorld(pData); });

// 游戏层触发 In 事件(向服务端发送请求)
std::shared_ptr<UKBEventData> pEventData =
    std::make_shared<UKBEventData_login>(account, password, datas);
KBEvent::fireIn(KBEngine::KBEventTypes::login, pEventData);

// 游戏层注册 In 事件(SDK 插件内部使用,拦截游戏层请求)
KBEvent::registerIn(KBEngine::KBEventTypes::login, "login",
    [this](std::shared_ptr<UKBEventData> pData) { this->handleLogin(pData); });

简化宏(推荐):

cpp
// Out 事件注册 —— 监听服务端推送
KBENGINE_REGISTER_EVENT_OUT(KBEngine::KBEventTypes::onEnterWorld, onEnterWorld);

// In 事件注册 —— 拦截游戏层请求(插件内部使用)
KBENGINE_REGISTER_EVENT_IN(KBEngine::KBEventTypes::login, onLogin);

// In 事件触发 —— 游戏层向 SDK 发送请求
KBENGINE_EVENT_FIRE_IN(KBEngine::KBEventTypes::login, pEventData);

// Out 事件触发 —— SDK 向游戏层推送
KBENGINE_EVENT_FIRE_OUT(KBEngine::KBEventTypes::onKicked, pEventData);

// 注销
void UGameKBEMain::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    KBEvent::deregisterOut(this);
    KBEvent::deregisterIn(this);
}

暂停 / 恢复示例

typescript
// TypeScript
KBEEvent.pause();   // 暂停 Out 事件触发
// ... 加载场景、初始化 UI、注册事件 ...
KBEEvent.resume();  // 恢复,清空积压队列
csharp
// C#
KBEngine.Event.pause();
// ... 初始化 ...
KBEngine.Event.resume();
cpp
// C++
KBEvent::pause();
// ... 初始化 ...
KBEvent::resume();