服务端和客户端总是需要互相交换数据,来做到实时的游戏体验。
很久之前,我们的网速都不是很快,甚至带宽只有 1Mbps (128KB/s)这样的速度,作为当时一个网络实时对战游戏,每时每刻都要传递数据,为了完成各种各样复杂的效果,要传递的数据量自然也就多了。
为了减少需要发送的数据大小,引擎的程序员开发出了 Delta 模块。
它能把需要传递的数据的大小压缩到极致,大大减少了网络传输的数据量,使得当时网速很慢的电脑也能流畅地玩游戏!
如果你看过我的前两篇文章,你一定对 clientdata_t、entity_state_t 有些了解(如果你还没看过,我建议你去看看),查看结构体你会发现里面的成员大多数是 int、float (4字节变量)为了节约网络带宽占用,引擎还会对这些变量进一步处理,才能发给客户端。
这里就是 Delta 模块发挥的时候了,打开 valve/delta.lst 或者 cstrike/delta.lst 这个文件,能看到这样的代码:
clientdata_t none { DEFINE_DELTA( flTimeStepSound, DT_INTEGER, 10, 1.0 ), // ... DEFINE_DELTA( m_flNextAttack, DT_FLOAT | DT_SIGNED, 22, 1000.0 ), // ... DEFINE_DELTA( m_iId, DT_INTEGER, 5, 1.0 ), // ... }
首先你看到了眼熟的 clientdata_t 这个结构体名字,接着还有它的成员们。
我们可以看到 DEFINE_DELTA ( .. ) 里有一些参数,我们逐个来分析它们。
为了方便讲解,我们从 DEFINE_DELTA( m_iId, DT_INTEGER, 5, 1.0 ) 这行开始。
第一个参数 m_iId 毫无疑问表示 clientdata_t 里的 m_iId 这个成员变量。
第二个参数 DT_INTEGER 表示这个成员变量是 整数型。
第三个参数 5 表示这个变量最多占用多少个“位”(如果你不知道“位”是什么,我建议你搜索“位和字节”来复习一下基础)下文简称占位参数。
第四个参数 1.0 对于整数型成员变量来说,它总是 1.0 不需要改变
重点来啦!注意第三个参数,这里是 Delta 模块压缩数据的精髓!我们知道一个 int 型变量要占用 4字节 也就是 32位,它能存下一个超级大的数值,
但我们的变量可能不需要存这么大的数值,比如我的 m_iId 的值最大就 31,连 1字节 都不够,只需要 5位!
Delta模块会在发送数据前,根据这个配置文件来精简变量数据(压缩),把要发送的数据压缩到最小!
比如上面的 m_iId 这个变量,经过 Delta压缩,只剩下 5位,这远远小于一个 int(32位)!Delta 根据配置文件来压缩每个成员变量的大小,到最后,引擎要发送的数据将会变得非常小!
这里还要解释一下传递小数(FLOAT)时是如何计算位数的,上面提到了一个 1.0 的参数,实际上它是一个倍数,Delta模块在处理小数时,会把小数乘上这个倍数,得到一个整数,
比如我的 m_flNextAttack 里的数值是 12.55 乘 1000.0 变成 12550 这样一个整数,而这个整数至少需要 13位 才能存得下,所以占位参数至少要写 13 才能发送 12.55 这个小数给客户端。然后客户端再除以 1000.0 就得到了这个小数。
你一定注意到还有个 DT_SIGNED ,它表示这个变量允许保存负数,它会额外占用 1位 数据,所以写占位参数时,要考虑到它。
你可以在 delta.lst 里为每个成员变量配置大小,这是非常必要的。当你决定要传递一个新的数据时,请一定要记得配置 delta.lst 不然你的客户端可能会接收到一个错误的数值。
Delta模块还有十分强大的差异管理功能,服务端不会每一帧都发送完全相同的数据给客户端,这很没必要。Delta会过滤掉没有改变的数据,只发送有变化的部分数据给客户端,进一步减少了数据发送量!
相信你一定已经被Delta的强大吸引,但是经过Delta压缩的数据,还会经过更高级的数据压缩算法压缩才发送给客户端。。。
我总觉得我忘了什么事情,等我想起来再补充吧!