在线游戏中处理玩家速度问题

3
我是一名有帮助的助手,我会为您翻译以下内容。这是关于编程的:

所以我正在编写这个在线游戏,但是我在处理应该多久接受一个移动数据包方面遇到了困难。问题在于,如果不信任客户端,我无法让它正常工作 基本上我的想法是:

客户端

/**** [game/client] movePlayer ***/


boolean playerCanMove = Date.now() - player.lastMove > 1000;

if(playerCanMove){

    player.lastMove = Date.now();
    player.move(RIGHT);
    client.send(new PacketMove(RIGHT));
}

服务器:

/**** [server] handle a move packet ****/
/** The only data received from the client is the direction(RIGHT) **/
/** the server has its own player.lastMove **/

let playerCanMove  = Date.now() - player.lastMove > 1000; 

if(playerCanMove){
    player.lastMove = Date.now();
    player.move(RIGHT);  
}
else{
   error = "Player is moving too fast"; 
}

这样做的问题在于,由于数据包到达的时间不同,服务端player.lastMove与客户端/服务器中的不同。因此,如果客户端发送了两个移动数据包,第一个传输时间为100ms,第二个传输时间为99ms,则服务器会认为玩家移动得太快,但问题不在于此,问题在于传输时间变化并且服务器稍晚保存了player.lastMove,在这种情况下存在一定的误差也不是很好。

enter image description here

有什么想法吗?

编辑:为了方便说明,只有玩家想要移动的方向被发送到服务器,它们只是同样的变量名。服务器有自己的player.lastMove变量。

解决方法感谢@Lev M.

我还没有实现这个,但我已经思考了很多,我找不到任何弱点,请随意评论!

我认为可以说客户端唯一能做的就是改变时间戳,他也可以改变时钟,但效果相同,所以我认为只假设他正在更改数据包时间戳。

enter image description here

&& packet.timeStamp < server.clock后添加额外内容

变为

boolean isTimeStampValid = packet.timeStamp >= player.lastMove + 1000 && packet.timeStamp < server.clock

这样做不会出错,并且会将攻击 #1 的isTimeStampValid标记为false


我同时使用服务器端的Javascript和客户端的Java。这就是为什么我放了两个标签,但有人编辑了它们。但实际上这并不重要,因为代码实际上是伪代码,为了让问题尽可能简单。 - centenond
我不确定您在这里尝试解决什么问题。您担心机器人在您的游戏中进行诈骗吗?我问这个问题是因为我不确定为什么您不能相信前端来处理这个问题。但是,如果您正在编写一个API,该API无法接受除特定时间间隔之外的移动请求,那么您必须像您现在这样将时间间隔保存在服务器上,并在移动速度超过该时间间隔时进行拒绝响应。您编写的前端可以始终自动重新提交,以确保网络延迟不会使时间间隔变得困难。 - DCTID
@centenond 你的问题太宽泛了,但你可以参考其他类似的问题,比如https://dev59.com/MHXYa4cB1Zd3GeqP6Gwe?rq=1 或者 https://dev59.com/pnE85IYBdhLWcg3wSxgv?rq=1 - Progman
DCTID 是一个快节奏的游戏,客户端先移动,然后如果服务器拒绝该位置,玩家会传送回其原本应该在的位置,大多数情况下,如果玩家没有作弊并且具有良好的互联网连接,这种情况不会发生,前端重新提交会使游戏无法进行,而且会破坏客户端预测的目的。 - centenond
3
我认为这并不是一个很宽泛的问题,这个问题只涉及到一个具体的问题,在这种情况下可能并不那么罕见。 - CertainPerformance
2个回答

2
这里有一个我认为很容易实现的想法: 不要使用 Date.now() 来计算移动之间的时间。
保持一种游戏内时钟。可以是自游戏开始以来经过的毫秒数,例如类似于Unix时间戳,或者可以是每个玩家唯一的值,即自登录以来经过的毫秒数。
在服务器端和客户端上分别独立保存此信息。 您可以偶尔使用类似于SNTP之类的东西进行同步,尽管这在您的情况下可能有些过度。
当玩家进行移动时,让客户端在数据包中插入基于此时钟的时间戳,以及移动数据。
然后,在服务器上具有检查这些时间戳一致性的逻辑。 这样,移动之间的时间将忽略数据包传输时间。
如果客户端想作弊,则不能发送两个时间戳相差小于1000毫秒的移动。 如果这样做,您的服务器将知道忽略该移动。
您还可以实现一些逻辑,如果检测到同时或接近到达的数据包(100-200ms),但其时间戳相差1000ms或更多,则停止游戏。 这将是捕捉作弊者的简单方法。
如果您想要一个快节奏的游戏,则不得不在客户端中保持状态,以便让玩家在每次按键时看到他们移动的结果而不必等待服务器响应。
实际上,根据游戏的速度,您可能希望批量报告移动,而不是每个移动单独报告,既可以节省数据(如果此游戏适用于移动设备),也可以更快地进行操作。
然后,您的服务器可以一起验证整个批次的一致性。

我开始觉得没有人理解这个问题,你似乎理解了,感谢回复!这似乎是一个不错的想法,当我实施和测试它时,我会再联系你。 - centenond

-1

你的架构有问题。

游戏的客户端应该几乎不处理用户输入的逻辑... 用户输入应该尽可能原样发送到服务器,而客户端不保留任何状态

服务器端保留整个游戏状态,从客户端接收用户输入,处理新的游戏状态(验证是否为有效移动),然后返回给客户端一个新的游戏状态

客户端应该简单地在屏幕上打印新状态。尽可能保留最少的本地数据

如果存在某种“撤销”上一步的情况,那么客户端“知道”上一步的想法才有意义

第二点是,如果您使用http在客户端和服务器之间传输数据,则服务器不可能在接收到move1之前接收到move2... http是一种tcp协议,tcp保证交付顺序,因此您关于不同延迟导致服务器端顺序错误的假设是错误的


谢谢回复。如果这是一个缓慢的游戏,比如国际象棋或跳棋,我会选择“哑客户端”方法,这样做是有道理的。但它不是,客户端预测在我的游戏中是必须的。客户端应该知道何时以及是否可以移动到那里。此外,我没有说服务器会在move1之前接收到move2,我的例子是,如果move1需要100毫秒才能到达,而move2需要99毫秒才能到达,则服务器中的boolean playerCanMove = Date.now() - player.lastMove > 1000;将导致false,因为player.lastMove将受到数据包到达所需的时间的影响。 - centenond
你的想法意味着我必须等待服务器响应才能移动玩家,这对于象棋或跳棋这样的游戏来说还好,但对于MMO或任何快节奏的游戏来说就不行了。 - centenond
我不会一遍遍解释我已经说过的事情... 你需要阅读有关分布式系统传输协议的内容... 如果你正在使用 top,即使你想要也绝对做不到这件事... - Rafael Lima
你必须等待服务器响应请求才能继续移动,否则你将不可避免地创建一种不一致的场景,“玩家看到”的情况可能会与“真实发生在游戏世界中”的情况不符,甚至可能允许某些人破解你的客户端,并执行各种不允许的移动,因为你的“逻辑”位于客户端。 - Rafael Lima
在真正的 MMORPG 中,客户端应该基本上只进行碰撞检测,其他所有操作都应该由服务器处理... 你可以通过假设服务器将允许你的移动并将其“打印”到用户端来“加速”处理... 但是,客户端应该保持最少的数据... - Rafael Lima
我清楚地知道我应该相信客户端什么,这就是为什么这个问题首先出现的原因(因为我不想相信客户端的player.lastMove),而且我也没有说我的服务器没有游戏“逻辑”,你可以看到我的服务器代码也检查了玩家自己的数据是否能移动。如果我的英语不好以至于不能分享我的问题,我很抱歉,但由于某种原因,你一直在假设我从未说过或暗示的事情。很抱歉给你的回答投了反对票,因为它甚至与问题无关。 - centenond

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