在在线游戏中处理玩家速度
Dealing with player speed in a online game
所以我正在写这个在线游戏,我有点难以处理我应该多久接受一次移动数据包。问题是,如果不信任客户端一些数据,我就无法使它工作。基本上我的想法是:
客户端
/**** [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
与 client/server 中的服务器不同,因为数据包到达需要时间。
所以如果客户端发送2个移动数据包,第一个移动时间为100ms
,第二个移动时间为99ms
,服务器会认为玩家移动太快,当这不是问题时,问题是旅行时间变化并且服务器保存 player.lastMove
有点晚,在这种情况下误差范围听起来也不太好。
有什么想法吗?
编辑:发送到服务器的唯一数据是玩家想要移动的方向,为了这个例子,它只是碰巧是相同的变量名。服务器有自己的player.lastMove
变量
解决方案
我还没有实现这个,但是我想了想放弃了,我找不到任何弱点,如果你实现了,请随时评论!
我认为可以肯定地说,客户端唯一可以做的就是更改时间戳,他也可以更改时钟,但这会产生相同的效果,所以我只是在假设他正在更改数据包的情况下才想到这一点时间戳
添加一个额外的 && packet.timeStamp < server.clock
至
boolean isTimeStampValid = packet.timeStamp >= player.lastMove + 1000 && packet.timeStamp < server.clock
不会出错,它会将攻击 #1 isTimeStampValid
标记为 false
你的架构是错误的。
游戏的客户端应该有大约零逻辑处理用户输入...用户输入应该尽可能原始地发送到服务器,客户端保持无状态
服务器端保留游戏的整个状态,接收来自客户端的用户输入,处理新的游戏状态(验证是否是有效移动)然后 returns 到客户端一个新的游戏状态
客户端应该简单地在屏幕上打印新状态。 尽可能保持最小的本地数据
你的客户想法“知道”最后一步只有在有某种“撤消”最后一步
第二点是,如果您使用 http
在客户端-服务器之间传输数据,则服务器无法在 move1 之前接收 move2...http 是一种 tcp 协议,tcp 保证交付顺序,所以你关于不同延迟导致服务器端错误顺序的假设是错误的
这里有一个我认为很容易实现的想法:
不要使用 Date.now()
来计算移动之间的时间。
保持某种游戏时钟。它可以是整个游戏开始后的毫秒数,例如 Unix timestamps,也可以是每个玩家唯一的,例如他们登录后的毫秒数。
将其独立保存在服务器和客户端中。
您可以偶尔使用 SNTP 之类的方法同步它们,尽管在您的情况下这可能有点过分。
每当玩家移动时,让客户端在数据包中插入基于此时钟的时间戳以及移动数据。
然后,让服务器上的逻辑检查这些时间戳的一致性。
这样,移动之间的时间将忽略数据包传输时间。
并且如果客户端想要作弊,则不能发送两个时间戳相隔小于 1000 毫秒的动作。
如果是这样,您的服务器将知道忽略该移动。
您还可以实施一些逻辑,如果它检测到数据包一起到达或关闭(100-200 毫秒)但时间戳相隔 1000 毫秒或更长时间,则会停止游戏。
这将是一个简单的方法来抓住作弊者。
如果你想要一个快节奏的游戏,你不得不在客户端中保持状态,让玩家看到他们移动的结果,而无需在每次击键时等待服务器响应。
事实上,根据您游戏的速度,您可能希望向服务器分批报告走法,而不是每次走法都自己报告,这既是为了节省数据(如果此游戏是移动版的)又是因为这样会更快。
然后您的服务器可以一起验证整个批次的一致性。
所以我正在写这个在线游戏,我有点难以处理我应该多久接受一次移动数据包。问题是,如果不信任客户端一些数据,我就无法使它工作。基本上我的想法是:
客户端
/**** [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
与 client/server 中的服务器不同,因为数据包到达需要时间。
所以如果客户端发送2个移动数据包,第一个移动时间为100ms
,第二个移动时间为99ms
,服务器会认为玩家移动太快,当这不是问题时,问题是旅行时间变化并且服务器保存 player.lastMove
有点晚,在这种情况下误差范围听起来也不太好。
有什么想法吗?
编辑:发送到服务器的唯一数据是玩家想要移动的方向,为了这个例子,它只是碰巧是相同的变量名。服务器有自己的player.lastMove
变量
解决方案
我还没有实现这个,但是我想了想放弃了,我找不到任何弱点,如果你实现了,请随时评论!
我认为可以肯定地说,客户端唯一可以做的就是更改时间戳,他也可以更改时钟,但这会产生相同的效果,所以我只是在假设他正在更改数据包的情况下才想到这一点时间戳
添加一个额外的 && packet.timeStamp < server.clock
至
boolean isTimeStampValid = packet.timeStamp >= player.lastMove + 1000 && packet.timeStamp < server.clock
不会出错,它会将攻击 #1 isTimeStampValid
标记为 false
你的架构是错误的。
游戏的客户端应该有大约零逻辑处理用户输入...用户输入应该尽可能原始地发送到服务器,客户端保持无状态
服务器端保留游戏的整个状态,接收来自客户端的用户输入,处理新的游戏状态(验证是否是有效移动)然后 returns 到客户端一个新的游戏状态
客户端应该简单地在屏幕上打印新状态。 尽可能保持最小的本地数据
你的客户想法“知道”最后一步只有在有某种“撤消”最后一步
第二点是,如果您使用 http
在客户端-服务器之间传输数据,则服务器无法在 move1 之前接收 move2...http 是一种 tcp 协议,tcp 保证交付顺序,所以你关于不同延迟导致服务器端错误顺序的假设是错误的
这里有一个我认为很容易实现的想法:
不要使用 Date.now()
来计算移动之间的时间。
保持某种游戏时钟。它可以是整个游戏开始后的毫秒数,例如 Unix timestamps,也可以是每个玩家唯一的,例如他们登录后的毫秒数。
将其独立保存在服务器和客户端中。 您可以偶尔使用 SNTP 之类的方法同步它们,尽管在您的情况下这可能有点过分。
每当玩家移动时,让客户端在数据包中插入基于此时钟的时间戳以及移动数据。
然后,让服务器上的逻辑检查这些时间戳的一致性。 这样,移动之间的时间将忽略数据包传输时间。
并且如果客户端想要作弊,则不能发送两个时间戳相隔小于 1000 毫秒的动作。 如果是这样,您的服务器将知道忽略该移动。
您还可以实施一些逻辑,如果它检测到数据包一起到达或关闭(100-200 毫秒)但时间戳相隔 1000 毫秒或更长时间,则会停止游戏。 这将是一个简单的方法来抓住作弊者。
如果你想要一个快节奏的游戏,你不得不在客户端中保持状态,让玩家看到他们移动的结果,而无需在每次击键时等待服务器响应。
事实上,根据您游戏的速度,您可能希望向服务器分批报告走法,而不是每次走法都自己报告,这既是为了节省数据(如果此游戏是移动版的)又是因为这样会更快。
然后您的服务器可以一起验证整个批次的一致性。