18.3 进入高级卷
卷二到这里就结束了。回头看一眼起点:第 1.1 节我们只有一张需求规格——功能需求 FR-01 到 FR-08、非功能需求 NFR-01 到 NFR-06、用例 UC-01 到 UC-05,加一个判定用的 evaluateAcceptance;现在《方块冲刺》是一个能在 H5、微信小游戏、原生三端跑起来、能灰度、能热更、能看数据的产品。这一节不写新功能,只做三件事——盘点、划界、交接。
本节是《Cocos 游戏开发实战》的收尾:盘点全卷从工程搭建到上线运营的完整轨迹,点出三个贯穿始终的关键决策,明确卷二没讲、留给高级卷的内容边界,交代高级卷《Cocos 游戏开发高级编程》要回答的问题,并给出「不靠记忆、靠查
cc.d.ts」的实证工作方法与常见误区澄清。最后列出继续打磨《方块冲刺》的三条路。
18.3.1 卷二做完了什么
把 18 章按主题归拢,你能看清这条从「demo」到「产品」的路径:
| 阶段 | 章节 | 产出 |
|---|---|---|
| 工程地基 | 1–6 | 目录约定、编辑器工作流、组件体系、资源管线、启动流程、UI 系统 |
| 玩法实现 | 7–12 | 核心循环、数据驱动关卡、动画、音频、存档、平台 SDK |
| 质量与性能 | 13–16 | Draw Call 优化、内存、Profiler、三端构建、分包、冒烟、埋点 |
| 上线运营 | 17–18 | 灰度回滚、热更、活动配置、发布清单、复盘 |
如果只记一句话,那就是:卷二教会你的是「怎么把一个 Cocos 工程组织成一个能长期维护、能上线运营的产品」。
它没教你引擎是怎么实现的——那是高级卷的事。
回望这条路径,最值得说的是它不是一条直线。第 7 章做手感时,我们把跳跃参数硬编码进了脚本;到第 8 章才意识到要数据驱动,回头把参数搬进 configs/levels.json。第 14 章做三端构建时,才发现第 11 章的存档键名在三端不一致,又回头统一。真实的工程就是这样反复回修的——本卷把这些回修过程如实写了出来,因为「一次写对」是不存在的,能快速发现并回修才是工程能力。
18.3.2 三个贯穿全卷的关键决策
卷二里有很多具体技巧,但真正值钱的是三个贯穿始终的决策。它们不依赖引擎版本,换个项目、换个引擎也成立。
决策一:能配置化的东西,都做成数据。
从第 8 章起,玩法参数被赶进 configs/levels.json;到 17.3,活动被赶进 activities.json;连灰度开关都在 remote.json 里。这条决策的回报在 17.2 兑现——只有数据才能热更。如果手感参数写死在代码里,你连调一个跳跃力度都要发版。
决策二:所有远端配置,都有本地兜底。
17.1 的 remote.json、17.3 的 activities.json,都在 assets/configs/ 下留了一份本地版本。远端拉不到就用本地的,且本地版本永远比远端保守。这条决策让你在 CDN 挂掉时,游戏还能玩。
决策三:能纯逻辑化的部分,与引擎解耦。
compareVersion、hashBucket、validateActivities——这些都不 import ... from 'cc',因此能被 vitest 直接单测。引擎相关的代码最难测,把逻辑挤到引擎之外,测试成本立刻降一个数量级。
三个决策合起来就是卷二的方法论:数据化、可兜底、可单测。
举一个具体例子看三者如何配合。假设运营要在周末做「双倍金币」活动:
- 数据化:活动写在
activities.json,不写进代码。 - 可兜底:远端拉不到,就用
assets/configs/activities.json里的本地版本。 - 可单测:
validateActivities是纯函数,配错时间窗在本地就报错,不会上线才炸。
三件事各自不难,难的是每次都这么想。卷二的价值就在于把这套思路变成了肌肉记忆。
18.3.3 把《方块冲刺》当成模板
卷二结束时,工程的目录结构长这样。它可以直接当成下一个 2D 小游戏的模板:
block-dash/
├── assets/
│ ├── scenes/ # main.scene、level.scene
│ ├── prefabs/ # 角色、金币、道具、弹窗
│ ├── textures/ # 图集与小图
│ ├── audio/ # BGM 与音效
│ ├── configs/ # levels.json / activities.json / remote.json(本地兜底)
│ ├── bundle-levels/ # 关卡分包(bundleName = bundle-levels)
│ └── scripts/
│ ├── core/ # 纯逻辑:compareVersion / hashBucket / validateActivities
│ ├── game/ # 玩法:角色、碰撞、关卡推进
│ ├── ui/ # 弹窗栈、列表、适配
│ └── platform/ # remote-config / hot-update / liveops
├── settings/v2/packages/ # project.json / builder.json
└── build/ # 按版本归档:web-mobile/1.3.0/ 等
看这个结构就能想起卷二的骨架:core/ 不碰引擎、platform/ 处理端差异、configs/ 放可热更数据、bundle-levels/ 装分包。下次开新项目,先把这个目录复制过去,能省掉很多「这个文件该放哪」的纠结。
四条命名约定也一并带走:
- 目录与文件名用英文小写 + 短横线,不用中文、不用大写。
- 一个模块只导出一类东西:
core/出纯函数,platform/出适配器。 - 常量集中放
core/,比如分包名BUNDLE_LEVELS。 - 配置文件的键名与代码里的类型字段一一对应,改一处要能搜到全部引用。
这四条看着琐碎,但它们决定了半年后你还敢不敢改这个工程。
18.3.4 卷二刻意没讲什么
明确划界,免得你以为漏了什么:
- 引擎内部机制:渲染管线怎么走、合批的判定算法、GC 什么时候触发。卷二只在 13 章用到「现象级」的结论(比如「合批要同图集」),没讲原理。
- 自研渲染 / 自定义材质:卷二用的是 Cocos 内置渲染,没有碰 Effect 与自定义 shader。
- 引擎源码级调试:怎么读引擎的 TypeScript 源码、怎么打点进引擎内部。
- 服务端:联机、房间、帧同步一概没讲。那属于《Cocos 游戏联机实战》。
- 商业与发行:买量、变现设计、Steam 发行,本套书明确不写。
这些不是「不重要」,而是「不属于卷二的定位」。卷二的定位是实战:把引擎当工具用熟。用熟之后再问「为什么」,才问得动。
再补一句关于「自研渲染」的说明。很多人一上手就想写自定义 shader,觉得那才叫「高级」。但实战经验是:80% 的 2D 项目用内置渲染就够了,写 shader 往往是为了解决一个本来该用图集解决的问题。卷二选择不碰 shader,是想让你先把内置能力用透——第 13 章的 Draw Call 优化,比手写 shader 更能提升帧率。
18.3.5 高级卷要回答的问题
高级卷《Cocos 游戏开发高级编程》会接着《方块冲刺》往下挖,回答这类问题:
- 第 13 章说「合批能降 Draw Call」,为什么?引擎在什么时候打断合批?
- 第 6 章的 UI 适配「看起来对了」,底层的坐标变换与锚点是怎么算的?
- 第 9 章的 Tween「能跑」,但它是每帧重算还是缓动插值?什么时候该用
Tween,什么时候该自己写update? - 第 4 章的引用计数「会自动释放」,具体的引用关系是怎么建立的?
你会发现这些问题都有一个共同点:它们追问的是「为什么」而不是「怎么用」。卷二给的是「怎么用」,高级卷给的是「为什么」。高级卷的写法也和卷二不同——它会直接打开 cc.d.ts,用声明文件里的类型与注释来实证每一个结论,而不是只给「这样写就对了」。
把「怎么用」和「为什么」的差别列成表,你会更清楚高级卷的定位:
| 问题 | 卷二回答 | 高级卷回答 |
|---|---|---|
| 合批 | 同图集能合批 | 合批的判定条件与打断时机 |
| UI 适配 | 用 SHOW_ALL 就行 | 锚点与坐标变换的数学 |
| 动画 | 用 Tween 做 | Tween 的内部调度与性能边界 |
| 资源 | load 后记得 release | 引用计数如何建立与解构 |
| 生命周期 | onLoad 在 start 前 | 引擎如何调度组件回调 |
为什么高级卷要这么写?因为**「怎么用」会过时,「为什么」不会**。sys.Platform.WECHAT_GAME 这个枚举成员可能在某天改名,但「判断平台要靠引擎暴露的枚举而不是自己猜字符串」这条原则不会变。卷二教你的是具体的 API,高级卷教你的是当 API 变了,你怎么自己找到答案。
这也是为什么本套书坚持三层可运行性口径:L1 真跑、L2 真做类型检查、L3 如实标注不可运行。能验证的结论才敢写,不能验证的就明说——这条原则从卷一贯穿到高级卷。
18.3.6 从「会用」到「会查」:cc.d.ts 实证方法
高级卷里有一件事会贯穿始终,值得在这里先交代:不要凭记忆写 cc API,要会查 cc.d.ts。
本书写作时,每一个 cc 符号落笔前都对着 @cocos/creator-types@3.8.8 的 engine/cc.d.ts 核过。你也可以这么做——查一个符号是否存在,只消在声明文件里搜它的导出行:
grep -nE 'export (declare )?(class|interface|function|const) Component\b' \
node_modules/@cocos/creator-types/engine/cc.d.ts
把这件事变成习惯,你就不需要背 API 了。下面这段 L2 片段就是一个「会查」的例子——它列出一个节点上挂的所有组件,用的全是核对过的符号:
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('NodeInspector')
export class NodeInspector extends Component {
@property(Node)
target: Node | null = null;
dump(): string[] {
if (!this.target) return [];
return this.target.components.map((c) => c.constructor.name);
}
}
node.components 返回 ReadonlyArray<Component>,c.constructor.name 就是脚本类名。这个小工具在排查「这个节点到底挂了什么」时非常有用——比在编辑器里一个个点开快得多。
「会查」比「会背」更可靠,因为引擎版本会变,你的记忆不会自动更新,但 cc.d.ts 永远是对的。
18.3.7 常见误区澄清
收尾时,把几个容易走偏的认识澄清一下:
| 误区 | 事实 |
|---|---|
| 「Cocos 代码不能类型检查」 | 用 @cocos/creator-types 就能对真实引擎声明做 tsc --noEmit |
| 「热更能改一切」 | 只能改资源与数据,脚本逻辑改不了(17.2.1) |
| 「灰度是大厂才做的」 | 小游戏发布是热的,几千用户也需要灰度 |
| 「首包大点没关系」 | 微信小游戏首包直接决定首次打开率 |
| 「工具链越全越好」 | 卷二只用 TS + vitest + 必要的构建,没有引入多余依赖 |
「.meta 是自动生成的,不用管」 | 它是资源身份,丢了引用就断(18.2.2) |
| 「切场景会帮我释放资源」 | 常驻节点与缓存不会自动放(18.2.4、18.2.5) |
| 「判平台用字符串更快」 | 枚举成员才对,字符串比较是头号 bug 源(18.2.1) |
| 「拖拽就能做完游戏」 | 生命周期、资源、平台差异都在代码层 |
最后一条尤其想说:本套书从头到尾没有引入第三方游戏库、状态管理框架、UI 框架。Cocos 工程最忌讳「为了优雅引入一堆依赖」,因为每一个依赖都会进包体、都要跨端适配。能用一个引擎自带的组件解决,就别拉一个库。
还有一个常见误区是「用 Cocos 就是拖拖拽拽,不写代码」。事实上,本卷 18 章里,几乎每一章的难点都在脚本层——编辑器能帮你摆节点,但生命周期、资源释放、平台差异这些事,编辑器替不了你。Cocos Creator 是「编辑器 + 代码」的组合,不是无代码工具。
18.3.8 继续做《方块冲刺》的三条路
如果你想把这款游戏继续做下去,三条路按投入从小到大:
- 打磨现有玩法:用 17.3 的关卡漏斗找到卡点,调
configs/levels.json的难度曲线,加几种道具。改动全在数据层,能热更,风险最低。 - 加一层元游戏:成就、每日任务、皮肤解锁。这需要新的 UI 与存档结构,但不用动核心循环。
- 做联机对战:把单机跑酷改成双人竞速。这是从单机到联机的跃迁,涉及房间、同步、断线重连——正好是《Cocos 游戏联机实战》的主题。
无论走哪条路,第 18.2 节那八个坑和 18.1 的发布清单都还能用。工程能力是可以复用的,具体玩法不能。
18.3.9 推荐的下一步学习路径
合上卷二后,如果你还想继续,按这个顺序最省力:
- 先把高级卷读一遍,重点看「为什么」,把引擎的内部机制补上。
- 动手改一遍
cc.d.ts实证:随便挑一个你用过的 API,去声明文件里看它的真实签名,你会发现不少「原来还有别的参数」。 - 给《方块冲刺》加一个小功能,全程走一遍「改数据 → 热更 → 看漏斗」的闭环。
- 读引擎的 TypeScript 源码(高级卷会给出路径),从
Component的生命周期入手。
第 2 条是最快见效的:查一次声明文件,比读十篇教程都实在。
展开说第 2 条。挑一个你天天用的 API,比如 instantiate,去 cc.d.ts 里搜它的声明,你会看到它其实有多个重载:有的带父节点参数,有的不带。很多人写了半年 instantiate(prefab) 再 parent.addChild,却不知道可以直接 instantiate(prefab, parent)。 这类「原来还能这样」的发现,只有查声明文件才做得到。
第 3 条的价值在于闭环。单独改数据、单独热更、单独看漏斗,你都做过;但把三者串成一次完整操作,你才会真正理解它们之间的依赖——比如改完数据忘了递增 manifest 版本,热更就不生效(这正是 17.2 反复强调的那条)。
第 4 条留到最后。读引擎源码是有门槛的,但一旦你能读 Component 的生命周期实现,很多「玄学问题」就变成了「代码问题」。高级卷会带你走这一步。
18.3.10 只做一件事的话
如果你时间有限,只能做一件事,那就做 18.3.9 的第 2 条——查一次声明文件。它不需要装任何东西,不需要跑构建,成本最低,收益却最直接。挑一个你写过很多遍的 API,看看它的完整签名,你多半会有新发现。
18.3.11 最后的检查清单
合上卷二之前,确认这几件事你已经带走:
- 能用
sys.Platform.X而不是字符串判断平台 - 知道
.meta必须提交、library//temp/该忽略 - 会用
tsc --noEmit对cc.d.ts校验自己的脚本 - 会查
cc.d.ts而不是背 API - 有一套三端构建与分包的实际配置
- 有一份可执行的发布检查清单
- 知道热更能改什么、不能改什么
- 有一份「数据化 / 可兜底 / 可单测」的工程直觉
- 遇到玄学问题时,知道先清缓存、再查
cc.d.ts
如果这七条你都打勾了,那你已经从「会用 Cocos」走到了「能交付 Cocos 项目」。高级卷会在此基础上,带你理解引擎为什么这么设计。
卷二到此收官。愿你做的下一款游戏,上线那天不用熬夜。
阅读导航:上一节:18.2 复盘:Cocos 工程里最容易踩的坑 · 下一节:回到目录 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。