1.1 需求、用例与验收标准
卷一《Cocos 游戏开发入门》做的是「从零手写一个 2D 运行时 mini2d,再拿 Cocos Creator 3.8 对照」。卷二反过来:我们不再自己造引擎,而是把《方块冲刺》用 Cocos Creator 3.8 做成一个能真发布到 H5、微信小游戏和原生端的完整产品。 主线从此变成 L2(import { X } from 'cc' 的组件脚本)与 L3(.scene / .prefab / .meta / settings/*.json)——因为真实的 Cocos 项目里,一半的复杂度在编辑器与工程配置里,而不在代码里。
与站内 game/client/ 那 116 篇的关系:那些文章讲的是 Godot、Phaser 这类引擎「某件事怎么做」;本卷讲的是 Cocos Creator 编辑器与工程体系(.meta、Bundle、常驻节点、构建面板),而且每段脚本都真实对过 cc.d.ts,能过 tsc --noEmit。
本节把《方块冲刺》的「产品规格」定下来:做成什么样、给谁玩、在哪些平台跑、什么叫做完了。后面 17 章都要逐条兑现这些结论——尤其 18.1 的发布检查清单,会直接引用本节这张验收表。规格一旦定错,后面每章都要还债。
1.1.1 从 demo 到产品,差的是哪几件事
卷一的《方块冲刺》是一个「能在浏览器里跑起来、能被单测断言」的 demo。卷二要把它变成一个「能上架、能更新、能扛住真机」的产品。这两者的差距不在玩法,而在下面这五件事上:
| 维度 | 卷一 demo | 卷二产品 |
|---|---|---|
| 资源 | 代码里 new Image() 或程序化绘制 | 编辑器导入、.meta 管理、Bundle 分包、CDN 远程 |
| 结构 | 运行时 addChild 拼节点树 | .scene / .prefab 在编辑器里搭好并序列化 |
| 输入 | 自己的指针事件队列 | Node 事件系统 + 多端触控/键盘 |
| 发布 | vite build 出一个 HTML | H5 / 微信小游戏 / Android / iOS 三端构建 |
| 质量 | vitest 单测 + Playwright 截图 | 加埋点、崩溃上报、灰度、热更 |
所以卷二不重复卷一的「从零实现」叙事。需要复用卷一的纯逻辑时(比如那个固定步长累积器),一句话交代即可:「卷一 4.2 那个累积器,这里直接用引擎的 update(dt) 替代。」
1.1.2 工程设定(全书锁定,不得前后矛盾)
这本书所有章节共享同一套工程设定。它们是后面每一节示例的前提,任何一处写歪都会让读者照着做时对不上:
| 项 | 取值 |
|---|---|
| 引擎版本 | Cocos Creator 3.8.8(全书锁定) |
| 工程名 / 包名 | block-dash / com.example.blockdash |
| 设计分辨率 | 960 × 540,fitHeight,适配策略 SHOW_ALL |
| 目标平台 | H5、微信小游戏、Android / iOS 原生 |
| 资源目录 | assets/ 下 scenes/ prefabs/ textures/ audio/ configs/ scripts/ |
| 脚本目录 | assets/scripts/ 下 core/(纯逻辑)game/(玩法)ui/ platform/ |
| 核心玩法 | 横版自动跑 + 点击/空格跳跃 + 金币收集 + 三种道具(加速/无敌/磁铁) |
| 关卡数据 | configs/levels.json,含 obstacles / coins / goalX / speed |
| 存档键 | 微信端 wx.setStorageSync,H5 端 localStorage,原生端 sys.localStorage |
| 分包 | 首包只含启动场景与主 UI,关卡资源进 bundle-levels 分包 |
注意 fitHeight 与 SHOW_ALL 的组合:在 960×540 的设计分辨率下,横屏手机通常比 16:9 更宽,fitHeight 保证上下不被裁,SHOW_ALL 保证两侧要么留黑边、要么露出更多背景,而不会让画面被拉伸。这个取舍在 6.1 会展开。
1.1.3 术语表
本书会出现一批中英混用的词。先把口径统一,免得后面各章理解不一:
| 术语 | 含义 |
|---|---|
| 节点(Node) | 场景树上的一个对象,有变换、有父子关系,是引擎的基本单位 |
| 组件(Component) | 挂在节点上、带生命周期回调的脚本实例 |
| 预制体(Prefab) | 可复用的节点模板,运行时用 instantiate 复制 |
| 资源(Asset) | 由 assets/ 导入、带 .meta 的文件,如贴图、音频、JSON |
| Bundle | 一组资源的集合,可独立加载与分包 |
.meta | 每个资源旁的元数据文件,记录 uuid 与导入设置 |
| L1 / L2 / L3 | 本套书的三层可运行性口径(纯逻辑 / 引擎脚本 / 工程文件) |
1.1.4 功能需求
功能需求只列「必须做」的,每条都可被验收:
| 编号 | 需求 | 验收方式 |
|---|---|---|
| FR-01 | 角色在横版场景中自动向右跑,速度随关卡递增 | 观察 + 埋点 |
| FR-02 | 点击屏幕或按空格跳跃,二段跳上限 2 次 | 真机 + 用例 |
| FR-03 | 收集金币,分数实时更新 | 断言 score 变化 |
| FR-04 | 三种道具各生效 8 秒,同类道具刷新时长不叠加 | 计时用例 |
| FR-05 | 碰撞障碍后进入结算,可重开 | 用例 |
| FR-06 | 关卡进度、最高分、金币总额本地持久化 | 存档用例 |
| FR-07 | 支持中英文切换 | 多语言用例 |
| FR-08 | 微信端支持分享、激励视频复活 | 真机 |
需求编号后面会被反复引用:8.1 的关卡配置表对应 FR-01,10.3 的打击感对应 FR-02,11.1 的存档对应 FR-06。
1.1.5 非功能需求
功能需求决定「能不能玩」,非功能需求决定「玩得爽不爽、能不能上架」。它们同样是硬指标:
| 编号 | 需求 | 目标值 | 复测时机 |
|---|---|---|---|
| NFR-01 | 帧率 | 主流机 ≥ 60,低端机 ≥ 55 | 每章性能改动后 |
| NFR-02 | 首屏耗时 | 中端机 ≤ 1.2 秒 | 15.3 |
| NFR-03 | 主包体积 | ≤ 4 MB(微信红线) | 每次构建 |
| NFR-04 | 崩溃免疫率 | ≥ 99.5% | 上线后持续 |
| NFR-05 | 内存峰值 | ≤ 180 MB | 13.2 |
| NFR-06 | Draw Call | 单帧 ≤ 60 | 13.1 |
这些数字是预算不是目标口号:13 章会教你用 Profiler 把每一项测出来,15 章会把首包压到红线以下。NFR-01 与 NFR-06 是耦合的——合批做不好,Draw Call 一高,低端机帧率就掉,所以两条要一起看。
1.1.6 用例
需求写「做什么」,用例写「谁在什么情况下怎么做」。把主路径写清楚,才好在 16.2 做自动化冒烟:
| 用例 | 触发 | 主路径 | 失败表现 |
|---|---|---|---|
| UC-01 首次进入 | 冷启动 | 启动场景 → 主菜单 → 点「开始」→ 关卡 1 | 白屏、卡在加载 |
| UC-02 单局游玩 | 关卡内 | 跑 → 跳 → 吃金币 → 撞障碍 → 结算 | 掉帧、穿模 |
| UC-03 复活 | 结算页 | 看激励视频 → 原地复活 → 继续 | 视频未回调 |
| UC-04 断线重进 | 切后台再回 | 恢复存档 → 回到主菜单 | 存档丢失 |
| UC-05 换设备 | 新设备 | 登录 → 拉云存档 → 继续 | 覆盖本地存档 |
UC-04 是最容易被忽略的:微信小游戏切后台后,director 会暂停,回来后必须靠存档恢复而不是靠内存里的状态。
1.1.7 验收标准写成可测函数
「帧率不低于 55、首屏 1.2 秒内、首包 4 MB 内、崩溃率 0.5% 以下」——这类话如果只写在文档里,永远没人能判定通过与否。所以本节把验收标准写成一个纯 TypeScript 函数:它不碰 cc、不碰 DOM,可以在 Node 里直接被单测断言。这部分逻辑与引擎无关,可直接单测。
export interface AcceptanceSample {
readonly fps: number;
readonly firstPaintMs: number;
readonly firstPackageKb: number;
readonly crashFreeRate: number;
}
export interface AcceptanceResult {
readonly pass: boolean;
readonly failed: readonly string[];
}
export const ACCEPTANCE_BUDGET = {
fps: 55,
firstPaintMs: 1200,
firstPackageKb: 4096,
crashFreeRate: 0.995,
} as const;
export function evaluateAcceptance(sample: AcceptanceSample): AcceptanceResult {
const failed: string[] = [];
if (sample.fps < ACCEPTANCE_BUDGET.fps) failed.push('fps');
if (sample.firstPaintMs > ACCEPTANCE_BUDGET.firstPaintMs) failed.push('firstPaint');
if (sample.firstPackageKb > ACCEPTANCE_BUDGET.firstPackageKb) failed.push('package');
if (sample.crashFreeRate < ACCEPTANCE_BUDGET.crashFreeRate) failed.push('crashFree');
return { pass: failed.length === 0, failed };
}
这份「预算表」不是拍脑袋来的:55 帧是低端机可玩的底线,1200 毫秒首屏是小游戏渠道的容忍上限,4 MB 是微信小游戏主包的传统红线,0.995 的崩溃免疫率对应「千次启动最多 5 次崩溃」。数字可调,但判定逻辑必须是可执行的——16.2 的冒烟脚本会把采样结果喂给 evaluateAcceptance。
1.1.8 平台矩阵与降级策略
三端不是「同一份代码原样跑」,而是「同一份玩法、不同的能力探测」。下面这个组件在启动时判断当前跑在哪一层平台,据此决定后面用哪套存档与 SDK:
import { _decorator, Component, sys } from 'cc';
const { ccclass } = _decorator;
type PlatformTier = 'wechat' | 'mobile-web' | 'desktop' | 'native' | 'unknown';
@ccclass('PlatformProbe')
export class PlatformProbe extends Component {
resolveTier(): PlatformTier {
const p = sys.platform;
if (p === sys.Platform.WECHAT_GAME) return 'wechat';
if (p === sys.Platform.MOBILE_BROWSER) return 'mobile-web';
if (p === sys.Platform.DESKTOP_BROWSER) return 'desktop';
if (p === sys.Platform.ANDROID || p === sys.Platform.IOS) return 'native';
return 'unknown';
}
onLoad(): void {
console.log('[block-dash] tier =', this.resolveTier(), 'native =', sys.isNative);
}
}
sys.platform 是一个字符串枚举,成员名必须逐字写对——WECHAT_GAME、MOBILE_BROWSER、DESKTOP_BROWSER、ANDROID、IOS 都在 cc.d.ts 的 _pal_system_info_enum_type_platform__Platform 里。判错平台是卷二最常见的一类 bug:比如把微信小游戏当成 MOBILE_BROWSER,于是走了 localStorage 分支,结果在真机上读不到存档。
| 平台层级 | 存档实现 | 分享 | 广告 |
|---|---|---|---|
wechat | wx.setStorageSync | 微信分享卡片 | 激励视频 |
mobile-web / desktop | localStorage | 复制链接 | 无(或 H5 广告位) |
native | sys.localStorage | 系统分享 | 接入各渠道 SDK |
1.1.9 里程碑与风险
18 章不是平铺的,按交付节点切成四个里程碑,每章末的「工程视角收口」都会告诉你处在哪一段:
| 里程碑 | 覆盖章节 | 完成标志 |
|---|---|---|
| M1 工程就绪 | 1–5 | 空工程能跑通「启动 → 主菜单 → 空场景」 |
| M2 玩法可玩 | 6–12 | 一局完整的跑酷可玩,含 UI、存档、SDK |
| M3 性能达标 | 13–15 | NFR-01 / 03 / 06 全部进预算 |
| M4 可交付 | 16–18 | 冒烟通过、埋点上报、可灰度 |
风险清单只列真实存在的三条:一是编辑器版本漂移(同事用 3.7 打开 3.8 工程,.scene 会提示版本不一致),对策是 package.json 里锁 creator.version;二是平台 API 分叉(wx.* 与浏览器 API 不通用),对策是 1.1.8 那层平台探测;三是首包超红线(美术资源直接拖进 assets/ 就进主包),对策是 4.1 起的 Bundle 分包纪律。
这三条风险都不是「万一」,而是「必然」:团队里只要超过一个人,编辑器版本就会漂;只要做多端,API 就会分叉;只要美术把图丢进 assets/,主包就会涨。把它们写在第一章,是为了让后面每一章的选择都有依据——而不是等到上线前一天才发现主包 8 MB。
1.1.10 本节自检清单
规格这类东西,最怕「看着写全了」。逐条打勾再往下走:
- 工程名是
block-dash,包名是com.example.blockdash - 设计分辨率写成
960 × 540,fitHeight+SHOW_ALL - 三端目标明确:H5、微信小游戏、Android / iOS
- 资源目录六层、脚本目录四层,与 1.2 完全一致
- 存档键三端口径写死:
wx.setStorageSync/localStorage/sys.localStorage - 分包名是
bundle-levels,首包只含启动场景与主 UI -
evaluateAcceptance能在 Node 里跑,无cc依赖 - 平台探测只用
sys.Platform里真实存在的成员名
第 4 条与第 5 条是跨章约束:1.2 会逐字重复这套目录,4.1 会重复分包名。一旦某处写歪,读者照着做就会在构建时踩到「找不到 Bundle」或「资源没进主包」。
1.1.11 工程视角的收口
到这一节结束,工程里还没有一行游戏代码,但磁盘上应该已经有一个由 Cocos Dashboard 创建的空项目。下面这个文件是 Cocos 项目根目录的 package.json,它标记了工程名与创建它的编辑器版本。
以下片段需 Cocos Creator 3.8 编辑器打开,本节不可命令行复现:
{
"name": "block-dash",
"uuid": "6f2a1c8e-7b4d-4c1a-9f30-2d5e8a0b1c44",
"creator": {
"version": "3.8.8"
},
"dependencies": {}
}
在编辑器里打开这个工程,你会看到:资源管理器左侧是空的 assets/;底部控制台输出 Cocos Creator v3.8.8;构建面板的「平台」下拉里能选到 Web Mobile、微信小游戏、Android、iOS。uuid 是工程的唯一标识,不要手改——它一变,编辑器会当成另一个项目。
本节交付的是规格而非代码:一张锁定参数的工程设定表、一份术语表、一份功能与一份非功能需求清单、一份用例表、一个可测的验收函数、一张平台降级矩阵、一段里程碑与风险。下一节我们把这些规格落到磁盘结构上——assets/ 怎么分层、scripts/ 怎么分职责、settings/v2/packages/ 里到底存了什么,以及为什么 library/ 永远不该进版本库。
阅读导航:上一节:目录 · 下一节:1.2 工程结构与目录约定 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。