有没有办法将 Y 旋转轴压缩到一个字节?

时间:2021-02-28 18:38:47

标签: c# unity3d networking compression

我正在制作一个 Unity 多人游戏,我想将 Y 旋转轴从发送整个四元数压缩到只发送一个字节。

  1. 我的第一次压缩尝试:
  • 我没有发送四元数,而是发送了一个 Y 轴浮点值
  • 结果: 16 字节 -> 4 字节(总共节省了 12 字节)
  1. 第二次压缩尝试:
  • 我缓存了 lastSentAxis 变量 (float),其中包含已发送到服务器的最后一个 Y 轴值
  • 当玩家改变他们的旋转方向(向右/向左看)时,会将新的 Y 轴与缓存的 Y 轴进行比较,并准备一个 delta 值(delta 保证小于 255)。
  • 然后,我创建一个新的 sbyte - 其中包含旋转方式(-1,如果向左转,1,如果向右转)
  • 结果: 4 个字节 -> 2 个字节(保存了 2 个字节,总共 14 个)
  1. 第三次压缩尝试(失败)
  • 定义一个字节标志,而不是创建之前提到的分隔字节(1 - 左,2 - 右)
  • 获取增量旋转值(如前所述),但将其添加到字节标志
  • 问题:我循环了 0 到 255 以找出哪些数字会与字节标志发生冲突。
  • 可能的解决方案:检查 flag + delta 是否在冲突编号列表中。如果是,请不要发送轮换请求。
  • 每 X 个请求,发送一个修正浮点值
  • 潜在结果: 2 个字节 -> 1 个字节(保存 1 个字节,总共 15 个)

我的问题是,是否有可能以更......适当的方式进行第三次压缩尝试,或者我的潜在解决方案是我唯一可以实现的?

1 个答案:

答案 0 :(得分:3)

我不会声称您总共节省了 15 个字节^^

如果无论如何您只需要一个旋转组件,那么同步单个浮点数(4 个字节)的第一步似乎非常明显;)

我还想说,超出这个范围听起来有点像不必要的微优化。


增量同步非常聪明,乍一看是从 4 个字节到 2 个字节的 100% 改进。

但是

  • 它也很容易出错,如果只有一个传输失败,可能会不同步。

  • 这当然会将精度降低到 1 度整数步长,而不是完整的 float 值。

老实说,我会坚持使用 4 个字节,只是为了稳定性和精度。


2 字节 - 约 0.0055° 精度

使用 2 个字节,您实际上可以做得比尝试更好!

为什么仅仅为了值的符号而浪费整个字节?

使用 short

  • 使用单个作为符号
  • 还有 15 位留给值!

您只需要将 -180180 的浮点范围映射到 -3276832767 范围。

发送

// your delta between -180 and 180
float actualAngleDelta;

var shortAngleDelta = (short)Mathf.RondToInt(actualAngleDelta / 180f * shortMaxValue);
var sendBytes = BitConverter.GetBytes(shortAngleDelta);

接收

short shortAngleDelta = BitConverter.ToInt16(receivedBytes);
float actualAngleDelta = (float) shortAngleDelta / (float)short.MaxValue * 360f;

但老实说,你不应该同步增量而是同步实际值。

所以,使用 ushort

它涵盖了从 065535 的值,因此只需在其上映射可能的 360 度。当然你会失去一点精度,但不会下降到全度;)

// A value between 0 and 360
float actualAngle;

ushort ushortAngle = (ushort) Mathf.RoundToInt((actualAngle % 360f) / 360f * ushort.MaxValue);
byte[] sendBytes = BitConverter.GetBytes(ushortAngle);

接收

ushort ushortAngle = BitConverter.ToUInt16(receivedBytes, 0);
float actualAngle = (float)ushortAngle / (float)ushort.MaxValue * 360f;

两者都保持低至约 0.0055 (= 360/65535) 度的精度!


单字节 - 约 1.41° 精度

如果您仍然可以选择较低的精度,那么您可以完全幻想并说您不会以度为单位同步每个精确的旋转角度,而是将圆划分为 256 步而不是 360 度。

然后,您可以将增量映射到较小粒度的“度”角,并且可以在单个字节中覆盖整个圆:

发送

byte sendByte = (byte)Mathf.RoundToInt((actualAngle % 360f) / 360f * (float)byte.MaxValue); 

收到

float actualAngle = receivedByte / (float)byte.MaxValue * 360f;

其精度约为 1.4 度。


但老实说,所有这些来回计算真的值得节省 2/3 字节吗?