跳到主要内容

WukongMP SDK 0.3.0 发布

· 阅读需 7 分钟
ReadyM 团队
WukongMP 和 OblivionMP 的开发者

服务器端脚本来了。模组现在可以在中继服务器内部运行自己的代码:在服务器实体上注册组件、在服务器循环上 tick 系统,以及通过 RPC 响应客户端的请求。这就是我们此前说过会做的那部分,联机合作模组已经跑在它上面了。

自定义组件现在也支持双向同步,因此模组可以把自己的状态放在实体上,让服务器和每个客户端对它保持一致,而不需要编写任何消息传递代码。

本版本还为管理面板新增了 Network 视图,它会显示中继实际发送的内容,并按 ECS 组件细分。除此之外还有三项联机合作模式的修复,其中包括 NPC 可能变为敌对并卡住任务的问题。

请把服务器二进制更新到 0.3.0。基于 0.2.4 构建的客户端模组需要重新编译,并做少量代码改动,参见迁移指南

服务器端模组

服务器端模组是一个 .NET 类库,由中继服务器从它的 server_mods/ 目录加载,这个目录与面向客户端的 mods/ 文件夹是分开的。把程序集放进去,服务器启动时就会加载它。

它能做什么:

  • 服务器实体上的组件,可以是本地的(仅服务器)或网络化的,挂到内置原型上,或挂到你的模组注册的新原型上。
  • 系统,在服务器的更新循环上 tick,适用于需要持续运行、而不是响应单个客户端的逻辑。
  • 服务器 RPC,客户端模组与服务器之间的请求与响应通道,只需定义一次,作为两边共享的契约。

请从快速上手开始,并阅读原型与组件了解内置实体都带有哪些组件。联机合作模组是这三者的一个小型完整示例:两个系统、一个 RPC 处理程序,以及一个共享契约项目。

API 参考现在也覆盖了服务器 SDK,位于 ReadyM.Relay.Server.SdkReadyM.Wukong.Common 之下。

自定义数据同步

0.3.0 中,网络化组件是双向同步的。在你的模组两半都引用的项目里声明一个带 [DeriveINetworkedComponent]partial struct,在每一侧注册它,把它挂到原型上,之后数值就会自行保持同步。不需要消息传递代码,也不需要手写序列化。

组件,位于共享项目中
[DeriveINetworkedComponent]
[StructLayout(LayoutKind.Auto)]
public partial struct BountyComponent
{
private int _kills;
private float _multiplier;
}

服务器模组在 RegisterComponents 中注册它,并在 Init 中挂载:

registry.RegisterComponent<BountyComponent>();
// ...
archetypeRegistry.ModifyArchetype(WukongArchetypes.GlobalPlayerArchetype, b => b.Add<BountyComponent>());

客户端模组在它的 Initialize 中做同样的事,通过 IComponentApi 和一个 IArchetypeRegistration

services.Resolve<IComponentApi>().RegisterComponent<BountyComponent>();
services.RegisterSingleton<IArchetypeRegistration, BountyRegistration>();

两边指定的是同一个原型:服务器端用 WukongArchetypes,客户端用 WukongApi.Archetypes。客户端一侧参见自定义组件,服务器一侧参见注册组件与原型

有一条规则值得记牢:在两边注册相同的组件,并保持相同顺序。组件 ID 是按位置分配的,并以一个字节的形式在网络上传输,所以不匹配时不会明确报错,而是会把数据流读错。把定义放在共享项目里、每一侧的注册都集中在一处,就足以避免这个问题。

如果客户端没有必要看到某个值,就跳过上面这一切,改用本地组件。它们永远不离开服务器,也不占用任何网络流量。

管理面板中的 Network 视图

面板新增了 Network 标签页,以一秒为窗口显示实时中继流量:入站与出站、已连接对端、服务器 tick 耗时,以及协议开销。

作为模组作者,值得你关注的是按组件的细分。它会把负载字节归因到各个 ECS 组件,并给出每个组件的扇出,这样在你发布一个每 tick 都同步的组件之前,就能看清它到底要花多少代价。tick 耗时图表覆盖的正是你的系统所运行的那次更新,因此某个系统每 tick 做得太多,最先会在这里显现。

查看它需要 Dashboard access 权限。这些数字怎么读,特别是线路字节与负载字节的区别,请参见网络统计

联机合作模式的修复

  • NPC 可能变为敌对并卡住任务。 怪物在共享世界中创建时用的是默认队伍 ID,而不是游戏分配给它们的队伍,因此任务 NPC 可能变成敌对,导致任务无法完成。现在它们会保留自己真实的队伍。
  • 塔区域的减益同步。 第三章塔区域中周期性的“迷魂曲”减益现在会在玩家之间保持同步。它的周期在服务器上运行,因此区域内所有人收到相同的预警和相同的生效时间段。
  • Boss HP 缩放移到服务器,并改了默认值。 按玩家数量缩放精英与 Boss 的 HP 现在是服务器端系统,而不再是客户端逻辑,默认值也变了。以前是 100% 加上每多一名玩家 150%:单人 100%,两人 250%,三人 400%。现在是每名玩家固定 100%:单人 100%,两人 200%,三人 300%。
  • 新增 bosshp 命令。 bosshp <百分比> 设置每名玩家对应的倍数,例如 bosshp 150 表示每名玩家 150%。它作用于整个服务器,并会在聊天中确认新的数值。

接下来做什么?

我们正在继续把对战模组的逻辑迁移到服务器端模组中。完成之后,0.2.0 中引入的临时 PvP 和 Cheats API 就可以按计划移除了。

再往后,我们计划把 SDK 本身开源,客户端和服务器端都包括在内。联机合作模组和对战模组从五月起就已经公开,把它们下面这一层也开放出来是顺理成章的下一步:你可以读到自己模组所依赖的代码,而修复也不必再等我们。等我们定下时间会再作说明。

迁移指南

把客户端模组从 0.2.4 更新到 0.3.0

  • 把服务器二进制更新到 0.3.0。旧版服务器无法加载基于 0.3.0 SDK 构建的模组。
  • 针对 0.3.0 SDK 重新编译你的模组。下载最新的模组模板并把你的模组文件复制过去,然后更新 manifest.json 依赖项中的最低 SDK 版本。
  • RPC 类的基类变了。 RpcClassBase 现在是 ClientRpcHandler,并且不再在构造函数中接收 IRpcClientIRelaySerializer,因为 SDK 会注入它们。请把 public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer) 替换为 public partial class MyRpc : ClientRpcHandler
  • RunOnMainThread 已移除。 [RpcEvent] 处理程序现在会由 SDK 调度到游戏线程上,所以请去掉那层回调包装,把方法体直接放进处理程序里。参见自定义 RPC
  • 存档文件 API 的类型换了命名空间。 SaveFileTypeFileInfo 不再位于 WukongMp.Sdk,它们现在在 ReadyM.Api.Saves 中,与服务器共享。请更新你的 using 指令。
  • 如果你写了一个继承 ServerRpcClient 的类来与服务器端模组通信,它必须用 [ServerRpcFor(typeof(YourContracts))] 标注它所实现的契约类。服务器端的 ServerRpcHandlersBase 同理。只使用 [RpcEvent] 的类不受影响。