自定义 RPC
WukongMP SDK 的核心功能之一是支持任意远程过程调用 (RPC) 函数。这些函数可以携带任意有效负载,并支持多种 中继模式。
RPC 处理程序允许你定义自己的事件,这些事件将发送给连接到同一服务器的其他玩家。
声明一个 RPC 处理程序类
为了向你的模组添加自定义 RPC 过程,你必须定义一个 partial 类,它继承自 RpcClassBase。
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
// 在此处编写 RPC 处理器
}
要注册任何 RPC 处理程序,类 必须被添加到 DI 容器中,在你的模组的 Initialize 方法中。请查阅
ModBase 的文档。
定义 RPC 处理程序
为了向你的模组添加一个新的 RPC 处理程序,请添加一个带有 RpcEvent 属性的方法。
方法名必须以“On...”开头——在同一个类中会自动为发送 RPC 生成相应的“Send...”方法。
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
[RpcEvent(RelayMode.AreaOfInterestOthers)]
private void OnMyCustomCall()
{
// 收到此消息时执行的操作
}
// 由 SDK 生成
private void SendMyCustomCall() { ... }
}
该属性需要一个类型为 RelayMode 的参数,该参数指示消息到达服务器时将如何传播给玩家。
当前支持以下模式:
| 中继模式 | 描述 |
|---|---|
| AreaOfInterestOthers | 消息已发送给同一等级的所有其他玩家 |
| AreaOfInterestAll | 消息将发送给同一等级的所有玩家,包括发送者本人。 |
| GlobalOthers | 消息已发送给服务器上的所有其他玩家 |
| GlobalAll | 消息已发送给服务器上的所有玩家,包括发送者 |
正在发送数据
RPC 处理程序支持在参数中传递数据,这些数据要么是:
- 原始数据类型
- 被 [DeriveINetSerializable] 装饰的结构体。
- 由 SDK 注入的特殊参数
一个 RPC 处理程序可以按任意数量、任意顺序声明这些参数。生成的“Send...”方法将具有相同的参数(不包括注入的参数)。
基本数据类型
我们支持在 C#
运行时定义的所有原始数据类型:bool、char、sbyte、byte、short、ushort、int、uint、long、ulong、float、double,以及
string。
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
[RpcEvent(RelayMode.AreaOfInterestOthers)]
private void OnMyCustomCall(int number, string text)
{
// 收到此消息时执行的操作
}
// 由 SDK 生成
private void SendMyCustomCall(int number, string text) { ... }
}
复杂数据类型
您可以在 RPC 参数中使用复杂的数据结构。这些数据结构必须实现 INetSerializable 接口。
载荷结构的字段可以是任何可序列化类型,包括其他复杂类型,前提是它们也实现了 INetSerializable。
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
[RpcEvent(RelayMode.AreaOfInterestOthers)]
private void OnPlayerBuffed(BuffData payload)
{
// 收到此消息时执行的操作
}
// 由 SDK 生成
private void SendPlayerBuffed(BuffData payload) { ... }
}
SDK 提供了 [DeriveINetSerializable] 属性,这使得 SDK 在大多数情况下能够自动生成序列化代码。为了实现这一点,结构体必须声明为 partial。
[DeriveINetSerializable]
public partial struct BuffData(int buffId, float duration) : INetSerializable
{
public int BuffId = buffId;
public float Duration = duration;
}
如果你想对通过网络发送的数据拥有完全的控制权,也可以手动实现该接口。
我们将 LiteNetLib 库作为我们网络栈的基础。你可以参考它的
documentation 以了解如何使用
NetDataWriter 和 NetDataReader。
public struct BuffAddData(int buffId, float duration) : INetSerializable
{
public int BuffId = buffId;
public float Duration = duration;
public void Serialize(NetDataWriter writer)
{
writer.Put(BuffId);
writer.Put(Duration);
}
public void Deserialize(NetDataReader reader)
{
BuffId = reader.GetInt();
Duration = reader.GetFloat();
}
}
特殊参数
目前只能使用一个特殊参数。我们可能会在未来版本的 SDK 中扩展此功能。
| 名称 | 类型 | 描述 |
|---|---|---|
__sender | PlayerId | 发送该 RPC 的玩家的标识符 |
此参数可与其他参数一起使用,但在生成的“Send...”方法中不可见。
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
[RpcEvent(RelayMode.AreaOfInterestOthers)]
private void OnComplexEvent(int number, PlayerId __sender, BuffData buffData)
{
// 示例:处理发送者昵称
if (WukongApi.Sync.TryGetPlayerInfoById(__sender, out var playerName, out _))
{
Logging.LogInfo("Received data from {Sender}", playerName);
}
}
// 由 SDK 生成
private void SendComplexEvent(int number, BuffData buffData) { ... }
}
在主线程执行回调
RPC 处理程序在单独的网络线程上执行,因此它们不应直接与游戏世界交互,因为这样做可能会导致崩溃。不过,你可以使用在你的 RPC 处理程序类中提供的 RunOnMainThread 方法,将回调安排在主线程上执行。
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
[RpcEvent(RelayMode.AreaOfInterestAll)]
private void OnDespawnAllMonsters()
{
// destroying an Unreal Engine pawn must be done on the main thread
RunOnMainThread(() =>
{
foreach (var monster in WukongApi.Sync.AreaTamers)
{
monster.Tamer?.CurrentRef.DestroyTamer(); // calls BGU_UnrealWorldUtil.DestroyActor
}
});
}
}