跳到主要内容
版本:0.2.4

自定义 RPC

WukongMP SDK 的核心功能之一是支持任意远程过程调用 (RPC) 函数。这些函数可以携带任意有效负载,并支持多种 中继模式。

RPC 处理程序允许你定义自己的事件,这些事件将发送给连接到同一服务器的其他玩家。

声明一个 RPC 处理程序类​

为了向你的模组添加自定义 RPC 过程,你必须定义一个 partial 类,它继承自 RpcClassBase。

最简 RPC 类定义
public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)
{
// 在此处编写 RPC 处理器
}
important

要注册任何 RPC 处理程序,类 必须被添加到 DI 容器中,在你的模组的 Initialize 方法中。请查阅 ModBase 的文档。

定义 RPC 处理程序​

为了向你的模组添加一个新的 RPC 处理程序,请添加一个带有 RpcEvent 属性的方法。

方法名必须以“On...”开头——在同一个类中会自动为发送 RPC 生成相应的“Send...”方法。

最简 RPC 处理器
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 处理程序支持在参数中传递数据,这些数据要么是:

一个 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。

复杂 RPC 参数示例
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 中扩展此功能。

名称类型描述
__senderPlayerId发送该 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 方法,将回调安排在主线程上执行。

Running code on the main thread
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
}
});
}
}