WukongMP SDK 0.3.0 发布
服务器端脚本来了。模组现在可以在中继服务器内部运行自己的代码:在服务器实体上注册组件、在服务器循环上 tick 系统,以及通过 RPC 响应客户端的请求。这就是我们此前说过会做的那部分,联机合作模组已经跑在它上面了。
自定义组件现在也支持双向同步,因此模组可以把自己的状态放在实体上,让服务器和每个客户端对它保持一致,而不需要编写任何消息传递代码。
本版本还为管理面板新增了 Network 视图,它会显示中继实际发送的内容,并按 ECS 组件细分。除此之外还有三项联机合作模式的修复,其中包括 NPC 可能变为敌对并卡住任务的问题。
请把服务器二进制更新到 0.3.0。基于 0.2.4 构建的客户端模组需要重新编译,并做少量代码改动,参见迁移指南。
服务器端模组
服务器端模组是一个 .NET 类库,由中继服务器从它的 server_mods/ 目录加载,这个目录与面向客户端的 mods/ 文件夹是分开的。把程序集放进去,服务器启动时就会加载它。
它能做什么:
- 服务器实体上的组件,可以是本地的(仅服务器)或网络化的,挂到内置原型上,或挂到你的模组注册的新原型上。
- 系统,在服务器的更新循环上 tick,适用于需要持续运行、而不是响应单个客户端的逻辑。
- 服务器 RPC,客户端模组与服务器之间的请求与响应通道,只需定义一次,作为两边共享的契约。
请从快速上手开始,并阅读原型与组件了解内置实体都带有哪些组件。联机合作模组是这三者的一个小型完整示例:两个系统、一个 RPC 处理程序,以及一个共享契约项目。
API 参考现在也覆盖了服务器 SDK,位于 ReadyM.Relay.Server.Sdk 和 ReadyM.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.0SDK 构建的模组。 - 针对
0.3.0SDK 重新编译你的模组。下载最新的模组模板并把你的模组文件复制过去,然后更新manifest.json依赖项中的最低 SDK 版本。 - RPC 类的基类变了。
RpcClassBase现在是ClientRpcHandler,并且不再在构造函数中接收IRpcClient和IRelaySerializer,因为 SDK 会注入它们。请把public partial class MyRpc(IRpcClient client, IRelaySerializer serializer) : RpcClassBase(client, serializer)替换为public partial class MyRpc : ClientRpcHandler。 RunOnMainThread已移除。[RpcEvent]处理程序现在会由 SDK 调度到游戏线程上,所以请去掉那层回调包装,把方法体直接放进处理程序里。参见自定义 RPC。- 存档文件 API 的类型换了命名空间。
SaveFileType和FileInfo不再位于WukongMp.Sdk,它们现在在ReadyM.Api.Saves中,与服务器共享。请更新你的using指令。 - 如果你写了一个继承
ServerRpcClient的类来与服务器端模组通信,它必须用[ServerRpcFor(typeof(YourContracts))]标注它所实现的契约类。服务器端的ServerRpcHandlersBase同理。只使用[RpcEvent]的类不受影响。
