18.3 回顾与路线:进入实战卷
这是卷一《Cocos 游戏开发入门》的最后一节。回头看,你从第 1 章的一行 requestAnimationFrame 出发,亲手写出了 mini2d——一个纯 TypeScript、零 cc 依赖、能跑能测的 2D 运行时,并用它把《方块冲刺》做成了一个可发布的游戏。这一节做三件事:回顾演进路径、总结核心收获、给出下一步的方向。
卷一到此结束,但《方块冲刺》还没做完。这一节也是通往卷二《Cocos 游戏开发实战》的桥——把同一款游戏,用 Cocos Creator 做成一个真正可发布的产品。
18.3.1 mini2d 的 18 章演进路径
把 18 章的主线收成一张表,左边是章节,右边是它落成的模块:
| 阶段 | 章节 | 主题 | 落成的模块 |
|---|---|---|---|
| 起步 | 1 | 环境、心智模型、第一个循环 | core/loop.ts |
| 渲染 | 2–3 | 精灵、相机、场景图、批处理 | render/* |
| 时间 | 4 | deltaTime、固定步长、时间缩放 | core/loop.ts |
| 输入 | 5 | 坐标换算、手势、输入缓冲 | input/* |
| 组件 | 6 | 组件模型、生命周期、序列化 | core/component.ts |
| 物理 | 7 | 碰撞检测、宽相、碰撞响应 | physics/* |
| 动画 | 8 | 帧动画、补间、属性动画 | anim/* |
| 音频 | 9 | 音效池、BGM 淡入淡出、移动端解锁 | audio/* |
| 资源 | 10 | 资源清单、异步加载、引用计数 | assets/* |
| UI | 11 | 布局、虚拟列表、弹窗栈 | ui/* |
| 状态 | 12 | 状态机、存档迁移、云存档冲突 | save/* |
| AI 与关卡 | 13 | 行为树、A* 寻路、程序化生成 | game/level.ts |
| 玩法 | 14 | 结算管线、效果系统、掉落表 | game/* |
| 调试 | 15 | 调试绘制、性能剖析、开发者控制台 | tools/* |
| 测试 | 16 | 纯逻辑单测、渲染 E2E、确定性回放 | test/* |
| 交付 | 17–18 | 构建打包、多端发布、综合对照 | build/* |
每一行都对应一个真实存在的文件——这不是一份知识清单,而是一个能跑起来的工程。这正是本套书和一般教程最大的区别:你没有「学过」这些概念,你是「写出来」的。
换句话说,如果哪天你把这个工程删掉,你丢的不是「一些代码」,而是一整套「从零实现 2D 游戏」的肌肉记忆。这份记忆会在你之后用任何引擎时持续兑现。
18.3.2 18 章的模块如何拧成一个引擎
把上面这些模块组装起来,mini2d 的入口就是这么一小段代码——18 章的工作最终收敛成十几个对象:
// mini2d/src/index.ts(节选)
import { Loop } from './core/loop';
import { Viewport } from './render/viewport';
import { Renderer } from './render/renderer';
import { PhysicsSystem } from './physics/system';
import { AudioBus } from './audio/bus';
import { Loader } from './assets/loader';
import { SaveCodec } from './save/codec';
export function createGame(canvas: HTMLCanvasElement): Game {
const viewport = new Viewport(canvas);
const renderer = new Renderer(canvas.getContext('2d')!);
const physics = new PhysicsSystem({ step: 1 / 60 });
const audio = new AudioBus();
const loader = new Loader();
const save = new SaveCodec(3); // 存档版本号 3
const loop = new Loop(physics.step, renderer.render);
return { loop, viewport, renderer, physics, audio, loader, save };
}
在 Cocos 里,等价的「组装」不在代码里,而在编辑器里:场景文件描述节点树,组件脚本挂在节点上,director.loadScene 负责把整个场景加载并跑起来:
import { director } from 'cc';
// 等价于 createGame + loop.start:加载场景,引擎接管循环
director.loadScene('main');
一行 loadScene 顶掉了我们 createGame 里的大部分手工接线——这就是「引擎替你做的」在入口层的体现。
但请注意:loadScene 之所以能这么短,是因为它背后的活(资源加载、节点实例化、组件注册、循环接入)全被引擎包进了内部。你在卷一写的那些代码并没有消失,只是从「你写的」变成了「引擎写的」。 理解这一点,你就理解了「用引擎」和「懂引擎」的区别。
18.3.3 核心收获一:引擎不再是黑盒
卷一最大的收获,是让你不再把引擎当黑盒。当你亲手写过场景图的脏标记(第 3 章),你就知道 node.worldPosition 为什么是只读的;当你亲手写过批处理(第 3 章),你就知道为什么「打乱绘制顺序」会让 Draw Call 翻倍;当你亲手写过固定步长(第 4 章),你就知道为什么引擎的 dt 不能直接喂给物理。
这种「知道」的价值在于排查问题。别人遇到「角色卡顿」只能猜,你能立刻定位到是「渲染插值与固定步长没对齐」还是「宽相没做导致碰撞 O(n²)」。会用引擎的人和懂引擎的人,在出问题时的差距是指数级的。
举个具体的:画面偶尔「闪一下」或「跳一下」,多半不是渲染 bug,而是第 4 章讲的那个坑——某帧的 dt 因为切标签页变得极大,物理一次推进了好几步。知道这一层,你就会去查帧间隔而不是去翻渲染代码;不知道,就只能在渲染里瞎找。
18.3.4 核心收获二:分层——纯逻辑与外壳
第二个收获是分层。18.2 节反复强调的那条线——纯逻辑与外壳——是整卷最重要的架构决策:
- 纯逻辑只吃数字、只吐数字,不 import 任何引擎符号,能独立跑单测。
- 外壳负责把引擎的数据喂给纯逻辑,再把纯逻辑的结果写回引擎。
这个分层让你在切换引擎时几乎零成本:逻辑不动,只换外壳。它也让测试变得可能——第 16 章的确定性回放之所以能跑,就是因为核心玩法不依赖浏览器环境。
// logic/ 目录的纪律:只吃数字、只吐数字
import { stepRun, type RunState } from './run';
// 这个函数不 import 'cc'、不碰 ctx、不读 performance.now()
// 所以它能被 vitest 直接单测,也能在 mini2d 与 Cocos 里共用同一份
export function simulate(state: RunState, inputs: number[], dt: number): RunState {
return inputs.reduce((s, input) => stepRun(s, input, dt), state);
}
「不 import 引擎符号」这条纪律看似苛刻,但它是一道最省事的防火墙:只要一个文件能脱离引擎编译,它就不会被引擎的细节污染。
18.3.5 核心收获三:可测试性来自设计,不是来自工具
第三个收获是:可测试性是设计出来的,不是测出来的。第 16 章能用 vitest 给纯逻辑写单测、用 Playwright 做渲染截图、用固定种子做确定性回放,前提是前面 15 章就一直在「为可测而设计」:逻辑与渲染分离、时间可注入(假时钟)、随机数可播种。
如果你写代码时把 Math.random() 和 Date.now() 直接撒在逻辑里,那么再好的测试框架也救不了你。先有可测的结构,再有测试的工具——顺序不能反。
18.3.6 核心收获四:确定性是可以设计的
第四个收获是确定性。多人联机、录像回放、反作弊、可复现 bug——这些看似高级的需求,底层都指向同一件事:相同输入 + 相同初始状态 = 相同结果。要做到这一点,你得在三个地方下功夫:
- 固定步长:物理只在固定
dt下推进(第 4 章)。 - 可播种随机:所有随机都来自一个可复现的 PRNG(第 13 章的关卡生成)。
- 无隐藏状态:逻辑不读系统时钟、不读帧率,只读传入的参数。
这三条在卷一就埋下了伏笔,卷四《Cocos 游戏联机实战》会把它用满。其中「可播种随机」最容易被忽视,却最影响可复现性——一个最小实现长这样:
// 可播种伪随机:同样的种子,永远给出同样的序列
export class Rng {
private s: number;
constructor(seed: number) {
this.s = (seed >>> 0) || 1;
}
next(): number {
// xorshift32:够快,且跨平台结果一致
let x = this.s;
x ^= x << 13; x >>>= 0;
x ^= x >>> 17;
x ^= x << 5; x >>>= 0;
this.s = x;
return x / 0xffffffff;
}
}
把它接进关卡生成与掉落表(第 13、14 章),你就得到了「同一个种子生成同一张关卡、同一份掉落」的能力——这是确定性回放与联机同步的地基。
18.3.7 核心收获五:数据驱动
第五个收获是数据驱动。第 6 章的序列化、第 12 章的存档、第 13 章的关卡数据、第 14 章的掉落表——它们共同构成一个思想:把「会变的数字」从代码里搬出去,交给配置。
代码里硬编码 if (level === 3) spawnRate = 0.5 的写法,改一次需求就要重新发版;把关卡参数放进 JSON,改数值不用碰代码。Cocos 把这条思路做到了极致——连「节点在场景里的位置」都是数据(.scene 文件),只有行为才留在代码里。
一个判断标准:如果改一个数值需要「重新编译」,那这个数值就该被搬进配置。 数值策划、关卡设计、掉落概率——它们都不该躺在 .ts 文件里。
18.3.8 继续深入的方向
卷一结束,但 Go——不,游戏开发——的世界还很大。几个值得继续钻的方向:
- Cocos Creator 工程体系:场景 / 预制体 / 组件化 / 编辑器工作流。这是卷二的全部内容。
- 多端发布与性能:H5、微信小游戏、原生的差异,包体优化、内存与帧率调优(第 17 章只开了个头)。
- 渲染进阶:合批原理、自定义材质、Shader 与渲染管线(属于卷三)。
- 联机与同步:帧同步、状态同步、网络抖动处理(属于卷四)。
- 工程化:CI、自动化测试、热更新、灰度发布。
- 数据与后端:账号、排行榜、支付、运营后台——这些在卷四会从「接口约定」的角度涉及。
想查 mini2d 的 API 速查,见本卷附录 A;想查 Cocos 工程结构与 cc.d.ts 速查,见附录 B;环境与离线复现见附录 C;常见错误见附录 D。
18.3.9 三个可以立刻动手的小练习
看书不等于会写。如果想让卷一的收获真正长在手上,建议做这三个小练习,每个都能在一两天内完成:
- 给《方块冲刺》加一个新道具:从掉落表(第 14 章)到效果系统(第 14 章)到表现(第 8 章),走一遍完整的数据驱动流程,只在配置里加一条、代码里加一个效果分支。
- 把某一关改成程序化生成:用第 13 章的种子随机生成关卡,跑第 16 章的确定性回放,验证「同一种子生成同一关卡」。
- 把纯逻辑抽出去跑单测:挑一个玩法模块,确保它不 import 任何引擎符号,然后给它写满 80% 覆盖率的 vitest 用例。
这三个练习的共同点是:它们都逼你回到「分层」这条主线上。做完之后,你对卷一的理解会从「读过」变成「用过」。
18.3.10 mini2d 的边界:它做不到什么
诚实地讲,mini2d 离一个商业引擎还很远,它在几个地方有明显的边界:
- 渲染后端单一:只有 Canvas2D,没有 WebGL / WebGPU,粒子与滤镜一多就掉帧。
- 没有可视化编辑器:场景靠代码拼,改一个坐标要改代码、重跑,效率远不如拖拽。
- 没有资源管线:资源靠手写清单,没有导入、压缩、平台适配。
- 没有跨端封装:原生与微信小游戏要自己接适配层。
- 没有成熟的工具链:性能剖析、热更新、构建面板都得自己造。
认识到这些边界,你才不会拿 mini2d 去做它做不了的事。卷一的目标从来不是造一个能用的引擎,而是造一个能让你「懂」引擎的引擎。 一旦这个目标达成,mini2d 的使命就完成了——接下来该用 Cocos Creator 这样的成熟引擎,去做真正的产品。
18.3.11 衔接卷二:把《方块冲刺》做成产品
卷一教你「引擎内部长什么样」,卷二教你「怎么用 Cocos Creator 把它做出来」。同一款《方块冲刺》,在卷二里会经历一次彻底的工程化:
- 用编辑器搭出真正的场景与预制体,而不是运行时拼节点;
- 用 Cocos 的组件体系重写外壳,逻辑层沿用卷一的纯逻辑;
- 接入真实的资源管线、UI 框架与多端构建;
- 做出一个能上线、能更新、能运营的可发布产品。
两卷的分工可以这样理解:
| 维度 | 卷一《入门》 | 卷二《实战》 |
|---|---|---|
| 引擎 | 自研 mini2d | Cocos Creator 3.8 |
| 场景 | 运行时拼节点 | 编辑器搭场景与预制体 |
| 重点 | 理解「引擎内部」 | 掌握「工程与工作流」 |
| 产出 | 可跑的运行时 + 游戏原型 | 可发布的产品 |
| 验证 | Node 单测 + Playwright | 编辑器 + 真机 |
卷一是「知其所以然」,卷二是「知其所以能」。 两者合起来,你才是一个完整的 Cocos 开发者——既不会被引擎的黑盒困住,也不会停在「造轮子」而不出成品。
18.3.12 写在最后
如果你一路跟着把 mini2d 写完了,那你手上已经有一个别人没有的东西:一个自己写的、能跑的 2D 运行时。它不完美,性能也远不如商业引擎,但它每一行你都懂。
这份「懂」是接下来一切的基础。当你用 Cocos 做项目时,你不会因为「引擎这样设计一定有道理」就放弃追问;当引擎出问题时,你能从原理层面推理而不是盲目试错;当需要自己造一个小工具时,你知道从哪里下手。
mini2d 到这里就完工了。接下来,带着它给你的理解,去把《方块冲刺》真正做成一个产品吧。
小结
mini2d的 18 章演进,是一个运行时从零到可发布的完整路径,每个模块都对应真实文件。- 引擎不是黑盒:写过底层,才能在看问题时从原理推理而不是猜。
- 纯逻辑与外壳的分层,是切换引擎几乎零成本、测试能跑起来的前提。
- 可测试性来自设计;确定性靠固定步长、可播种随机与无隐藏状态。
- 数据驱动:把会变的数字搬出代码,Cocos 连场景位置都是数据。
- 下一步是卷二:用 Cocos Creator 3.8 把同一款《方块冲刺》做成可发布产品。
全卷到这里就结束了。你已经具备「从零理解一个 2D 运行时、并把它对照到 Cocos Creator」的完整能力——这是任何一本只讲 API 的教程给不了你的东西。卷二见。
阅读导航:上一节:18.2 迁移清单:mini2d → Cocos Creator · 下一节:回到目录 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。