下面是针对《三国志战略版》风格 SLG 游戏中塔防战斗玩法的「客户端消息分发与状态同步模块」的详细设计。该模块负责:
- 接收服务端推送的战斗状态更新(如怪物出现、塔攻击、技能释放等)
- 派发给具体的子系统(渲染层 / 动作层 / 音效层)
- 保证本地战斗状态与服务端同步、并能在断线后恢复
一、整体模块职责
[网络模块]
│
▼
[消息解包器(MsgDecoder)]
│
▼
[消息分发器(Dispatcher)] ←——— 游戏主循环拉消息
│
┌───┼────────────────────────────┐
│ │ │
▼ ▼ ▼
[战斗场景控制器] [状态同步模块] [战斗UI表现模块]
│ └→ 怪物生成 └→ 塔状态更新 └→ 血量/能量条更新
│ 攻击动画触发 技能冷却同步 计时器/倒计时展示
二、模块结构说明
1. 网络模块
- 支持 TCP/WebSocket 的长连接
- 支持断线重连、自动重发
- 接收到数据后,将
[]byte包传入MsgDecoder
// 示例伪代码
function onNetworkReceive(buffer: Uint8Array) {
const message = MsgDecoder.decode(buffer);
Dispatcher.dispatch(message);
}
2. 消息解包器 MsgDecoder
- 负责将 Protobuf 字节流解析成结构化对象
- 根据消息 ID 解析为对应类型的结构
class MsgDecoder {
static decode(buffer: Uint8Array): TowerMessage {
const raw = TowerDefendMessage.decode(buffer);
const cmd = raw.cmd;
const payload = raw.payload;
switch (cmd) {
case CmdType.SYNC_EVENT:
return TowerSyncEvent.decode(payload);
case CmdType.BATTLE_RESULT:
return TowerBattleResult.decode(payload);
...
}
}
}
3. 消息分发器 Dispatcher
- 根据消息类型调用相应的模块处理
- 维护一个消息队列,支持在主线程帧循环中逐帧执行(避免卡顿)
class Dispatcher {
private static queue: TowerMessage[] = [];
static dispatch(msg: TowerMessage) {
queue.push(msg);
}
// 每帧调用
static update() {
while (queue.length > 0) {
const msg = queue.shift();
if (msg instanceof TowerSyncEvent) {
SyncProcessor.processEvent(msg);
} else if (msg instanceof TowerBattleResult) {
BattleResultView.show(msg);
}
}
}
}
4. 状态同步模块 SyncProcessor
该模块对接服务端推送的事件列表,负责驱动场景内对象更新。
支持的事件类型例如:
| 事件类型 | 含义说明 |
|---|---|
| MONSTER_SPAWN | 某波怪物开始生成 |
| TOWER_ATTACK | 某塔开始攻击某怪物 |
| MONSTER_HIT | 某怪物受到伤害 |
| TOWER_UPGRADE_DONE | 塔升级完成 |
| SKILL_CAST | 释放了某个技能 |
| MONSTER_DEAD | 怪物死亡 |
class SyncProcessor {
static processEvent(event: TowerSyncEvent) {
switch (event.eventType) {
case EventType.MONSTER_SPAWN:
MonsterManager.spawn(event.payload);
break;
case EventType.TOWER_ATTACK:
TowerManager.attack(event.payload);
break;
case EventType.MONSTER_HIT:
MonsterManager.applyDamage(event.payload);
break;
...
}
}
}
5. 战斗状态模块(模型层)
战斗对象数据结构
class Monster {
id: number;
hp: number;
position: Vector2;
moveSpeed: number;
// ...
}
class Tower {
id: number;
type: number;
position: Vector2;
level: number;
cooldown: number;
}
状态更新周期
- 每帧根据服务端广播事件更新本地状态
- 动画效果由状态差驱动(如:HP 变化 → 播放受击)
6. 战斗 UI 表现模块
- 监听模型层数据更新(或由
SyncProcessor通知) - 更新血条、CD 图标、技能按钮状态等
class BattleUI {
static onMonsterDamaged(monsterId, newHp) {
// 查找UI控件并更新血量条
}
static onTowerUpgradeDone(towerId) {
// 播放升级成功动画
}
}
三、断线重连与状态补偿支持
设计思路
- 所有状态以「事件流」形式广播(时间戳+事件序列)
- 客户端断线时记录
last_seq_id - 重连后请求补发未收到的事件片段
客户端实现关键点
onReconnect() {
Net.send('ReconnectBattle', {
battleId: currentBattleId,
lastSeqId: this.lastReceivedSeqId
});
}
onReceiveSyncEvent(event) {
this.lastReceivedSeqId = event.seqId;
SyncProcessor.processEvent(event);
}
四、帧同步与状态同步的权衡
SLG 塔防战斗通常采用状态同步(服务端权威),原因如下:
| 维度 | 状态同步(本文方案) | 帧同步(如 RTS) |
|---|---|---|
| 流量消耗 | 中(只传状态变化) | 高(每帧传输入) |
| 防作弊 | 强(服务端权威) | 弱(客户端可伪造) |
| 延迟容忍 | 高(500ms 无感) | 低(需 < 100ms) |
| 回滚复杂度 | 低 | 高(需本地重新模拟) |
| 适用类型 | 塔防、回合制、卡牌 | 即时战略、格斗 |
塔防玩法节奏慢、数值确定性要求高,状态同步是天然选择。
五、消息压缩与协议优化
Protobuf 变长编码
message TowerSyncEvent {
uint32 seq_id = 1; // 单调递增事件序列号
uint32 timestamp_ms = 2; // 服务端时间戳
repeated Event events = 3; // 批量打包,减少包头开销
}
message Event {
uint32 event_type = 1; // 变长编码:小数值仅占1字节
bytes payload = 2; // 具体事件体
}
流量优化策略
| 策略 | 实现 | 效果 |
|---|---|---|
| 事件合并 | 100ms 窗口内同类事件合并 | 减少 30-50% 包量 |
| Delta 压缩 | 只传变化字段(如 HP 差值) | 减少 60% Payload |
| 包体压缩 | LZ4 压缩大事件包 | 减少 40-70% 体积 |
| 心跳精简 | 空闲时仅发序号确认 | 维持连接,几乎零负载 |
六、本地预测与服务器 reconcilation
为提升操作反馈的即时感,在状态同步中引入乐观本地预测:
class LocalPredictor {
// 用户点击释放技能
onSkillCast(skillId: number, target: Vector2) {
// 1. 立即在本地播放技能特效(不等待服务端确认)
this.playSkillVFX(skillId, target);
// 2. 发送请求到服务端
Net.send('SkillCast', { skillId, target, clientSeq: this.nextSeq() });
// 3. 记录预测状态,用于后续 reconcilation
this.pendingPredictions.set(this.nextSeq(), {
type: 'skill_cast',
predictedResult: this.calcSkillResult(skillId, target)
});
}
// 收到服务端回执后进行 reconcilation
onServerConfirm(serverEvent: TowerSyncEvent) {
const prediction = this.pendingPredictions.get(serverEvent.clientSeq);
if (!prediction) return;
if (!this.resultsMatch(prediction.predictedResult, serverEvent.result)) {
// 预测失败:回滚到服务端权威状态,重新插值
this.rollbackAndReplay(serverEvent);
}
this.pendingPredictions.delete(serverEvent.clientSeq);
}
}
预测成功率统计:塔防场景下技能释放预测准确率通常 > 95%,回滚概率低。
七、弱网与断线恢复详设
断线检测与自动重连状态机
[正常在线]
│ 心跳超时 3 次
▼
[探测中] ──→ 发送探测包,启动快速重试
│ 探测成功
▼ │ 探测失败
[已恢复] ◄───┘
│ 进入重连
▼
[重连中] ──→ WebSocket reconnect,指数退避 1/2/4/8s
│ 重连成功
▼
[请求补帧] ──→ 发送 last_seq_id,请求缺失事件
│ 收到补帧
▼
[快速追赶] ──→ 2倍速执行积压事件,追上当前进度
│
▼
[正常在线]
总结:客户端状态同步模块优势
| 特性 | 说明 |
|---|---|
| 解耦 | 网络、解包、状态处理、UI 展示解耦 |
| 保证一致性 | 所有状态由服务端事件广播推送 |
| 可恢复 | 支持断线重连与丢包重发 |
| 可扩展 | 新增事件类型只需加一个 case 逻辑 |
| 支持本地预测(可选) | 可根据技能释放等提前预判表现,提升体验 |
| 流量高效 | 事件合并 + Delta 压缩 + LZ4 |
| 弱网鲁棒 | 自动重连 + 补帧 + 快速追赶 |
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。