使用绝对最小位动态存储数值?

时间:2017-10-26 14:05:19

标签: c# unity3d networking

我正在使用C#/ Unity制作游戏,并希望最大限度地减少带宽使用。每秒十次,服务器通过更改单位位置来更新客户端。服务器仅在数据已更改时才发送数据,并仅发送此记录值和上次记录的值之间的差异;这可能是积极的或消极的。任何时刻都可能有零到几千个单位。

每个单位的默认数据是一个ushort和四个浮点变量(id,x,y,z,direction)。这将是18字节+对象(序列化)和数据报字节。现在我正在发送它,但是只有一千个单位运行... 18 * 10 * 1000 = 180kb / s太简单了。

这可以简化为整数,因为我只关心说两个小数点的精度。例如:13.35 - 13.10 = 0.25 ...我们可以存储为25.如果很多值将远小于255(一个字节),那么最佳解决方案将允许我们仅动态存储所需的位数加上尽量少开销。该解决方案还允许我们发送更大的值,如果需要的话。

这对其他服务器命令很有用,例如对单位施加伤害,这需要id和损坏值。在大多数情况下,伤害值可能不到20。

使用C#如何以尽可能少的位动态存储所需数据以进行网络传输?

2 个答案:

答案 0 :(得分:1)

编辑:添加了对负值的支持。

我认为你正在寻找这样的东西:

public static UInt32 Pack(float dx, float dy, float dz)
{
    // Track if the incoming values are negative or not.
    // If they are, make them positive.
    bool ndx = false;
    bool ndy = false;
    bool ndz = false;

    if (dx < 0)
    {
        dx *= -1;
        ndx = true;
    }

    if (dy < 0)
    {
        dy *= -1;
        ndy = true;
    }

    if (dz < 0)
    {
        dz *= -1;
        ndz = true;
    }

    // breakdown:
    //
    // (dx * 100)
    // Take a decimal value and scale it to an integer value. So 0.25 becomes 25.
    //
    // (int)(dx * 100)
    // Truncate to an integer.
    //
    // ((int)(dx * 100) % 100)
    // If this was something like 4.25 it would have been 425. This takes only
    // the last two digits (mod 100).
    // This will go into the lowest 8 bits.
    // 
    // ((int)(dy * 100) % 100) << 8
    // Do the same, but stuff it into bits 8-15.
    //
    // ((int)(dz * 100) % 100) << 16
    // Do the same, but stuff it into bits 16-23.
    var packed = (UInt32)(((int)(dx * 100) % 100) | ((int)(dy * 100) % 100) << 8 | ((int)(dz * 100) % 100) << 16);

    // Since 100 is less than 128, doing "% 100" will result in a seven bit number.
    // If the incoming value was negative, set the highest bit to flag that:
    if (ndx)
        packed |= (uint)0x80;
    if (ndy)
        packed |= (uint)0x8000;
    if (ndz)
        packed |= (uint)0x800000;

    return packed;
}

public static void Unpack(UInt32 input, out float dx, out float dy, out float dz)
{
    // breakdown:
    //
    // input & 0x7f
    // Take the first 7 bits.
    //
    // (float)(input & 0x7f)
    // Cast to a float value. So 25 becomes 25f.
    //
    // (float)(input & 0x7f) / 100
    // Shift back down to a deci number. So 25 -> 0.25.
    //
    // (float)((input & 0x7f00) >> 8) / 100;
    // Do the same thing, but with bits 8-14.
    //
    // (float)((input & 0x7f0000) >> 16) / 100;
    // Do the same thing, but with bits 15-23.
    dx = (float)(input & 0x7f) / 100;
    dy = (float)((input & 0x7f00) >> 8) / 100;
    dz = (float)((input & 0x7f0000) >> 16) / 100;

    // If the negative flag is set, make this a negative value:
    dx *= (input & (uint)0x80) > 1 ? -1f : 1f;
    dy *= (input & (uint)0x8000) > 1 ? -1f : 1f;
    dz *= (input & (uint)0x800000) > 1 ? -1f : 1f;
}

正如您在此处所见,此解决方案可能会产生舍入错误:

> var p = Pack(0.25f, -.67f, -0.12f);
> Unpack(p, out a, out b, out c);
> a
0.25
> b
-0.67
> c
-0.11
> 

答案 1 :(得分:1)

(首先,我认为这个可能过早优化。我是否已经完全确定在这个问题上没有结构/架构方法?减少远程对象的更新速率?等?)

反正:

请不要立即解除这个想法(由于CPU复杂性),但您可能需要考虑深入了解ZIP和JPG格式的工作原理。

我提到的原因是因为ZIP的基本工作方式是使用&#34;可变长度&#34;存储。而不是说&#34;每个数据块都是一个字节&#34;而是通过找出表示数据的好方法来压缩事物。

想象一下,我正在存储一个文本文件:

Seeeeeeeeeee meeeeeeeeeee leeeeeeeeeeeeeeeean freeeeeeeeeeeeeely.

...常规文本文件每个字符需要一个字节,或520位。但为什么?实际上,你会期望该文件中的平均字符是&#39; e&#39; - 如果你只是用一个比特来代表&#39; e&#39;字符?   如果第一位是&#39; 0&#39;则它是&#39;&#39;否则,您必须阅读更多位以确定它是什么字符。也许是这样的:

0: 'e'
100: ' '
101: 'l'
11000: 'S'
11001: 'm'
11010: 'a'
11011: 'n'
11100: 'f'
11101: 'r'
11110: 'y'
11111: '.'

字符串占用多少空间? 52个每个一个位,3个空格和2个每个3位的1个,以及每个5位的8个其他字符:52x1 + 5x3 + 8x5 = 107位。 (有更好的方法来压缩该字符串,但我试图保持简单。)

这如何适合您的问题?

你的很多的价值很低 - 你越接近0,你就越有可能看到它(你会有更多)如果你希望大多数值可以存储在4位中,为什么要使用8位来存储大部分值?

我不是说&#34;在你的数据上调用一个zip库&#34; (尽管这可能有用 - 我从来没有尝试过在小内存段上查看它的时间密集程度) - 但是如果你深入研究可变长度编码,你可能会想出一个好的方案来表示使用这种方法的数据。

编辑:

...在从我的结尾做了一些额外的谷歌搜索之后,你可能想看看System.IO.Compression.GZipStream。