在线联机原型全集:第 16 章 简版 MOBA(Battle + AI Units|Tick Sync, Reconnect & Recovery)

第 16 章:简版 MOBA(Battle + AI Units|Tick Sync, Reconnect & Recovery),介绍了一个简单的实时 MOBA 游戏原型,包括地图设计、玩家控制、AI 单位、胜负判定、断线重连、事件回放等功能。

简版 MOBA(Battle + AI Units|Tick Sync, Reconnect & Recovery)

  • 类别:实时战斗 + 小规模 MOBA + AI 兵线/野怪
  • 目标:在 3v3 或 4v4 的小场景中,验证服务器权威 Tick 同步可重放的事件流玩家断线→重连→完整恢复、以及**AI 单位(兵线/防御塔/野怪)**的确定性驱动。
  • 原型代号proto-016-mini-moba
  • 依赖模块proto-007-snake-battle(实时房间/网关基线)、proto-014-room-escape-sync(事件溯源/快照思路)、proto-015-mini-race-rollback(回滚/插值经验复用)
  • 推荐语言栈:服务端 Go/Java(Go 栈优先),客户端 TypeScript(WebGL/Unity WebGL 任一),AI/导航可选 C/Rust 扩展
  • 协议栈:WebSocket(可靠通道) + UDP(可选,玩家输入与位置增量)
  • Tick 频率:服务器 20–30Hz(建议 20Hz=50ms/帧),客户端渲染 60fps

1. 核心玩法与约束

  • 地图:单兵线小地图(直线或“L 型”),每方一座基地 + 2 座外塔(T1/T2)。

  • 玩家:每队 3–4 人;英雄有 2 个主动技能 + 1 个大招(冷却长)。

  • AI 单位

    • 兵线(Creeps):每 20s 刷一波,顺路推进,遇敌/塔停留输出;
    • 防御塔(Towers):锁定范围内最近/仇恨最高目标;
    • 中立野怪(可选):提供临时增益。
  • 胜负:推掉对方基地即胜(不引入买活/装备系统,专注同步/AI/恢复链路)。

  • 确定性:同一种输入在相同 Tick 序列下,服务端必须得到一致结果(AI 与技能需可确定性重放)。


2. 同步模型(Tick-Based Authoritative Server)

2.1 模式选型

  • 服务器权威 + 客户端轻预测:客户端仅对自身移动非冲突性 UI 动画做局部预测;伤害、击杀、硬控等必须以服务器回传为准
  • 输入延迟缓冲:客户端发送输入时标记 client_tick,服务器按Tick 序执行;客户端渲染延后 2–3 Tick 以吸收抖动。
  • 确定性物理/AI:避免不确定 API(随机数统一来自服务器种子、帧内固定迭代顺序、避免浮点不可重现差异)。

2.2 帧结构

  • 输入帧(InputFrame){uid, tick, move_vec, cast_cmd?, target?}
  • 状态帧(StateFrame){tick, changed_entities[], acks[], rng_mark, hash}
  • 关键帧(Keyframe):每 N 帧(建议 1–2s)落盘快照;其余帧用事件重放恢复。

2.3 Tick 流程(服务器)

for each Tick t:
  1) 收集输入(t-Δ 到 t),丢失则复用上次移动向量
  2) 处理技能指令(排队/判定资源/冷却/施法前摇)
  3) 推进 AI(兵线/塔/野怪):感知→决策→执行
  4) 执行移动/碰撞/投射体/伤害结算
  5) 生成状态增量(diff)与一致性哈希
  6) Append event_log(t);必要时写关键帧 snapshot(t)
  7) 广播 StateFrame(t)

2.4 客户端时序

  • 渲染滞后:渲染 t_render = t_server - 2..3
  • 自身移动预测:本地立即推进;当服务端状态回到达时,做微校正(< 0.2m 的偏差进行平滑 Lerp)。
  • 他人/AI:仅插值/外推(外推长度 ≤ 2 帧),到权威帧即对齐。

3. 断线恢复(Reconnect & Recovery)

3.1 目标

  • 10 秒内恢复战斗画面(场上状态“秒回”),输入可继续提交;
  • 保证恢复后与服务器一致(校验 Hash),拒绝异常客户端。

3.2 恢复链路

  1. 客户端重连取得 room_idlast_acked_tick

  2. 服务器根据 last_acked_tick 选择最近关键帧 KF(k)(k ≤ last_acked_tick),返回:

    • snapshot@k(二进制,压缩)
    • events[k+1..t_now](事件流增量)
    • 当前 tick=t_nowrng_mark
  3. 客户端离线重放t_now,计算哈希比对 hash@t_now

  4. 比对通过 → 切换到在线帧流;不通过 → 回落到全量快照再重放;仍失败 → 强制全量替换并提示“状态已对齐”。

3.3 数据与校验

  • 状态快照:压缩的 ECS 数据包(只含必要组件字段,位置/血量/技能 CD/AI 状态等)。
  • 事件流:输入、技能施放、投射体生成/命中、AI 选择目标、死亡事件等(按 tick 顺序)。
  • 一致性哈希:服务器每帧计算 hash = SHA1(t || rng || ∑entity.comp_hash),客户端比对。

4. 实体与组件(ECS)

4.1 核心实体

  • Hero:玩家操控英雄
  • Creep:兵线小兵
  • Tower:防御塔
  • Projectile:投射体
  • Neutral(可选):中立怪/增益点

4.2 核心组件(示例)

组件字段(示例)
Transformpos[x,y], rot, vel[x,y]
Statshp,max_hp, atk, def, ms, as
Teamcamp(blue/red)
AbilitySetQ,W,R(冷却、法力、前/后摇、施法范围)
AIStatestate(enum), target(id), threat, leash
Aggrotable[targetId]=threat
Projectileowner, speed, dir, range, onHit
TowerBrainrange, priority_rule, cooldown
Respawn(可选)dead_until_tick

5. AI 设计(兵线 & 塔 & 野怪)

5.1 通用行为树(BT)/状态机(FSM)

采用轻量 FSM + 条件表,确保确定性与可重放。

5.1.1 兵线(Creep)FSM

stateDiagram-v2
  [*] --> Advance
  Advance --> Fight: EnemyInRange
  Fight --> Advance: NoEnemyInRange
  Fight --> Retreat: LeashBroken or LowHP
  Retreat --> Advance: ReEnterPath
  • Advance:沿预计算路径点前进(A* 预烘焙,帧内线性跟踪)
  • Fight:锁定最近敌方单位(英雄优先 > 小兵 > 建筑),普攻
  • Retreat:超过牵引半径(leash)或生命过低则回到路径点

5.1.2 塔(Tower)规则

  • 目标选择:优先攻击攻击过友方英雄的敌英雄,否则最近目标;
  • 仇恨转移:若多个目标满足条件,选择仇恨最高者;
  • 射击节奏:固定冷却(如 1s),投射体飞行可见;
  • 伤害模型:塔对英雄伤害递增(防“越塔强杀”)。

5.1.3 野怪(可选)

  • Idle → Patrol → Fight → LeashBack
  • 仇恨丢失/牵引回位:离出生点过远或 3s 未受击,则回位回血。

5.2 威胁/仇恨(Threat)

  • 基础威胁 = 造成伤害 × α + 治疗量 × β + 距离加权;
  • 受控于上限,衰减系数 γ(每秒);
  • 塔单独维护“英雄优先级”规则覆盖 Threat。

6. 技能与战斗(Ability System)

6.1 技能生命周期

sequenceDiagram
  participant C as Client
  participant S as Server
  C->>S: cast(Q, target=posA, tick=1234)
  S->>S: validate(cooldown, mana, range)
  S-->>C: ack(cast_id, start_tick)
  S->>S: apply pre-cast (lock, forward)
  S->>S: spawn projectile / apply buff
  S-->>All: state_diff(entities..., tick=1235..)

6.2 技能类型

  • 定点投射体(Fireball):起点/方向/速度,命中即伤害 + 击退(可选);
  • 瞬发指向(Bolt):直接对目标单位结算(需视野与距离);
  • 圆形 AoE(Nova):延迟触发(地面提示),命中给减速/沉默等;
  • 通用参数:施法前摇/后摇、冷却、施法距离、法力消耗、可打断与否。

6.3 伤害结算与数值

  • 公式示例:
    dmg = max(1, (atk * k_skill + flat) * (1 - def / (def + 100)))
  • 暴击(可选):crit_chancecrit_mult
  • 减益/控制:slow%, stun_duration, silence_duration;控制统一在服务器侧打表。

7. 移动、碰撞与投射体

  • 移动:英雄移动速度 ms,每帧 pos += dir * ms * Δt
  • 碰撞:简单圆形碰撞体,使用网格/四叉树加速邻域收集;
  • 穿插:小兵之间可设置低优先级“软挤压”避免卡死;
  • 投射体:服务器每帧推进,命中后触发 onHit;命中判断采用圆形交叠射线到圆最短距
  • 地形:路径烘焙 + 站位点;非走地面(障碍)直接拒绝。

8. 数据结构与协议

8.1 WS 消息(示例)

// 输入帧:客户端 -> 服务器
{ "t":"in", "uid":1001, "tick":20250, "move":[0.7, -0.2], "cast":{"slot":"Q","target":[38.2,17.6]} }

// 状态帧:服务器 -> 客户端(增量)
{
  "t":"st","tick":20250,"rng":39122,"hash":"a8f...3c",
  "ents":[
    {"id":1,"pos":[10.8,5.2],"hp":720,"cd":{"Q":4.1}},
    {"id":201,"pos":[12.0,5.3],"hp":180} // creep
  ],
  "ev":[
    {"k":"hit","src":1,"dst":201,"dmg":65,"type":"phys"},
    {"k":"death","id":201}
  ],
  "ack":[20248,20249]
}

// 快照 + 事件流:用于断线恢复
{
  "t":"recover","kf_tick":20000,
  "snapshot":"<binary-zstd-base64>",
  "events":[ /* 20001..20250 */ ],
  "now":20250,"rng":39122,"hash":"a8f...3c"
}

8.2 服务器内部表

  • rooms(room_id, map_id, seed, tick, state)
  • entities(room_id, id, archetype, comp_blob)
  • event_log(room_id, tick, seq, payload)
  • snapshots(room_id, kf_tick, blob_uri, size)
  • matches(id, teamA, teamB, result, duration, stats_blob)

9. 一致性与确定性

  • 随机数rng(seed, tick, salt) 生成;事件内需以固定顺序迭代,避免迭代顺序差异;
  • 浮点问题:尽量改用定点数(例如 int32 表示 1/1000 m),或限制计算路径(统一 round);
  • 组件遍历顺序:按照 entity_id 升序;
  • 哈希:对关键字段做 stable-hash;忽略非关键的渲染字段。

10. 断线/重连 UX

  • 软暂停:单人断线不暂停全局;其英雄进入“保守 AI”模式(护塔/后撤)。
  • 重连面板:显示“同步进度/已重放 X 帧”;
  • 技能与输入:重连前排队的技能若未开始施放 → 自动取消;移动命令不回放,避免“幽灵走位”。

11. 反作弊与风控

风险说明对策
输入回放/加速高频或超前 tick服务器丢弃超前/回放帧;限流
修改位置客户端篡改自身位置/速度以服务器计算为准
改技能 CD本地绕过服务器校验资源与冷却
透视/射线客户端显示作弊服务器做视野裁剪;仅送可见信息
事件篡改伪造命中投射体/伤害由服务器生成与结算
断线博弈蓄意断线免死断线进入“可击杀”AI,不免伤

12. 监控与指标

  • 同步avg_rtt, jitter, loss, ack_delay
  • 断线恢复reconnect_count, mean_recover_ms, recover_fail_rate
  • AIlane_pressure, tower_shots/min, creep_kill_share
  • 战斗dps_avg, teamfight_length, stun_time_sum
  • 一致性hash_mismatch_rate, resim_frames_per_min
  • 性能tick_ms_p50/p95/p99, ents_count, diff_bytes_per_tick

13. 压测与测试计划

  1. 确定性回放:相同事件流重放 100 次哈希一致;
  2. 延迟/丢包:50–200ms RTT / 5–10% loss;测量恢复时间插值抖动
  3. AI 对齐:对不同 CPU/平台跑同一波次兵线,检查推进距离/存活率一致;
  4. 断线恢复:中途拔网线 5s,再连回,验证 2s 内画面对齐;
  5. 边界用例:关键帧损坏/丢失 → 回退策略有效;
  6. 压力:每房 8 人 + 60 AI,服务器 Tick p95 < 40ms。

14. 参考实现骨架(节选)

14.1 Go 服务器 Tick 循环(简化)

type Room struct {
  Tick int64
  RNG  RNG
  Ents *World // ECS
  Bus  EventBus
}

func (r *Room) Step() {
  t := r.Tick + 1
  inputs := DrainInputs(t)               // 收集 t 的输入
  ApplyInputs(r.Ents, inputs, t)         // 写入意图组件
  StepAbilities(r.Ents, t, &r.RNG)       // 技能前后摇/冷却/投射体生成
  StepAI(r.Ents, t, &r.RNG)              // 兵线/塔/野怪决策
  IntegrateMovement(r.Ents, t)           // 移动/碰撞推进
  ResolveCombat(r.Ents, t, &r.RNG)       // 伤害结算/死亡/经验
  diff := BuildDiff(r.Ents)              // 生成增量
  hash := StableHash(r.Ents, t, r.RNG)   // 一致性哈希
  AppendEventLog(t, inputs, diff, hash)
  if t%40 == 0 { WriteKeyframe(t, r.Ents) }
  BroadcastState(t, diff, hash)
  r.Tick = t
}

14.2 TS 客户端重连恢复(简化)

async function recover(roomId: string, lastAck: number) {
  const rec = await fetch(`/recover?rid=${roomId}&ack=${lastAck}`).then(r=>r.json());
  const snap = decodeAndInflate(rec.snapshot);
  world.loadSnapshot(snap); // ECS 重建
  for (const ev of rec.events) applyEvent(world, ev);
  const ok = hash(world, rec.now, rec.rng) === rec.hash;
  if (!ok) {
    // 回退到全量替换
    world.loadSnapshot(decodeAndInflate(rec.full_snapshot));
  }
  connectRealtime(rec.now); // 切入在线流
}

15. 地图与兵线参数(示例)

参数默认
刷兵间隔20s
单波数量3 近战 + 1 远程
兵线 HP/ATK280 / 18
塔射程/冷却8m / 1s
英雄移速4.5 m/s
经验/金币仅作统计,不影响战斗(原型阶段)

16. 版本迭代路线

版本目标要点
v0.1房间框架 + Tick 同步输入→AI→战斗→广播链路跑通;无断线
v0.2断线恢复关键帧 + 事件流 + 哈希校验;2s 内恢复
v0.3塔与兵线行为打磨仇恨/优先级/牵引;塔递增伤害
v0.4技能系统完善Q/W/R 三系;投射体/地表延迟技能
v1.0稳定性/压测p95<40ms;丢包/抖动容错;A/B 指标看板

17. MVP 勾选清单

  • 权威 Tick 同步(20Hz),输入缓冲与 ACK
  • 兵线/塔 AI(确定性 FSM),威胁表
  • 技能 Q/W/R(瞬发/投射体/AoE 各一类)
  • 断线恢复(关键帧 + 事件流 + 哈希)
  • 客户端自身移动微预测 + 他方插值
  • 回放(事件溯源)与赛后复盘
  • 基本反作弊与视野裁剪

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页