《Cocos 游戏开发实战》1.1 需求、用例与验收标准

卷二不再自己造引擎,而是用 Cocos Creator 3.8 把《方块冲刺》做成能真发布的产品。本节锁定全书工程设定(工程名 block-dash、960×540、三端发布、bundle-levels 分包),给出功能与非功能需求、用例清单,把验收标准写成可测的纯 TypeScript 判定函数,并用 sys.Platform 落实平台降级矩阵。

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 出一个 HTMLH5 / 微信小游戏 / 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 MB13.2
NFR-06Draw Call单帧 ≤ 6013.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 分支,结果在真机上读不到存档。

平台层级存档实现分享广告
wechatwx.setStorageSync微信分享卡片激励视频
mobile-web / desktoplocalStorage复制链接无(或 H5 广告位)
nativesys.localStorage系统分享接入各渠道 SDK

1.1.9 里程碑与风险

18 章不是平铺的,按交付节点切成四个里程碑,每章末的「工程视角收口」都会告诉你处在哪一段:

里程碑覆盖章节完成标志
M1 工程就绪1–5空工程能跑通「启动 → 主菜单 → 空场景」
M2 玩法可玩6–12一局完整的跑酷可玩,含 UI、存档、SDK
M3 性能达标13–15NFR-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 工程结构与目录约定 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「cocos」更多文章

  1. 《Cocos 游戏联机实战》9.3 延迟补偿与体验权衡
  2. 《Cocos 游戏联机实战》9.2 网络状态 UI 与提示
  3. 《Cocos 游戏联机实战》9.1 同步表现与插值动画