中级游戏后端的逆袭之路——常规游戏功能设计(六、防作弊)
核心思想永远别信客户端作弊的本质就是玩家修改了本该由游戏规则控制的数据。比如把血量改成无限、让角色瞬移、一枪打出9999伤害……所以防作弊的第一条铁律就是所有影响游戏公平的关键逻辑必须在服务器执行客户端只负责显示和操作指令。防御体系第一层客户端上报 → 服务端校验客户端说“我打了100点伤害”服务器不信自己算一遍。如果对不上直接踢掉。第二层数据签名与防篡改给每个网络包加个签名类似信封上的火漆一旦中间被改过服务器立刻发现。第三层行为分析反外挂就算客户端没改数据但操作频率、移动速度明显超出人类极限服务器也能识别出来。示例代码1️⃣ 服务端校验伤害最基础但最有效importhashlib,time,random# 假设这是服务器存储的真实玩家属性player_stats{attack:50,crit_rate:0.2,crit_damage:2.0}defserver_calculate_damage(attacker_id,target_defense):服务器独立计算伤害baseplayer_stats[attacker_id][attack]# 暴击判定ifrandom.random()player_stats[attacker_id][crit_rate]:damagebase*player_stats[attacker_id][crit_damage]else:damagebase# 减去目标防御finalmax(1,int(damage-target_defense))returnfinaldefverify_combat(client_report): 客户端上报格式{attacker:player1,target:monster,claimed_damage:150} 服务器重新计算并对比 real_damageserver_calculate_damage(client_report[attacker],get_target_defense(client_report[target]))ifabs(client_report[claimed_damage]-real_damage)5:# 允许微小误差print(f⚠️ 作弊嫌疑客户端报{client_report[claimed_damage]}实际应为{real_damage})ban_player(client_report[attacker])else:print(✅ 伤害正常)2️⃣ 防重放攻击时间戳 签名有些外挂会截获你发出的“合法”数据包然后反复发送比如重复使用同一个跳跃指令。解决方案每个包带时间戳和一次性随机数nonce加上签名。SECRET_KEYgame_secret_12345defcreate_signed_packet(action,data,timestampNone):iftimestampisNone:timestampint(time.time())rawf{action}|{data}|{timestamp}signaturehashlib.sha256((rawSECRET_KEY).encode()).hexdigest()return{action:action,data:data,ts:timestamp,sig:signature}defverify_packet(packet):# 检查时间戳是否在合理范围内比如前后5秒nowint(time.time())ifabs(packet[ts]-now)5:returnFalse# 检查签名rawf{packet[action]}|{packet[data]}|{packet[ts]}expected_sighashlib.sha256((rawSECRET_KEY).encode()).hexdigest()returnpacket[sig]expected_sig# 客户端构造包packetcreate_signed_packet(move,x10,y20)# 服务端验证ifverify_packet(packet):process_move(packet[data])else:reject(非法请求)3️⃣ 速度检测行为分析如果客户端每秒移动超过游戏设定的最大速度比如正常跑是5米/秒突然变成50米/秒基本就是开了加速挂。MAX_SPEED5.0# 米/秒classPlayerMovementTracker:def__init__(self):self.last_pos{}self.last_time{}defcheck_movement(self,player_id,new_x,new_y,current_time):ifplayer_idnotinself.last_pos:self.last_pos[player_id](new_x,new_y)self.last_time[player_id]current_timereturnTrueold_x,old_yself.last_pos[player_id]dtcurrent_time-self.last_time[player_id]ifdt0:returnFalse# 时间倒流作弊distance((new_x-old_x)**2(new_y-old_y)**2)**0.5speeddistance/dtifspeedMAX_SPEED*1.5:# 留一点容差print(f{player_id}疑似加速速度{speed:.2f}m/s)returnFalse# 更新记录self.last_pos[player_id](new_x,new_y)self.last_time[player_id]current_timereturnTrue