有没有办法将Y旋转轴压缩到一个字节?
Is there a way to compress Y rotation axis to one byte?
我正在制作一个 Unity 多人游戏,我想将 Y 旋转轴从发送整个四元数压缩为只发送一个字节。
- 我的第一次压缩尝试:
- 我没有发送四元数,而是发送了一个 Y 轴浮点值
- 结果: 16 字节 -> 4 字节(总共节省 12 字节)
- 第二次压缩尝试:
- 我缓存了 lastSentAxis 变量(浮点型),其中包含已发送到服务器的最后一个 Y 轴值
- 当玩家改变他们的旋转(看起来right/left)时,一个新的Y轴将与缓存的进行比较,并准备一个delta值(delta保证小于255)。
- 然后,我创建一个新的 sbyte - 其中包含旋转方式(-1,如果向左转,1,如果向右转)
- 结果: 4 个字节 -> 2 个字节(保存 2 个字节,总共 14 个)
- 第三次压缩尝试(失败)
- 定义字节标志而不是创建之前提到的分隔字节(1 - 左,2 - 右)
- 得到一个增量旋转值(如前所述),但将其添加到字节标志中
- 问题:我从 0 到 255 循环查找哪些数字会与字节标志冲突。
- 可能的解决方案:检查 flag + delta 是否在冲突号码列表中。如果是,请不要发送轮换请求。
- 每X个请求,发送一个校正浮点值
- 可能的结果: 2 个字节 -> 1 个字节(保存 1 个字节,总共 15 个)
我的问题是,是否有可能以更...正确的方式进行第三次压缩尝试,或者我的潜在解决方案是我唯一可能实现的?
我不会说你总共节省了 15 个字节 ^^
如果您只需要旋转的一个组成部分,那么同步单个浮点数(4 字节)的第一步似乎非常明显;)
我还要说,超出这个范围听起来有点像不必要的微优化。
增量同步非常巧妙,乍一看是从 4 字节到 2 字节的 100% 改进。
但是
它也很容易出错,如果只有一个传输失败,可能会不同步。
这当然会将精度降低到 1 度整数步,而不是完整的 float
值。
老实说,为了稳定性和精度,我会坚持使用 4 个字节。
2 个字节 - 大约 0.0055° 精度
有了 2 个字节,您实际上可以比您的尝试做得更好!
为什么仅仅为了值的符号就浪费整个字节?
使用 short
- 对符号
使用单个位
- 还有 15 位 留给值!
您只需要将 -180
到 180
的浮点范围映射到 -32768
到 32767
的范围。
发送中
// 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
!
它涵盖了从 0
到 65535
的值,因此只需在其上映射可能的 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 字节吗?
我正在制作一个 Unity 多人游戏,我想将 Y 旋转轴从发送整个四元数压缩为只发送一个字节。
- 我的第一次压缩尝试:
- 我没有发送四元数,而是发送了一个 Y 轴浮点值
- 结果: 16 字节 -> 4 字节(总共节省 12 字节)
- 第二次压缩尝试:
- 我缓存了 lastSentAxis 变量(浮点型),其中包含已发送到服务器的最后一个 Y 轴值
- 当玩家改变他们的旋转(看起来right/left)时,一个新的Y轴将与缓存的进行比较,并准备一个delta值(delta保证小于255)。
- 然后,我创建一个新的 sbyte - 其中包含旋转方式(-1,如果向左转,1,如果向右转)
- 结果: 4 个字节 -> 2 个字节(保存 2 个字节,总共 14 个)
- 第三次压缩尝试(失败)
- 定义字节标志而不是创建之前提到的分隔字节(1 - 左,2 - 右)
- 得到一个增量旋转值(如前所述),但将其添加到字节标志中
- 问题:我从 0 到 255 循环查找哪些数字会与字节标志冲突。
- 可能的解决方案:检查 flag + delta 是否在冲突号码列表中。如果是,请不要发送轮换请求。
- 每X个请求,发送一个校正浮点值
- 可能的结果: 2 个字节 -> 1 个字节(保存 1 个字节,总共 15 个)
我的问题是,是否有可能以更...正确的方式进行第三次压缩尝试,或者我的潜在解决方案是我唯一可能实现的?
我不会说你总共节省了 15 个字节 ^^
如果您只需要旋转的一个组成部分,那么同步单个浮点数(4 字节)的第一步似乎非常明显;)
我还要说,超出这个范围听起来有点像不必要的微优化。
增量同步非常巧妙,乍一看是从 4 字节到 2 字节的 100% 改进。
但是
它也很容易出错,如果只有一个传输失败,可能会不同步。
这当然会将精度降低到 1 度整数步,而不是完整的
float
值。
老实说,为了稳定性和精度,我会坚持使用 4 个字节。
2 个字节 - 大约 0.0055° 精度
有了 2 个字节,您实际上可以比您的尝试做得更好!
为什么仅仅为了值的符号就浪费整个字节?
使用 short
- 对符号 使用单个位
- 还有 15 位 留给值!
您只需要将 -180
到 180
的浮点范围映射到 -32768
到 32767
的范围。
发送中
// 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
!
它涵盖了从 0
到 65535
的值,因此只需在其上映射可能的 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 字节吗?