如何实现子弹的Server Authoritative

解读

国内主流多人射击、MOBA、动作类游戏在版号合规与反外挂高压下,服务器权威(Server Authoritative) 是必答题。面试官想确认三点:

  1. 你是否理解“客户端只表现、服务器才裁决”的生死权归属;
  2. 能否把 Unity 的物理/碰撞逻辑无损迁移到 Linux 服务器,并保证帧一致
  3. 在高并发、高 ping 的国服环境下,如何兼顾手感、带宽、反作弊

知识点

  • 确定性(Deterministic):浮点误差、Unity.Physics 与 PhysX 差异、Fixed Timestep 对齐。
  • 状态同步 vs 命令同步:子弹用状态同步(位置+速度),玩家输入用命令同步(输入+时间戳)。
  • 时间回滚(Rewind & Replay):服务器保存 1 s 环形缓冲区,收到客户端射击命令后回滚到射击帧再前向模拟,解决高 ping 误判。
  • 碰撞层级:服务器只保留**“子弹-受击盒”**层级,剔除渲染、特效、音效,降低 CPU 占用。
  • 帧号(Frame ID):客户端与服务器统一30 Hz 物理帧,所有命令带帧号,防止客户端插值错位。
  • 快照压缩:子弹状态只同步出生帧号+初始坐标+方向+速度,服务器每 50 ms 广播一次死亡事件(命中/超时),带宽 <1 KB/s/玩家。
  • 热更新友好:服务器逻辑用纯 C# .NET 6 实现,不依赖 UnityEngine.dll,方便在 Linux 云函数上热更。

答案

  1. 协议设计
    客户端发FireCmd:{frame, origin, dir, weaponId, seed},服务器立即校验武器冷却、弹夹、射线遮挡(反无后坐力外挂)。

  2. 服务器模拟

    • 使用开源确定性物理库(如 BEPUphysics 或自写简版 AABB+射线),Fixed Timestep = 0.033 s,与客户端对齐。
    • 子弹按匀速直线模拟,每帧更新:pos += velocity * dt;命中检测用线段检测(Raycast) 而非连续碰撞,降低复杂度。
    • 保存世界快照环形缓冲区(Dictionary<frame, Snapshot>),收到延迟命令时回滚到对应帧重新模拟,再把“真正命中”事件下发。
  3. 结果下发
    服务器只同步三件事:

    • BulletSpawn:出生帧号+初始参数,客户端做预测表现
    • BulletHit:{hitFrame, hitEntity, hitPoint, hitNormal},客户端收到后校正表现(血条、特效、音效);
    • BulletMiss:超时未命中,客户端销毁子弹。
  4. 客户端表现

    • 本地立即生成**“假子弹”**做特效与音效,保证手感;
    • 收到服务器 Hit/Miss 后,用插值/淡出方式平滑校正,若误差 > 0.5 m 则瞬移并打印日志供运营反外挂。
  5. 性能与反作弊

    • 服务器单核可跑1 万颗并发子弹(Linux 云主机 2.4 GHz),通过对象池+结构体避免 GC;
    • 对每颗子弹记录射击者 UID+帧号,若客户端伪造 Hit 事件,服务器直接拒绝并封号日志入库。

拓展思考

  • 子弹下坠与风力:若需抛物线,把重力加速度写成定点数(FixedPoint128),保证跨平台一致。
  • 霰弹枪多发弹丸:服务器只模拟逻辑弹片(如 3 片),客户端表现 12 片,用随机种子同步保证特效对齐。
  • 穿墙外挂:服务器在Rewind阶段用地图静态碰撞+动态掩体双重检测,客户端无法提前获知掩体状态。
  • WebGL 观战:服务器把关键事件流(出生+命中)写入 Redis,WebGL 端用Timeline 插值还原,无需跑物理。