如何使用客户端-服务器架构在我的游戏中有效地发送对象
How can I efficiently send objects in my game with client-server architecture
我做了一个联网的2d俯视射击游戏。这是相当标准的,用户使用 WASD 移动他们的播放器,并且可以使用 12345 和鼠标施法。用户必须杀死敌人并保持生命。
我已经联网让多个用户在同一个竞技场玩,但是效率太低运行。
精灵存储在 Dictionary<string, sprite>
中,存储在客户端和服务器计算机中。
是这样实现的:
- 用户键盘和鼠标输入每帧发送到服务器(
KeyboardState
,MouseState
)。
- 服务器接受输入并更新游戏状态。
- 服务器向每个客户端发送它们的视口(矩形),以及所有精灵的位置(浮动,浮动),精灵键(字符串),旋转(浮动),颜色(整数,整数,整数,整数)在该用户的视口中。
- 服务器将此信息存储为包含这些值的 class 的实例。
- 为了发送它,服务器使用
BinaryFormatter
序列化 class,并使用 TcpClient
和 Sockets
. 发送它
- 客户端将传入数据反序列化回 classes 并将它们绘制到屏幕上
不幸的是,这让游戏变得异常缓慢。我只在一台机器上测试过,所以没有延迟,所以我假设发送的数据太多了。
要解决这个问题,我想我要么需要有效地存储和发送这些数据,要么以不同的方式实现。
为了高效发送,我想我需要将数据打包为字节并发送,但我不知道该怎么做。
我如何有效地发送数据,或者以不同的方式实现它以使其有效?
尽量减少通过网络的数据量。仅发送所需数量的数据并通过时间戳和验证提供您自己的验证。
下面的每个 actionId 和 AnimationId 都应该展示最小(2 的幂)字节数以唯一地描述所有可能的值。
服务器 -> 客户端:
服务器已知客户端位置和旋转。最后一个 actionId 的确认。
所有其他客户端位置、方向(旋转)、animationIds 的列表......在客户端的视野内 +- maxRotationRate 在 MaxViewDistance +- maxMovementRate 的距离内。
客户端每一步必须将其当前位置与服务器位置进行平均。
在本地处理所有输入(鼠标和键盘),并只将相关数据发送到服务器:
每次更新,客户端都会发送相关数据并更新自己的内部状态并处理给定的服务器状态,并增加int值(称之为时间戳)。在此处包括奇偶校验或 CRC。
客户端 -> 服务器
当前位置、旋转、actionId、animationId 和单调递增的 int 值(称之为时间戳)。在此处包括奇偶校验或 CRC。
所有渲染决策、视口、当前 animation/sprite/colour 都应在客户端建立(颜色和初始位置需要在登录时提供给客户端)。
将共享位置数据(本地更新和服务器调整)。
每次更新都需要将位置、旋转和速度数据(线性和旋转)发送到服务器。在服务器收到后,必须进行验证过程以防止作弊。在错过、乱序或格式错误的更新时,服务器将采用最后位置 + 速度 *(与上一个时间戳的时间差 + 当前服务器时间)。
您必须使用 UDP 进行通信(请注意,您正在使用自己的时间戳和 CRC 来验证接收到的数据,不再因无效数据而崩溃)。
仅处理 parity/CRC 检查通过的数据包,并拒绝接收到的太短或未通过检查的数据包。这些检查将消除与坏数据包相关的崩溃。
TCP 的重试机制和有序传递不利于游戏性能。每次丢失单个数据包或乱序传送时,时间增量错误(发送数据的时间与服务器处理数据的时间)都会增加 RTT(往返时间,即 Ping 时间)由于服务器重试和数据包 re-ordering。这将导致 multi-second 延迟,这种延迟只会随着游戏的进行而增加。
网络延迟和 dropped/malformed 数据包是游戏的正常现象。在基于时间戳值的无序数据包的情况下,丢弃除最后一个以外的所有数据包,因为这表明玩家的最新愿望(移动)。随着开火(在你的情况下施放法术),继续发送法术位直到确认(ActionId)。忽略服务器上除第一个收到的拼写外的所有拼写(向客户端发送确认),直到充值期结束。
虽然这个答案排除了原始问题中没有提到的所有要点,主要是损坏,但应该创建一个结构,当损坏诱导元素的发生在服务器上产生时,根据定义的分配损坏损害分配规则,需要应用于每个受影响的客户。
我做了一个联网的2d俯视射击游戏。这是相当标准的,用户使用 WASD 移动他们的播放器,并且可以使用 12345 和鼠标施法。用户必须杀死敌人并保持生命。
我已经联网让多个用户在同一个竞技场玩,但是效率太低运行。
精灵存储在 Dictionary<string, sprite>
中,存储在客户端和服务器计算机中。
是这样实现的:
- 用户键盘和鼠标输入每帧发送到服务器(
KeyboardState
,MouseState
)。 - 服务器接受输入并更新游戏状态。
- 服务器向每个客户端发送它们的视口(矩形),以及所有精灵的位置(浮动,浮动),精灵键(字符串),旋转(浮动),颜色(整数,整数,整数,整数)在该用户的视口中。
- 服务器将此信息存储为包含这些值的 class 的实例。
- 为了发送它,服务器使用
BinaryFormatter
序列化 class,并使用TcpClient
和Sockets
. 发送它
- 客户端将传入数据反序列化回 classes 并将它们绘制到屏幕上
不幸的是,这让游戏变得异常缓慢。我只在一台机器上测试过,所以没有延迟,所以我假设发送的数据太多了。
要解决这个问题,我想我要么需要有效地存储和发送这些数据,要么以不同的方式实现。
为了高效发送,我想我需要将数据打包为字节并发送,但我不知道该怎么做。
我如何有效地发送数据,或者以不同的方式实现它以使其有效?
尽量减少通过网络的数据量。仅发送所需数量的数据并通过时间戳和验证提供您自己的验证。
下面的每个 actionId 和 AnimationId 都应该展示最小(2 的幂)字节数以唯一地描述所有可能的值。
服务器 -> 客户端:
服务器已知客户端位置和旋转。最后一个 actionId 的确认。 所有其他客户端位置、方向(旋转)、animationIds 的列表......在客户端的视野内 +- maxRotationRate 在 MaxViewDistance +- maxMovementRate 的距离内。
客户端每一步必须将其当前位置与服务器位置进行平均。
在本地处理所有输入(鼠标和键盘),并只将相关数据发送到服务器:
每次更新,客户端都会发送相关数据并更新自己的内部状态并处理给定的服务器状态,并增加int值(称之为时间戳)。在此处包括奇偶校验或 CRC。
客户端 -> 服务器 当前位置、旋转、actionId、animationId 和单调递增的 int 值(称之为时间戳)。在此处包括奇偶校验或 CRC。
所有渲染决策、视口、当前 animation/sprite/colour 都应在客户端建立(颜色和初始位置需要在登录时提供给客户端)。
将共享位置数据(本地更新和服务器调整)。
每次更新都需要将位置、旋转和速度数据(线性和旋转)发送到服务器。在服务器收到后,必须进行验证过程以防止作弊。在错过、乱序或格式错误的更新时,服务器将采用最后位置 + 速度 *(与上一个时间戳的时间差 + 当前服务器时间)。
您必须使用 UDP 进行通信(请注意,您正在使用自己的时间戳和 CRC 来验证接收到的数据,不再因无效数据而崩溃)。
仅处理 parity/CRC 检查通过的数据包,并拒绝接收到的太短或未通过检查的数据包。这些检查将消除与坏数据包相关的崩溃。
TCP 的重试机制和有序传递不利于游戏性能。每次丢失单个数据包或乱序传送时,时间增量错误(发送数据的时间与服务器处理数据的时间)都会增加 RTT(往返时间,即 Ping 时间)由于服务器重试和数据包 re-ordering。这将导致 multi-second 延迟,这种延迟只会随着游戏的进行而增加。
网络延迟和 dropped/malformed 数据包是游戏的正常现象。在基于时间戳值的无序数据包的情况下,丢弃除最后一个以外的所有数据包,因为这表明玩家的最新愿望(移动)。随着开火(在你的情况下施放法术),继续发送法术位直到确认(ActionId)。忽略服务器上除第一个收到的拼写外的所有拼写(向客户端发送确认),直到充值期结束。
虽然这个答案排除了原始问题中没有提到的所有要点,主要是损坏,但应该创建一个结构,当损坏诱导元素的发生在服务器上产生时,根据定义的分配损坏损害分配规则,需要应用于每个受影响的客户。