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

4

我目前正在为Unity游戏构建自己的网络代码(为了学习经验),但是在“解码数据包”的过程中遇到了一些严重的延迟。

基本上,我有一个名为playerObject的物体,它将其位置数据(Vector3(x,y,z))作为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;
        } 

所有这些“都可以”,但在同时连接3个客户端后变得非常缓慢。我做错了什么?肯定存在重大缺陷,因为我每0.015秒只发送3个玩家的更新,而像战地游戏这样的游戏可以为64个玩家每秒发送60个更新!任何建议将不胜感激。

1
至少将 JsonUtility.FromJson 移动到 if 语句中。 - hyankov
我非常怀疑 JSON 反序列化在这种情况下会造成如此巨大的影响。 - Evk
@Evk 我在计算接收到的数据包,同时也在计算 JSON 格式的数据包。我接收数据包的速度大约是能够将其转换为 JSON 格式的三倍。这意味着处理速度严重滞后。 - MrDysprosium
1个回答

18

当然,还有优化的空间。既然你引用了大型游戏引擎作为参考,我会给你一些基于旧版虚幻引擎的示例以及它们使用的一些优化技巧:

  • 网络数据包使用UDP协议。它仍然需要自己处理丢失、重复或乱序数据包的问题,但是它选择咽下这个苦果而不是使用实际的TCP连接,因为后者会引入更多的开销。
  • 服务器跟踪它向客户端发送的信息更新。对于每个由服务器进行复制的客户端对象,服务器会跟踪所有变量。在每个游戏循环更新结束时,服务器将检查哪些变量与服务器上次发送更新到客户端时的数值不同。只有当确实存在差异时,新值才会被发送到客户端。这似乎是一个令人难以置信的开销,但是网络带宽比系统内存和CPU时间更为稀缺。
  • 在发送到网络之前,向量信息实际上被截断。引擎使用浮点数作为其向量组件的数据类型,但仍然会将X、Y和Z四舍五入到最近的整数并传输它们,而不是传输未压缩的原始浮点数据。这样它们只占用了几个比特到几个字节,而不是未压缩的原始浮点数的12个字节。
  • 位置更新在客户端进行模拟,并与服务器传入的数据合并。为了避免位置更新导致的抖动,客户端会从物体速度值预测其新位置。当客户端稍后从服务器接收到位置更新时,它会将物体平稳地移动到新位置(基本上是客户端和服务器坐标之间的折衷),仅在服务器位置与客户端版本差异较大时进行强制更新。这个过程涉及许多内容。如果一名玩家发射了一个抛射物,在服务器上执行该操作,服务器将创建抛射物并将其发送给玩家以更新其位置。但玩家也会收到有关射击的函数调用,本地创建抛射物并更新其弹道。即使该抛射物在客户端上不能真正杀死一名玩家,其弹道和效果仍然可以在本地模拟,以给出平滑游戏的印象。如果它击中某些东西,则可以在该位置播放适当的效果,而无需要求服务器告诉客户端创建这些效果。
  • 服务器跟踪每个玩家的相关信息。如果一名玩家在远离您的屏幕另一侧且在多面墙后面,服务器不需要向您发送任何有关该玩家行动的更新。如果该玩家的抛射物在稍后进入您的视野,服务器仍然可以在适当的时间告诉您有关这些抛射物的信息。
  • 整个过程比我在这篇文章中所能涵盖的更为复杂,但大意可能是:网络带宽是多人游戏中最受限制的资源,因此引擎会尽其所能减少发送的信息包数量和大小。

    注意到了吗?即使只有位置向量的一个分量的值已经被认为太大,这个引擎仍然接受它。我猜 JSON 确实会由于其冗长而引入一些开销。你正在将数字作为字符串发送,而实际上可以直接发送它们的实际位数并从实际信息包中推断出其含义。


    2
    哇,感谢您的写作...有很多有用的信息。您可以详细说明一下最后一部分吗?“您也可以只发送它们的实际位并从实际数据包中推断其含义。” - MrDysprosium
    这更像是一种提示,实际的引擎可能就是这样做的,因为它们可能不使用JSON。如果您使用UDP这样的基本协议,可以自由地发送任何字节(每个数据包最大约为65000个字节)。然后,您可以有几个字节来标识玩家,一些来标识正在发送的数据,然后只需为向量的实际数值留下几个字节,然后在客户端自己解析它。在.NET中使用UDP的示例:https://social.msdn.microsoft.com/Forums/en-US/92846ccb-fad3-469a-baf7-bb153ce2d82b/simple-udp-example-code?forum=netfxnetcom - Crusha K. Rool
    1
    请记住,UDP本身不会处理重复、丢失或乱序的数据包,因此您可能需要自己处理这些问题。这意味着发送某种形式的确认消息以表明已接收到数据包,并在定期间隔内重新发送数据包,直到收到确认消息为止。同时,在发送数据包时进行时间戳记录,以便在接收端如果有更新的数据到达,则可以删除旧数据(但仍需发送确认消息,以防发送方继续发送这些数据包)。 - Crusha K. Rool

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接