网络同步 - scutrobotlab/RM2022_SimulatorX GitHub Wiki
在网络同步方案上,我们沿用了 Mirror 网络库。SimulatorX 所有的逻辑(包括车辆运行、裁判系统)都运行在服务端,客户端只是做视觉效果展示。
除了基本的位置、姿态同步外,为了让跨端同步与 Flux 架构相容,我们对 Flux 架构进行了一些延伸。
Flux 架构中的 Action 都在服务端流动,为了在保持设计一致性的同时,方便服务端实体控制客户端的视觉效果(车轮旋转、灯条闪烁等),我们从 StoreBase 类比出了 StoreChildBase 类。Child 类型可以与 Store 类组成层级关系。举个例子,步兵机器人由 InfantryStore 控制,他的四块装甲板继承于 StoreChildBase。当装甲板受到击打时,会发送相应 Action 通知 InfantryStore;而当 InfantryStore 判断需要启用、停用装甲板时,会发送 ChildAction 通知其子级装甲板。
/// <summary>
/// 装甲板控制器。
/// </summary>
public class Armor : StoreChildBase
{
public Identity.Camps camp;
public bool defaultOn;
public char text;
...
/// <summary>
/// 处理事件。
/// </summary>
/// <param name="action"></param>
public override void Receive(IChildAction action)
{
base.Receive(action);
switch (action.ActionName())
{
case ChildActionID.Armor.SyncArmor:
var syncAction = (SyncArmor) action;
camp = syncAction.Camp;
text = syncAction.Text;
RegisterChild(transform);
break;
...
}
...
}
...
}
这样设计的好处是,步兵无需关心自己安装了几块装甲板,甚至没有装甲板也没关系。组件之间可以解耦,任意组合。
Action 只在服务端流动,推动游戏逻辑运行;ChildAction 则会被跨端同步,将视觉效果信息同步到每一个客户端。为了在同步的同时保留类型信息,需要用到多态序列化、反序列化,(如果使用 Protobuf 就需要写 DSL 了,msgpack 似乎是更合理的选择,但)最后还是造了轮子。
// 别问,问就是轮子。
public static class PolymorphicSerializer
{
public static string Serialize(object value)
{
...
由于每一帧客户端都需要上报输入状态,我们简单压缩了一下输入信息:
// 在客户端打包输入数据,发送到服务端。
var primaryAxis = BitUtil.CompressAxisInput(InputManager.Instance().primaryAxis);
var secondaryAxis = BitUtil.CompressAxisInput(InputManager.Instance().secondaryAxis);
var input = new BitMap32();
input.SetByte(2, primaryAxis);
input.SetByte(3, secondaryAxis);
input.SetBit(2, InputManager.Instance().ButtonStatus[InputActionID.FunctionA]);
input.SetBit(3, InputManager.Instance().ButtonStatus[InputActionID.FunctionB]);
研判了一下所需精度后,我们将部分二维杆量输入压缩到了一个字节,将普通按键输入存储为 bit ,最后每一帧的所有输入信息可以被压缩到四个字节大小。
另外,ChildAction 在同步过程中会产生大量的冗余信息。例如,装甲板每一帧都会收到状态更新信息,一个只是表示点亮状态的信息每帧都被同步,导致占用大量带宽。在发送 ChildAction 之前进行简单的去重就可以降低服务器的带宽压力。
(40000%)
Mirror 是一个为 Unity 2019 LTS 及以上版本设计的网络同步框架,隐藏了不同底层传输协议的复杂性。其封装简单明了,能够有效提高开发效率。此框架之所以得名“Mirror”,是因为客户端与服务端软件实际上共用一套代码,不需要维护另外的服务器软件。