在多人游戏中,如何有效地通过套接字发送位置数据?

In a multiplayer game, how to efficiently send position data over sockets?

我目前正在为 Unity 游戏构建自己的网络代码(用于学习经验),我 运行 在 "decoding packets" 方面遇到了一些严重的延迟。

基本上,我有 playerObject 将它们的位置数据 (Vector3(x,y,z)) 作为 JSON 字符串发送到服务器,然后服务器将其发回给所有其他玩家。

套接字方面进展顺利。从创建数据包到接收数据包的延迟非常小。

但是我我的客户在尝试取消JSON远程客户的位置时遇到了巨大的延迟:

Vector3 remotePos = JsonUtility.FromJson<Vector3>(_command.Substring(5 + _usernameLength));

Json 字符串在字符串的开头有一个标识符,表示这是一个位置更新,后面跟着两个数字,表示用户名的长度,然后是用户名(以便远程客户端可以更新正确的 playerObject。整个字符串看起来像这样。

POS09NewPlayer{"x":140.47999572753907,"y":0.25,"z":140.7100067138672}

从服务器收到这样的数据包后,我的客户端将执行以下任务:

        int _usernameLength = Int32.Parse(_command.Substring(3, 2));
        string _username = _command.Substring(5, _usernameLength);
        Vector3 remotePos = JsonUtility.FromJson<Vector3>
        if (_username != username)
        {
        playerDict[_username].transform.position = remotePos;
        } 

所有这些 "works",但在只有 3 个客户端同时连接后变得非常缓慢。

我做错了什么?一定有一个重大缺陷,因为我每 .015 秒只为 3 名玩家发送更新,而像战地风云这样的游戏每秒可以为 64 名玩家发送 60 次更新!

如有任何建议,我们将不胜感激。

嗯,肯定有优化的空间。由于您引用的是大型游戏引擎作为参考,我将根据较旧的 Unreal Engine 版本和他们正在使用的一些优化给您一些示例:

  • 网络包使用UDP协议。它仍然需要自己处理丢失的、重复的或乱序的包,但它吞下了苦果而不是使用实际的 TCP 连接,因为后者会引入更多的开销。
  • 服务器跟踪它发送给客户端的信息更新。对于服务器复制的客户端的每个对象,服务器都会跟踪所有变量。在每个游戏循环更新结束时,服务器将检查哪些变量实际上具有与上次服务器向客户端发送更新时不同的值。并且只有当实际存在差异时,新值才会发送给客户端。这似乎是一个令人难以置信的开销,但网络带宽是比系统内存和 CPU 时间稀缺得多的资源。
  • 矢量信息在通过网络发送之前实际上被截断了。引擎使用浮点数作为其矢量分量的数据类型,但它仍将 X、Y 和 Z 舍入为最接近的整数并传输它们而不是完整的浮点数据。这样它们只占用几个位到几个字节,而不是未压缩的原始浮点数的 12 个字节。
  • 位置更新在客户端模拟,并与来自服务器的传入数据合并。为了避免位置更新引起的抖动,客户端根据对象的速度值预测对象的新位置。当客户端稍后从服务器接收到位置更新时,它会优雅地将对象稍微移动到新位置(基本上是客户端和服务器坐标之间的折衷)并且只有在服务器的位置与客户端的版本。这是用很多东西完成的。如果玩家发射弹丸,则该弹丸将在服务器上执行,服务器将创建弹丸并将其发送给玩家以更新其位置。但玩家也会收到关于射击的函数调用并在本地创建射弹并更新其轨迹。即使该射弹不能在客户端真正杀死玩家,它的轨迹和效果仍然可以在本地模拟,以给人流畅的游戏体验。如果它击中了什么,可以在该位置播放适当的效果,而无需服务器告诉客户端创建这些效果。
  • 服务器跟踪与每个玩家相关的内容。如果玩家位于地图的另一侧,在几堵墙后面,则不需要向您发送有关该玩家动作的任何更新。如果稍后那个玩家的弹丸恰好进入你的视野,服务器仍然可以在适当的时候告诉你这些弹丸。

整个事情比我能塞进的东西要复杂得多post,但它的要点可能是:网络带宽是多人游戏中最有限的资源,所以引擎运行尽量发送尽可能少的信息包。

注意到什么了吗?对于该引擎中的整个矢量,位置矢量的不到一个分量就已经被认为太大了。我想 JSON 确实在这里引入了一些开销,仅仅是因为它的冗长。当您也可以只发送它们的实际位并从实际包中推断出它们的含义时,您将数字作为字符串发送。