任务与剧情流程
Chapter / Act / Segment 状态机、分支解锁、断点恢复、主界面任务展示与奖励发放。
工作项目 · 客户端核心系统
一款以明代南京秦淮河流域为背景的叙事 RPG,包含 NPC 对话、全屏演出、AR 小游戏与多人对战。 负责范围为客户端核心系统——把不同内容形态组织为可配置、可恢复的任务流程,并完成多人架构、场景生命周期与内容工具建设。

CASE SUMMARY
负责客户端核心模块从方案设计、功能实现到真机联调与工程排障。
Chapter / Act / Segment 状态机、分支解锁、断点恢复、主界面任务展示与奖励发放。
NGO 客户端—服务器架构、专用服务器、云端房间生命周期、积分点、AR 挑战与比赛结算。
AR 场景叠加加载、任务单元接入、完成与评分判定、返回主场景后的状态恢复和流程续接。
ScriptableObject 内容配置、剧情绑定校验、NPC 动画接线、编辑器排障与工程问题修复。
定位源适配、地图坐标转换、围栏稳定性、权限状态和 Android 真机链路。
整条链路是:读取任务状态 → 分发 Segment → 执行对话 / 视频 / AR / 多人局 → 完成结算 → 保存并恢复进度。
地理触发是内容入口之一,但核心工作是后续任务调度、玩法执行与状态恢复。
| 环节 | 做的事 | 要解决的问题 |
|---|---|---|
| 任务状态 | 读取章节、前置与已确认游标,确定当前需要执行的最小内容单元。 | 不同内容不能依赖一条硬编码流程,冷启动也不能重复播放。 |
| 世界触发 | 任务满足前置后,由地理围栏或剧情事件激活章节入口。 | 触发条件与实际任务执行需要解耦。 |
| 章节推进 | 章节 / 幕 / 段落三级状态机,按段落类型分发对话、过场、AR、联机大厅。 | 内容形态差异大,不能写成一串硬编码流程。 |
| 玩法执行 | 叠加加载 AR 场景、激活对应任务单元、按完成情况评级并返回主场景。 | 场景切换时相机、玩家与 UI 的归属容易错乱。 |
| 联机对战 | 地理围栏建房、玩家同步、积分点生成与占领、AR 挑战、比赛结算回剧情。 | 对战是一局一份世界状态,必须与常驻业务分开。 |
| 结算与恢复 | 本地记录已完成幕与游标,发放道具与图鉴奖励,冷启动后续接。 | 玩家随时可能杀进程、切场景或中途离开。 |
投入最多的四个子系统。每块按「问题 → 做法 → 结果」展开,末尾补充关键设计决策的取舍理由。
问题:玩法完全依赖真实位置,但开发和测试几乎都在室内。 早期代码直接读 SDK 回调,导致测试必须出门,定位源一换就要动业务逻辑。
做法:定义统一的位置提供者接口,把真实 GPS、高德 SDK、键盘、触摸拖动与虚拟摇杆 全部实现成可替换的来源,由一个管理器单例对外广播同一种位置数据; 再用适配器把经纬度按等距投影换算成 Unity 世界坐标,驱动玩家与围栏判定。 高德侧还要处理国内坐标系与国际坐标系的转换差异。
结果:切换定位模式只需改一个枚举,业务代码零改动; 室内用摇杆就能跑完整章节流程,真机出门用同一套逻辑验证。
为什么不直接用 SDK 的回调?
因为定位来源会变,而且必须支持模拟。抽一层接口之后,模拟摇杆和真实 GPS 对下游是同一种数据,测试成本直接降下来。
只授权「大致位置」时会怎样?
早期表现是永远停在加载:定位服务不启动,世界原点不置位,POI 生成的等待循环一直卡着。后来改成三态检测加引导,并给等待加超时兜底。
问题:内容形态差异极大——有 NPC 对话、有全屏过场、有 AR 小游戏、还有多人对战, 但它们都要串在同一条章节推进里,并且玩家可能在任何一步退出。
做法:把内容拆成章节 / 幕 / 段落三级: 章节承担前置依赖与分支关系,幕绑定地理坐标与围栏半径,段落是最小执行单元并带类型。 推进器只做一件事:取出下一个未完成段落,按类型分发给对应的执行者。 章节数据全部落在 ScriptableObject 上,策划改内容不用碰代码。
结果:支撑了 8 章内容,包含三条并行支线的自由选择、隐藏任务, 以及断点续接——冷启动后能回到正确的段落,已完成的演出不重播。
为什么要拆到「段落」这一层?
因为一幕里往往是「先对话、再玩 AR、再对话」。如果只到幕,中途退出就只能整幕重来;拆到段落之后恢复粒度才够细。
分支怎么保证不会走乱?
章节声明前置依赖,同一分支组内本地只保留一个未完成的已选支线;完成后清除选择并重新弹出入口。终章同时依赖三条支线,三线全通才解锁。
问题:项目最初是 Photon Fusion 的 Host 模式,房主即服务器。 这在户外多人对战里不合适:房主退出整局就断,且积分与胜负判定放在玩家设备上不可信。
做法:迁到 Netcode for GameObjects 的客户端-服务器模式, 搭建独立运行的专用服务器;玩家位置由客户端上报、服务端裁决并同步, 得分点与比赛状态由服务端生成和结算。同时用条件编译把服务端不需要的 UI 与渲染剥离, 让服务端能以无图形模式运行。
结果:对战不再依赖某个玩家的设备; 补上了连接审批与房间容量上限、队伍标识、断线与踢出后的回主场景流程和重连提示。
为什么对战不能和常驻业务放在一个进程里?
端口不是问题,生命周期才是。一局结束要销毁的是这一局的世界状态,而账号与业务服必须常驻;一个进程里做不到「打完就杀」。而且一个进程一份会话,不拆进程房间就形同虚设。
迁移过程中最麻烦的是什么?
移动权威的归属。原先客户端直接改位置,迁移后要改成上报输入、由服务端执行移动,否则会出现两处同时写位置导致的抖动与拉回。
问题:每章 AR 玩法都是独立场景,但它们要能被剧情流程统一调度, 并且完成后必须干净地回到主场景,不留下相机、玩家与 UI 的残留状态。
做法:用叠加加载把 AR 场景挂到主场景之上,切换时统一处理相机堆叠、 玩家可见性与主界面显隐;场景内用任务单元封装单个小游戏的开始、完成、重玩与评级; 完成通知经事件通道回到流程推进器,再决定是否卸载场景并进入下一段。
结果:新增一个 AR 玩法只需实现任务单元并配置章节数据, 不需要改动流程推进逻辑;完成判定与评分口径在各章之间保持一致。
从 AR 场景返回时最容易出什么问题?
主界面没有正确恢复,以及玩家与远端玩家的渲染被整体隐藏后没有还原。这类问题都要求场景进出的显隐逻辑集中在一处,不能各写一份。
为什么用事件通道而不是直接调用?
AR 场景是叠加加载的,场景内脚本和主场景的流程推进器互相拿不到引用。用 ScriptableObject 事件通道之后,两边只依赖同一个资产,不需要互相查找对象。
后三张是独立实现的 AR 交互玩法。它们各自是叠加加载的场景, 由统一的任务单元接口接入章节流程,完成判定与评级口径保持一致。
玩法之外,项目里有一部分工作是把开发环境和真机问题按住。这部分同样关键,因为它决定团队能不能顺利往下做。
| 问题 | 定位过程 | 处理 |
|---|---|---|
| 编辑器像卡死、按钮点不动 | 表面看是 UI 失灵,实际是某个逐帧脚本用越界值索引输入设备,每帧抛异常,异常栈与控制台重绘吃满主线程。 | 改成先校验再读取,并加一个异常风暴守卫:一秒内异常超过阈值就自动暂停并报错,避免同类问题再拖垮编辑器。 |
| 地图材质异常、无法合批 | 一个场景里被写入上千个内嵌材质实例,另一个场景则残留大量空材质槽;根因是编辑器工具在内存里造材质后随场景保存。 | 逐批撤销无效覆盖、重建缺失材质资源并留好备份,同时定位到生成工具这个源头。 |
| 大场景打开就卡住 | 约两万个渲染对象、没有 LOD、未烘遮挡剔除,编辑器视图直接把显卡拖垮。 | 先用可见性守卫让编辑器可用,并把根治方案(分块与剔除)记录成待办,避免临时手段被当成修复。 |
| 联机连不上但日志显示成功 | 建房校验走的是一条独立连接,它成功不代表游戏连接已建立,两者要分别看日志。 | 按连接分层排查,并把关键状态与失败原因补进日志,减少反复复现的成本。 |
| 真机日志乱码 | 中文日志经设备日志再编码后变成乱码,出问题时反而看不懂。 | 约定日志只用 ASCII,中文留给界面文案,并在关键日志里补上操作名、状态码与相关 id。 |