Skip to content
当前页大纲

3D 实时作战游戏服务端架构

领域 游戏 · 3D 实时战斗 · 高并发角色 主程架构 · 核心开发状态 已落地 ·【时间待补充】#帧同步#ECS#命中判定#反作弊

3D 实时作战游戏的服务端架构:帧同步下的战斗一致性、技能与命中判定系统、逻辑与表现分离、服务器权威反作弊——战斗胜负判定的每一个字节都不能错。

这是一款 3D 实时作战游戏的服务端架构:玩家在 3D 场景里实时移动、释放技能、走位躲弹、组队开团。这类系统的工程难度和普通后端完全不在一个维度——普通后端是「吞吐量」的艺术,实时战斗服务端是「毫秒级确定性」的艺术:同样的一局战斗,在任何机器上回放必须得到完全一致的结果,否则「我明明躲开了」就会变成客诉。

一、实时作战和普通后端的本质区别

维度普通业务后端3D 实时作战服务端
请求模式低频请求-响应每玩家 10~20Hz 持续输入流
状态可丢可重算战斗状态一帧都不能错
延迟敏感度秒级可接受100ms 手感即崩
一致性要求最终一致即可全端强一致(确定性)
出错代价一笔订单异常整局战斗结果作废

核心矛盾:网络天然不可靠 + 战斗要求全端确定性。所有架构设计都在解这一对矛盾。

二、同步方案选型:为什么是帧同步

3D 实时作战的两个主流方案:

方案原理适合我们的选择
帧同步(Lockstep)只同步玩家输入,各端在相同初始状态下跑相同逻辑RTS、MOBA、动作格斗、操作密集型战斗
状态同步服务器算权威状态,广播给客户端MMO、大世界开放场景

选帧同步的三个决定性理由:

  1. 带宽:一帧同步 N 个玩家的输入(每人数十字节),比同步全量状态便宜一个数量级——团战 20 人同屏,状态同步的广播流量会爆炸
  2. 手感:客户端本地立即执行输入(零延迟反馈),网络只影响「别人的画面」,不影响自己的操作
  3. 回放/观战免费:存下输入流就存下了整局战斗——录像系统、战斗回放、观战、反作弊复核全部白送

代价也明确:逻辑必须完全确定性——浮点数不能用、随机数必须同种子同序列、容器遍历顺序必须一致、逻辑层不能依赖任何本地时钟。这是整个项目最深的纪律。

三、确定性:帧同步的生死线

帧同步项目里 90% 的疑难 bug 都是「不同步」。工程上的防御体系:

1. 定点数替代浮点

cpp
// 逻辑层全程 Q16.16 定点数,跨平台位级一致
using Fixed = int64_t;  // 数值 << 16
Fixed fmul(Fixed a, Fixed b) { return (Fixed)(((__int128)a * b) >> 16); }
  • 移动、弹道、技能距离、伤害计算全部定点化
  • 表现层(渲染、特效)才允许浮点——表现层的浮点误差不影响判定

2. 统一随机数

cpp
// 全局唯一 Lehmer RNG,随战斗初始化种子,逻辑层禁用其他随机源
uint32_t rng_state;
uint32_t rng_next() {
    rng_state = (uint64_t)rng_state * 279470273u % 0xfffffffbu;
    return rng_state;
}

暴击、掉落、技能随机效果全部走这个序列,种子在开局时同步给所有端。

3. 逻辑/表现分离(本项目最重要的架构决策)

┌────────────────────────────────────┐
│ 表现层(浮点·本地时钟·可以不精确)    │
│ 插值、平滑、特效、镜头、动画、音效    │
├─────────── 状态快照接口 ────────────┤
│ 逻辑层(定点·确定性·帧驱动)          │
│ 移动、技能、命中、伤害、Buff、AI      │
└────────────────────────────────────┘

逻辑层输出的状态快照(位置/朝向/血量/技能状态)交给表现层做插值平滑。表现层「看起来流畅」,逻辑层「算得精确」,两边互不污染。技能特效多炫、掉帧多卡,都不影响战斗判定——判定只认逻辑帧

四、战斗核心系统

1. 技能系统:数据驱动 + 流水线

技能不写死在代码里,全部配置化(策划每天改 50 个技能不能都找程序):

json
{
  "skill_id": 40021,
  "cast": { "type": "direction", "range": 800, "cd": 12000 },
  "phases": [
    { "type": "windup", "duration": 300, "cancelable": false },
    { "type": "dash", "distance": 400, "duration": 200 },
    { "type": "hit", "shape": "capsule", "radius": 120, "length": 300,
      "damage": [120, 160], "on_hit": ["stun:400", "apply_buff:2001"] }
  ]
}

每个技能 = 前摇/位移/命中判定/后摇的阶段流水线。命中形状(胶囊、扇形、球形、矩形)做成通用几何库,所有技能复用——技能的「花活」在配置,判定的「严肃」在引擎

2. 命中判定:帧内分步 + 查询加速

3D 空间里「这一剑砍没砍到」的判定,性能和正确性都要命:

  • 逻辑帧内分步移动:快速位移(冲刺/弹道)按小步长切片检测,防止「一帧穿人」(tunneling)——步长 = 最大移动速度 × 帧时长,3D 下尤其关键
  • 空间划分加速:3D 场景用均匀体素网格做粗筛(战场范围有限,均匀网格比动态八叉树简单且快),先查候选再精判几何
  • 判定必须确定性:同一帧内多个判定按实体 ID 排序执行,消除遍历顺序带来的差异

3. Buff 与结算顺序

Buff 系统(加速、减速、沉默、护盾、DoT)是同步地狱的重灾区——同一帧 10 个 Buff 到期、伤害结算顺序不同,结果就不同。规则:

  • 所有结算事件进优先级队列,按(帧号 → 优先级 → 实体 ID)全序执行
  • Buff 数值修改走「快照制」:每帧开始时数值快照,帧内修改基于快照,避免本帧内互相叠加的顺序依赖

五、网络层:卡顿对抗

客户端预测 + 服务器校验

客户端:输入 → 本地立即执行(手感零延迟) → 发给服务器
服务器:验证输入合法性(速度上限/CD/距离) → 帧缓冲 → 定帧广播
客户端:收到输入流 → 与本地预测对比 → 有偏差则回滚重演

关键参数的平衡:

  • 帧缓冲 2 帧:吸收抖动,代价是本地输入延迟 ~66ms(20Hz 逻辑帧)——动作游戏的命门参数,压到这个数以下靠的是「本地预测兜住了手感」
  • 超时断线重连:断线 60 秒内可重连,服务端保留输入流,客户端拉全量快照 + 追帧(加速倍率快进输入流)

流量控制

  • 输入编码:每玩家每帧压到 4~6 字节(按键位图 + 增量),20 人团战单帧广播 < 1KB
  • 空闲玩家(无输入)只发心跳位,连发压缩再砍一半

六、反作弊:服务器权威

帧同步的经典质疑是「客户端算逻辑,怎么防作弊」。方案是服务器同跑逻辑

  1. 服务器不转发原始输入,而是权威验证后再广播:速度超限、瞬移、CD 作弊的输入直接拒绝
  2. 服务器同跑一份战斗逻辑做结果校验:关键结算(击杀、伤害统计)以服务器为准
  3. 输入流全量落盘:被举报的战斗直接确定性回放复核——开挂的操作在回放里无处遁形(帧同步回放的免费红利)

七、性能指标

指标目标说明
逻辑帧率15~20 FPS(逻辑帧)表现层 60 FPS 渲染,两者解耦
单服战斗并发【待补充】单进程多战斗,线程池隔离
输入广播延迟≤3 帧帧缓冲的代价
断线重连≤60 秒保留战斗 + 追帧
战斗回放体积【待补充】输入流存储,远小于录像

八、收获

做 3D 实时战斗后端最大的成长是对「确定性」的敬畏:普通系统里「大概对」就行,这里必须「位级一致」。逻辑/表现分离、定点数、全序结算这些手段,本质都是把「不确定性」从逻辑层彻底驱逐。这套纪律反哺到其他项目也受益——写任何分布式系统时,我都会先问一句:这段代码的结果,在另一台机器上重跑会一样吗?

💬 对实时同步、战斗系统、游戏架构有想法?欢迎加我泡泡(微信)一起探讨技术与科技前沿 → 🍵 加我泡泡

MIT License.