《恶魔轮盘改》项目完整实现详解:架构、流程与双端同步
归档说明:本文档概括当前项目的整体实现细节,涵盖游戏架构分层、流程控制原理、客户端单机实现、服务端权威实现、联机 FlowStep 同步协议及客户端消费层。内容自洽,可直接作为开发存档阅读,于2026.6.12日留存于博客备份。
目录
1. 项目总览
《恶魔轮盘》是一款四人回合制卡牌博弈游戏,支持 Unity 客户端单机(人类 + 本地 AI)与 Unity WebGL / Standalone 联机(权威服务器 + 多人类 + AI 补位)两种运行模式。
技术栈概览:
| 层次 | 技术 | 职责 |
|---|---|---|
| Unity 客户端 | C# + UGUI/IMGUI | 表现、输入、单机本地逻辑驱动 |
| 服务端 | ASP.NET Core + SignalR Hub | 登录、大厅、房间、权威对局 |
| 共享逻辑 | GameCore(Assets/Scripts 链接至服务端) | 规则、流程、技能、道具、AI 决策 |
| 协议 | Protobuf(server/proto/v1/) | 客户端与服务端消息 DTO |
| 持久化 | MySQL | 账号、排行榜;房间与对局在内存 |
核心设计目标:
- 规则只写一份:
FlowController在 Unity 单机与服务端联机共用,避免双端逻辑漂移。 - 流程与表现分离:流程层只发
PresentationCueSpec和快照,不直接操作 UI。 - 联机防作弊:随机数、伤害、道具数量均在服务端
GameSession内结算;客户端只收FlowStep展示。 - 可中断协程流程:用
IEnumerator+yield表达「等玩家决策」,单机用 Unity 协程,服务端用CoroutineDriver。
1 | flowchart TB |
2. 整体架构分层
2.1 模块职责边界
| 模块 | 职责 | 禁止事项 |
|---|---|---|
| FlowController | 回合流转、五阶段状态机、响应链轮询、状态栈压栈/出栈 | 不直接画 UI、不读 WebSocket |
| GameSession | 聚合玩家列表、枪膛、事件总线、技能执行器、回合指针 | 不决定 UI 如何展示 |
| IDebugHost | 决策输入、表现 cue 队列、快照推送(双端差异插槽) | 不修改游戏规则 |
| SkillExecutor / ItemService | 响应事件、执行主动/被动技能、道具 CRUD | 不推进回合阶段 |
| AIDecisionMaker / GameDebugAiResolver | 输出 PlayerDecision | 不直接改实体状态 |
| FlowPublisher(仅服务端) | 合并 cue/战报/快照为 FlowStep 广播 | 不含游戏规则 |
| OnlineHudCoordinator(仅联机客户端) | revision 排序、表现门禁、权威态/显示投影 | 不跑 FlowController |
2.2 服务端进程结构
1 | NewRingGame.Server |
内存热数据(进程内字典,重启丢失):Session Token、ConnectionId→PlayerId、房间实例、进行中对局 MatchInstance(含 GameSession + seed)。MySQL 仅存账号与排行榜等冷数据。
2.3 客户端运行模式
GameEntry 通过三个布尔标志区分状态:
| 模式 | IsMatchRunning | IsOnlineMatch | 流程引擎位置 |
|---|---|---|---|
| 单机正常/Debug | true | false | 本地 Session.Flow.RunGameCoroutine() |
| 联机对局 | true | true | 不跑本地 FlowController,只消费 FlowStep |
| 大厅/结算 | false | false | 无 |
联机入口 BeginOnlineMatch 设置 IsOnlineMatch = true,由 NormalGameUIInputView.EnterOnlineMatch 初始化 HUD 与 OnlineHudCoordinator,等待服务端推送。
2.4 通信方式
- HTTPS REST:登录(账号不存在则自动注册)、健康检查。
- SignalR WebSocket(GameHub):大厅、房间、准备、对局
FlowStep下行、SubmitDecision上行。 - Unity WebGL 必须使用 WebSocket(wss),因此服务端选用 SignalR 而非裸 TCP。
连接流程:客户端 POST /api/auth/login 获 Token → ConnectHub?access_token=xxx → EnterLobby → 创建/加入房间 → 全员 Ready 后服务端 StartMatch → 广播 GameStarted + 首帧 FlowStep。
3. GameCore 共享逻辑层
GameCore 即 Assets/Scripts 下无 UnityEngine 依赖(或 #if HEADLESS 隔离)的纯 C# 逻辑,服务端项目通过 csproj 链接同目录源码编译。
3.1 GameSession:对局会话聚合根
GameSession 持有单局全部可变状态与模块引用:
1 | public class GameSession |
SetupGame(seats, seed) 初始化流程:
- 注入
Random(seed)(联机可复现)或本地随机。 - 清空事件总线、状态栈、技能注册。
- 按
SeatConfig创建PlayerEntity(座次、职业、ControlType、开局生命)。 - 注册全局被动(狂暴增伤、护身符格挡等)。
- 枪膛
Chamber.LoadRandom装入实弹/空弹序列。 - 全员发放开局道具,批次写入
_startingDealBatches供表现层发牌动画。
回合指针 _currentTurnIndex 按座次 0→1→2→3→0 顺时针流转;AdvanceTurn 跳过已淘汰玩家;仅剩一人存活时 SetGameOver。
3.2 核心枚举
TurnPhase(五阶段):
1 | TurnStart(0) → MainAction(1) → Judgment(2) → Discard(3) → TurnEnd(4) |
FlowStateType(状态栈条目类型):
| 值 | 含义 |
|---|---|
| TurnPhase | 常规阶段(栈调试描述用) |
| ShootResponse | 开枪响应窗口 |
| DyingResponse | 濒死自救 |
| TraitorChoice | 血量 1 点转职抉择 |
MainActionType:Shoot / UseItem / EndAction。
ControlType:Human / AI(断线时人类可切 AI 托管)。
3.3 GameStateStack:分层状态栈
1 | public class GameStateStack |
规则:栈顶为当前唯一活跃流程;下层挂起保留协程断点;临时流程结算后 Pop 恢复。支持开枪响应内再嵌套濒死等多层场景。
3.4 事件总线与技能
GameEventBus 发布 GameEventType(TurnStart、ShootDeclared、DamageApplied、PlayerDying、ItemAfterUse 等)。角色技能与道具技能统一注册为 SkillExecutor 实例,被动技能订阅事件、主动技能由流程层在决策后调用 ExecuteActive。
背叛者转职:玩家血量 1 且未抉择时进入 TraitorChoice 压栈;接受则 ConvertToTraitor 注销全部角色技能,仅保留开枪/道具基础权限。
3.5 PresentationCueRelay:权威表现指令
流程结算点不直接操作 HUD,而是构造 PresentationCueSpec 交给 IDebugHost.QueuePresentationCue:
| Cue 类型 | 触发场景 |
|---|---|
| Shoot | 开枪结算(含 bullet_type = FinalBullet) |
| ItemFly / StealHand / EjectDrink | 道具使用 |
| HealthDelta / HitImpact | 血量变化 |
| DealCard | 发牌(回合开始或被动奖励) |
| MagicianTrajectory | 魔术师弹道翻转 |
| HideItemSlot | 道具消耗前隐藏栏位(供 ItemFly 捕获起点) |
CanEmit 条件:session.IsNormalPlayMode && session.Debug != null。Debug 面板模式跳过表现 cue,加速测试。
4. 流程控制模块详解
4.1 模块定位
FlowController 是游戏顶层调度中枢:负责回合流转、五阶段推进、全局状态栈、事件广播、响应链轮询。对标三国杀式「主流程 + 插入响应窗口」机制。
4.2 对局主循环
1 | public IEnumerator RunGameCoroutine() |
开局 PresentStartingDealsIfAny:取出 _startingDealBatches,经 PresentationCueRelay.EmitDealCards 播发牌动画,WaitActionPresentation 等待结束后再刷新 HUD。
4.3 单回合五阶段
1 | public IEnumerator RunTurnCoroutine(PlayerEntity player) |
各阶段要点:
| 阶段 | 行为 | 玩家响应 |
|---|---|---|
| TurnStart | 强化剂衰减、发回合道具(默认 2 件)、枪膛空则装弹、TurnStart 被动 | 无 |
| MainAction | 循环:开枪 / 用道具 / 结束行动 | 是(主行动决策) |
| Judgment | 濒死结算、血量 1 转职扫描 | 是(濒死/转职) |
| Discard | 道具超上限则循环弃牌 | 是(弃牌决策) |
| TurnEnd | TurnEnd 被动、清理临时标记 | 无 |
1 | flowchart TD |
4.4 主行动决策循环
1 | private IEnumerator RunMainActionPhase(PlayerEntity player) |
连续无法开枪达到上限(MaxFailedShootAttemptsBeforeAutoEnd)时自动结束主行动。
4.5 开枪完整流程
1 | private IEnumerator ExecuteShoot(PlayerEntity shooter, string targetId, Action<bool> onExecuted) |
响应轮询规则(RunShootResponseWindow):
- 起点:当前回合玩家的顺时针下家。
- 依次询问存活玩家;仅 魔术师且道具数 ≥ MagicianResponseMinItems 时轮询(
ShouldPollShootResponse)。 - 发动「弹道」主动技能则翻转
ctx.FinalBullet,播MagicianTrajectorycue。
结算规则(ResolveShoot):
Chamber.PopNext出弹;实弹则DamageAboutToApply事件链 → 可能护身符格挡 →EnhancementItemService.ApplyDamage。- 锁枪规则:自射空弹不锁;对他人开枪或任意实弹均锁枪(
ShootLockedThisTurn = true)。 - 实弹击杀:背叛者累计
TraitorKills;目标 Health≤0 进入HandlePlayerDying。 - 结算后
PresentationCueRelay.EmitShootResolve(bullet 用 FinalBullet,非初始 Peek 值)。
4.6 濒死与转职
濒死(HandlePlayerDying):压栈 DyingResponse → RequestDyingDecision → 可选奶茶/道具自救 → 仍 ≤0 则清空道具并 Eliminate。
转职(HandleTraitorChoice):压栈 TraitorChoice → RequestTraitorDecision → 接受则 ConvertToTraitor。
主行动中 ExecuteUseItem 后可调用 ProcessImmediateDying 即时处理濒死,不等到 Judgment 阶段。
4.7 弃牌阶段
RunDiscardPhase:当 player.ItemCount > player.GetItemCap() 时循环 RequestDiscardDecision,移除选中道具并记录战报。
4.8 PushAuthoritativeHudSnapshot 条件编译
1 | private void PushAuthoritativeHudSnapshot(FlowBoundaryKind boundary = FlowBoundaryKind.ActionSettle) |
单机 Unity 编译时不调用(本地 HUD 由 DebugService.RefreshHud 驱动);服务端 HEADLESS 编译时每次行动/阶段/回合边界推送 FlowStep。
4.9 IDebugHost 接口契约
流程层通过以下方法与宿主交互:
| 方法 | 用途 |
|---|---|
RequestMainActionDecision | 主行动:开枪/道具/结束 |
RequestShootResponseDecision | 开枪响应(魔术师弹道) |
RequestDyingDecision | 濒死自救 |
RequestTraitorDecision | 转职抉择 |
RequestDiscardDecision | 超限弃牌 |
WaitActionPresentation | 等待表现动画结束 |
QueuePresentationCue | 入队表现 spec |
PushAuthoritativeSnapshot | 联机推送权威快照(服务端) |
LogEvent | 战报文本 |
NotifyPhase / RefreshHud | 阶段与 HUD 元数据 |
5. 客户端单机实现
5.1 启动链路
1 | // GameEntry.Start |
DebugService 实现 IDebugHost:承担 IMGUI Debug 面板或 NormalHud 的决策 UI、本地 cue 协程队列、战报日志。
5.2 决策路径
DebugService.RequestDecision 分支:
- AI 或托管(
AiDelegation.ShouldAutoDecide)→ExecuteAiDecision→AIDecisionMaker立即返回。 - 人类 →
WaitHumanDecision:设置_waitingHuman = true,UI 展示选项;玩家调用SubmitHumanDecision(decision)后协程继续。
Debug 模式支持控制台指令(shoot player2 | use item1 | end);正常模式由 NormalGameUIInputView 渲染按钮与道具栏。
5.3 表现播放
DebugService.QueuePresentationCue:
PresentationCuePlayback.TryBuildFromSpec将 spec 转为 Unity 协程。- 入队
_presentationCueQueue。 WaitActionPresentation顺序 Dequeue 并yield return每个 cue 协程。- 播放完毕后
FlushPendingHealthDeltas等收尾。
单机无 revision 概念;流程 yield 等待本地动画自然结束后再开下一决策窗。
1 | flowchart LR |
5.4 玩法模式差异
| PlayModeKind | 表现 | 手牌可见性 |
|---|---|---|
| Simple | NormalHud,无 Debug 面板 | 他人手牌可见 |
| Master | NormalHud | HideOpponentHandItems=true,他人道具栏显示卡背 |
| Debug | 完整 Debug IMGUI + 可选 AI 托管 | 全信息 |
6. 服务端权威实现
6.1 对局启动:GameLoopService
1 | public void StartMatch(RoomModel room, GameStarted started) |
RunMatchAsync:
1 | await match.FlowPublisher.PublishMatchStartAsync(); |
6.2 MatchInstance 与 MatchStore
MatchInstance 持有:GameSession、ServerDebugHost、FlowPublisher、RoomModel、Seed、CancellationTokenSource。
PresentationSync 锁 + PresentationPipeline Task 链:串行化 FlowStep 广播,避免并发 Commit 乱序。
MatchStore 维护 MatchId / PlayerId / RoomId 三向索引,供 Hub 路由决策与断线处理。
6.3 ServerDebugHost:决策与推送
AI 即时决策:
1 | if (player.Control == ControlType.AI || AiDelegation.ShouldAutoDecide(player)) |
人类等待 WebSocket:
1 | BeginWait(player, session, kind, shoot); // 设置 PendingDecision |
TrySubmitDecision(Hub 调用链:GameLoopService.TrySubmitDecision):
- 校验
PendingDecision.Scene与 payload 一致。 - 校验
player_id为当前等待者。 - 主行动用道具时服务端预校验
MainActionRules.CanUseItem。 - 写入
_resolvedDecision,_waiting = false,PublishDecisionCloseAsync。
表现等待:
1 | public IEnumerator WaitActionPresentation(GameSession session) |
PushAuthoritativeSnapshot 映射 boundary 到 Publish 方法:
| FlowBoundaryKind | Publish 方法 |
|---|---|
| ActionSettle | PublishActionSettleAsync(含 hold) |
| TurnChange | PublishTurnChangeAsync |
| PhaseChange | PublishPhaseChangeAsync |
| DecisionOpen | PublishDecisionOpenAsync |
| DecisionClose | PublishDecisionCloseAsync |
| MatchStart | PublishMatchStartAsync |
6.4 FlowPublisher:revision 合并广播
核心约束:同一逻辑步内,先 BufferCue / BufferEvent,再 Publish,保证 cue 与 snapshot 同一 revision。
1 | private Task PublishAsync(ProtoFlowBoundary boundary, bool includeHold) |
BuildResyncStep(viewerPlayerId):断线重连用,不递增 revision,boundary = MATCH_RESYNC。
大师模式(Master):BroadcastFlowStepPerViewerAsync 对每个真实玩家单独克隆 Step,掩码他人手牌事件文本与 cue,重建 per-viewer snapshot。
6.5 CoroutineDriver:无 Unity 协程运行时
1 | public static async Task RunAsync(IEnumerator root, Func<Task>? onIdle, CancellationToken ct) |
6.6 断线、重连、超时
| 事件 | 服务端行为 |
|---|---|
| 人类断线 | HandleDisconnect:entity.Control = AI,广播最新 snapshot |
| 重连 | ResyncPlayerAsync:恢复 Human 控制,单播 MatchResynced(含完整 FlowStep + RoomUpdated) |
| 决策 30s 超时 | AI 代打,LogEvent [AI] 决策超时,PublishDecisionClose |
| 对局异常 | 不广播 GameOver(避免多人存活误弹结算),Remove match,房间回 Waiting |
6.7 PresentationHoldCalculator(双端共用)
| Cue 类型 | 估算时长 |
|---|---|
| Shoot | 2800ms |
| ItemFly | 1200ms |
| StealHand | 1600ms |
| EjectDrink | 1400ms |
| HealthDelta / HitImpact | 1150ms |
| MagicianTrajectory | 2000ms |
| DealCard | 800ms × 张数 |
| HideItemSlot | 50ms |
| 合计上下限 | 400ms ~ 6000ms |
7. 联机 FlowStep 协议与同步模型
7.1 设计优先级
| 优先级 | 层级 | 职责 |
|---|---|---|
| 1 | 流程流转 | 回合/阶段/决策权何时转移;服务端 gate 下一决策 |
| 2 | 状态同步 | 血量、道具、枪膛、PendingDecision 等权威 HUD 数据 |
| 3 | 特效推送 | 开枪、道具飞行、回血/掉血飘字等纯表现 |
核心原则:同一逻辑步使用同一个 revision,cue 与 snapshot 捆绑下发,客户端按序处理。
7.2 FlowStep 消息结构
1 | message FlowStep { |
GameStateSnapshot 主要字段:
| 字段 | 含义 |
|---|---|
| match_id / turn_owner_id / phase | 对局与回合上下文 |
| flow_state | 状态栈顶描述(调试/Intel) |
| chamber | 枪膛剩余、已知下一发、已消耗序列 |
| players[] | 各玩家血量、道具栏、ControlType、shoot_locked 等 |
| pending_decision | 当前等待决策的玩家、scene、timeout、wait_started_at |
| revision / boundary | 与 FlowStep 对齐 |
PlayerDecisionPayload 上行字段:scene、main_action、target_player_id、item_instance_id、use_shoot_response_skill、use_dying_save、accept_traitor_conversion、items_to_discard 等。
Hub 消息种类:HUB_MESSAGE_KIND_FLOW_STEP = 111;重连 MatchResynced 含完整 FlowStep state(boundary = MATCH_RESYNC)。
旧版分通道 game_state_snapshot / presentation_cue / game_event 仍保留解析,FlowStep 启用后对局内应忽略(OnlineSession 丢弃 stale legacy 消息)。
7.3 FlowBoundaryKind 与客户端行为
| boundary | 含义 | 客户端行为 |
|---|---|---|
| ACTION_SETTLE | 一次行动结算完成 | 播 cues → 应用 snapshot;服务端按 hold 等待 |
| TURN_CHANGE | 回合切换 | Abort 积压特效 → 全量刷新 HUD |
| PHASE_CHANGE | 阶段切换 | 同上 |
| DECISION_OPEN | 打开决策窗 | 应用 snapshot + 展示决策 UI;不 abort 特效 |
| DECISION_CLOSE | 提交/关闭决策 | 关闭决策 UI;等后续 ACTION_SETTLE |
| MATCH_START | 对局开始 | Abort + 初始化 HUD |
| MATCH_RESYNC | 断线重连 | Abort + 直接应用 resync snapshot |
7.4 服务端推送流水线
1 | FlowController 结算点 |
决策推送:人类进入等待发 DECISION_OPEN(含 pending_decision);提交或超时发 DECISION_CLOSE。不再每 300ms 轮询推全量快照。
7.5 联机对局配置(OnlineMatchOptions)
| 配置 | 默认 | 说明 |
|---|---|---|
| AiFillMax | 3 | AllowAiFill 开局最多补位 AI 数 |
| AiStartingHealth | 2 | AI 开局生命(人类仍为 3) |
固定四角座位(seatIndex 0–3),UI 旋转使本机永远在左上;空座隐藏即可。
8. 客户端联机消费层
联机客户端不运行 FlowController,职责是:按 revision 顺序接收 FlowStep → 更新权威态 → 播放 cue → 提交显示投影 → 在 DECISION_OPEN 时展示决策 UI → 上行 SubmitDecision。
8.1 组件链路
1 | OnlineSession(Hub 消息入口) |
8.2 OnlineFlowOrchestrator
Enqueue(step, dispatch):revision ≤ lastApplied 丢弃;乱序入SortedList缓冲。dispatch返回 true → 设置_presentationGated,暂停后续派发。NotifyPresentationIdle→ 清 gate,继续TryDispatchSequential。ApplyResync/FastForwardToRevision:重连或追帧用。
8.3 OnlineHudState:双态模型
| 状态 | 变量 | 用途 |
|---|---|---|
| 权威态 | _authoritative + _authoritativePlayers | 决策合法性、Intel、PendingDecision |
| 显示投影 | _display | 实际渲染 HUD 的快照 |
| 待提交 | _pendingFullCommit | 表现 busy 时排队,idle 后 CommitDisplay |
表现播放中:权威态立即更新,显示投影延后,避免动画未播完道具栏已刷新导致穿帮。
OnlineHudCoordinator.ShouldDeferHudCommit 统一判定:决策边界不延后;硬边界不延后;表现 busy / orchestrator gated / 有待提交时延后。
8.4 客户端消费状态机
| 状态 | 行为 |
|---|---|
| Idle | Orchestrator 可派发下一 revision |
| Presenting | 播放 cue 和/或 hold 计时;门禁后续 Step;权威态更新,显示排队 |
| Applying | 表现 idle → CommitDisplay → NotifyPresentationIdle |
硬边界(TurnChange / PhaseChange / MatchStart / MatchResync)调用 AbortQueuedPresentation,清 gate,立即全量刷新。
8.5 DispatchOnlineFlowStep 处理顺序
- 判定
ShouldAbortPresentation→ abort + 清 pending commit。 BeginFlowStep:HideItemSlot cue 前TryCaptureItemFlyStart。- Cue 入队
OnlineMatchPresentationPlayer。 SetAuthoritative(snapshot)写权威态。- DecisionOpen/Close 边界立即刷新决策 UI(不能等表现 idle,否则「提交中」状态卡住)。
- events 追加战报。
- 按规则 QueueFullCommit 或立即 CommitDisplay。
8.6 道具栏与 ItemFly 策略
- 快照
players[].items为权威数量。 - 处理顺序:先 cue 入队,再写权威态;表现 busy 时不重建道具栏 DOM。
_onlinePresentationHiddenItems仅本机 optimistic hide;观察他人以 snapshot 为准。- 发牌
pendingReveal有 12s fallback(TryFinalizeOpeningDealPresentation)。
8.7 开枪特效规则
- 权威子弹类型:
PresentationCue.bullet_type=ctx.FinalBullet(魔术师翻转后的最终子弹)。 - 同一 FlowStep 内 Shoot cue 入队成功后,禁止用战报 regex 二次播放。
- 实弹 + 护身符:Shoot 仍播实弹动画,另播护身符反馈 cue,不播 HealthDelta。
8.8 重连 MatchResynced
1 | Hub 收到 MatchResynced |
服务端 ResyncPlayerAsync 同时恢复 ControlType.Human 并 PublishSnapshotAsync 广播给他人。
8.9 双通道兼容
| 通道 | 入口 | FlowStep 启用后 |
|---|---|---|
| FlowStep | FlowStepReceived | 主路径 |
| legacy snapshot | SnapshotReceived | 表现 busy 时 partial;校验 IsStaleLegacySnapshot |
| legacy cue/event | PresentationCueReceived | OnlineSession 丢弃,防重复 |
OnlineSession.UsesFlowStepChannel:收到首帧 FlowStep 后切换,丢弃 stale legacy。
1 | sequenceDiagram |
9. 双端对照与架构模式总结
9.1 实现对照表
| 维度 | 客户端单机 | 服务端联机 | 客户端联机 |
|---|---|---|---|
| 流程引擎 | 本地 FlowController | 同一份 FlowController | 不运行 |
| 宿主 | DebugService | ServerDebugHost | OnlineHudCoordinator |
| 决策 | IMGUI/NormalHud | WS SubmitDecision | 同左,经 Hub 上行 |
| AI | AIDecisionMaker | GameDebugAiResolver | 无(服务端算) |
| 表现 | 本地 cue 协程 | cue 进 FlowStep | OnlineMatchPresentationPlayer |
| 快照 | RefreshHud 本地 | FlowPublisher → proto | ApplyOnlineSnapshotData |
| 随机数 | 本地 Random | seed 注入 | 不使用本地 Random |
| 协程 | Unity StartCoroutine | CoroutineDriver | Unity 仅播表现 |
9.2 三种架构模式组合
- 分层状态机 + 状态栈:主回合五阶段 + 开枪/濒死/转职插入流程。
- 协程脚本化工作流:
yield return Request*Decision表达异步人机交互;服务端 CoroutineDriver 移植。 - 权威服务器 + 捆绑同步:逻辑步 = FlowStep(revision, boundary, snapshot, cues, events, hold)。
9.3 联机客户端不跑 FlowController 的原因
- 防作弊:Random、伤害、道具必须以服务端为准。
- 单点真相:避免客户端先算再上报的冲突。
- 表现可慢不可错:动画可延长,revision 严格单调。
10. 扩展接入与调试验证
10.1 新增动作接入步骤
- 流程层(GameCore):在
FlowController结算点调用PresentationCueRelay.Emit*;新决策场景扩展IDebugHost.Request*Decision。 - 表现时长:
PresentationCueKind+PresentationHoldCalculator.EstimateCueMs。 - 协议:proto
PresentationCueKind+PresentationCueMapper.ToProto。 - Unity 表现:
PresentationCuePlayback.TryBuildFromSpec新分支。 - 不要单独广播 snapshot/cue/event;统一
PushAuthoritativeSnapshot一次 Commit。
10.2 调试要点
- 观察
FlowStep.revision是否严格单调递增。 - 特效丢失:检查 cue 是否为空、
TryBuildFromSpec是否 false。 - 道具数量错乱:检查 snapshot.items 与是否误用 hidden 集合过滤他人。
- 服务端:确认 BufferCue 在 Publish 之前完成。
10.3 自动化测试
客户端 FlowSync 单元测试(无需启动服务端):
1 | cd server/tools/ProtoSmokeTest |
覆盖:OnlineFlowOrchestrator 排序/门禁、OnlineHudState 显示延后、OnlineFlowSync 规则。
联机对局时序冒烟(需 API + 自动对局):
1 | cd server/tools/ProtoSmokeTest |
校验:revision 连续、bundle 内 snapshot/cue revision 对齐、ActionSettle 含 cue 时 hold>0、无 legacy 分通道消息。
Proto 代码生成:
1 | cd server && ./scripts/generate-proto.sh |
11. 源码文件索引
GameCore(Assets/Scripts,服务端链接编译)
1 | Flow/FlowController.cs 流程主控:五阶段、响应链、压栈 |
服务端(server/src/NewRingGame.Server)
1 | Services/Game/GameLoopService.cs 对局启动、决策路由、断线重连 |
协议
1 | server/proto/v1/game.proto FlowStep、GameStateSnapshot、PlayerDecisionPayload |
联机客户端(Assets/Scripts/Network + UI)
1 | Network/OnlineSession.cs Hub 连接、消息分发 |
文档版本:GameCore 共用 FlowController + FlowStep 联机方案。归档日期:2026-06。