首页 > 解决方案 > 如何使用客户端-服务器架构有效地在游戏中发送对象

问题描述

我制作了一个联网的 2d 自上而下的射击游戏。这是相当标准的,用户使用 WASD 移动他们的玩家,并且可以使用 12345 和鼠标施法。用户必须杀死敌人并保持生命。

我已经将它联网,供多个用户在同一个竞技场玩,但运行效率太低。

Sprites 存储在 aDictionary<string, sprite>中,它存储在客户端和服务器机器中。

它是这样实现的:

  1. KeyboardState用户的键盘和鼠标输入每帧 ( , )都会发送到服务器MouseState
  2. 服务器接受输入并更新游戏状态。
  3. 服务器向每个客户端发送他们的视口(矩形),以及该用户视口中所有精灵的位置(浮动,浮动),精灵键(字符串),旋转(浮动),颜色(int,int,int,int)。
  4. 服务器将此信息存储为保存这些值的类的实例。
  5. 要发送它,服务器使用 aBinaryFormatter序列化类,并使用 and 发送TcpClientSockets
  6. 客户端将传入的数据反序列化回类并将它们绘制到屏幕上

不幸的是,这使游戏变得异常缓慢。我只在一台机器上测试了这个,所以没有延迟,所以我假设发送的数据太多。

为了解决这个问题,我认为我需要有效地存储和发送这些数据,或者以不同的方式实现它。

为了有效地发送它,我想我需要将数据打包为字节并发送,但我不知道该怎么做。

我将如何有效地发送数据,或者以不同的方式实现它以使其高效?

标签: c#networkingserverxnatcpclient

解决方案


尽量减少通过网络的数据量。仅发送所需数量的数据并通过时间戳和验证提供您自己的验证。

下面的每个 actionId 和 AnimationId 都应显示最小(2 的幂)字节数,以唯一地描述所有可能的值。


服务器 -> 客户端:

服务器已知客户端位置和轮换。确认最后一个 actionId。所有其他客户端位置、方向(旋转)、animationIds 的列表......在客户端的视野范围内 +- maxRotationRate 在 MaxViewDistance +- maxMovementRate 的距离内。

客户端必须在每一步平均其当前位置与服务器位置。

在本地处理所有输入(鼠标和键盘),并且只将相关数据发送到服务器:

每次更新,客户端都会发送相关数据并更新自己的内部状态并处理给定的服务器状态,并增加 int 值(称为时间戳)。在此处包括奇偶校验或 CRC。


Client -> Server 当前位置、旋转、actionId、animationId 和一个单调递增的 int 值(称之为时间戳)。在此处包括奇偶校验或 CRC。

所有渲染决策、视口、当前动画/精灵/颜色都应该在客户端上建立(颜色和初始位置需要在登录时提供给客户端)。

位置数据将被共享(本地更新和服务器调整)。

每次更新都需要将位置、旋转和速度数据(线性和旋转)发送到服务器。在服务器收到后,必须进行验证过程以防止作弊。在错过、无序或格式错误的更新时,服务器将假定最后位置 + 速度 *(与上一个时间戳的时间差 + 当前服务器时间)。


必须使用 UDP 进行通信(请注意,您使用自己的时间戳和 CRC 来验证接收到的数据,不会再因无效数据而崩溃)。

仅处理奇偶校验/CRC 检查通过的数据包,并拒绝接收太短或未通过检查的数据包。这些检查将消除与坏数据包相关的崩溃。

TCP 的重试机制和有序投递对游戏性能不利。每次单个数据包丢失或无序交付时,时间增量错误(发送数据的时间与服务器处理它的时间)都会增加RTT(往返时间,即 Ping 时间),因为服务器会重试和数据包重新排序。这将导致几秒钟的延迟,只会随着游戏的进行而增加。

网络延迟和丢弃/格式错误的数据包是游戏的正常现象。在基于时间戳值的无序数据包的情况下,丢弃除最后一个之外的所有数据包,因为这表明玩家最近的愿望(移动)。随着射击(在你的情况下施法),继续发送法术位直到被确认(ActionId)。忽略服务器上第一个收到的咒语以外的所有咒语(向客户端发送确认),直到充电期到期。

虽然此答案排除了原始问题中未提出的所有点,主要是损坏,但应创建一个结构,当在根据定义的损坏分布规则分配损坏的服务器上产生损坏诱导元素的发生时,这需要适用于每个受影响的客户。


推荐阅读