简介:这份资源是面向Cocos Creator初学者与独立开发者的猜成语游戏完整工程,基于Cocos Creator 2.3.3构建,适合想通过实战项目掌握2D游戏开发流程、UI搭建与数据驱动逻辑的读者。压缩包共2765个文件,约18.74MB,包含941个json数据文件、634个meta配置、611个png图片、256个js脚本、139个prefab预制体,以及mp3音效、effect特效、plist图集等,覆盖成语数据库、排行榜、界面资源与游戏逻辑等模块。已有1239人学习下载。工程内可参考成语数据的JSON组织方式、随机抽题与答案判断逻辑、排行榜本地存储实现,以及音效动画的接入方法,目录结构完整,便于对照搭建自己的猜成语项目或二次开发。
1. 猜成语游戏为什么适合用 Cocos Creator 做第一个完整项目
猜成语这个品类看起来简单:一张图、四个选项、一个关卡列表。但真动手做,你会发现它把游戏开发里最核心的几块都串了一遍——资源加载、UI 布局、状态管理、数据持久化、打包发布。对想入门 Cocos Creator 的人来说,它比做飞机大战更能练到工程能力,因为飞机大战可以靠一个脚本硬撑,猜成语不行,它有明确的关卡数据和进度系统。
Cocos Creator 在这个场景下的优势很直接:TypeScript 写逻辑,编辑器里拖 UI,一套代码同时出微信小程序游戏和 APK。热搜里常出现的「cocos creator 打包 apk」「微信小程序游戏开发」这两个需求,猜成语项目都能覆盖。它不需要 3D、不需要物理引擎,对性能要求低,反而逼你把 UI 层级、资源引用、事件解绑这些基础打牢。
这篇文章面向的是写过一点 JS/TS、但没完整交付过一款游戏的人。我会按「数据结构 → 核心玩法 → 关卡与进度 → 打包与优化」的顺序讲,每个环节都给能直接抄的代码和参数。读完你应该能自己跑出一个可玩、可打包、可扩展题库的猜成语 Demo。
2. 猜成语的数据结构与 Cocos Creator 场景搭建
2.1 题库用 JSON 还是 TS 常量:先定数据格式
猜成语的核心数据就三样:题目图片、正确答案、干扰选项。常见做法是把它做成一个 JSON 数组,放在 resources/config/ 下,用 resources.load 动态读。这样改题库不用重新编译逻辑代码,运营同学也能改。
// resources/config/idioms.json
{
"levels": [
{
"id": 1,
"image": "imgs/level_001", // 相对 resources 的路径,不带扩展名
"answer": "画蛇添足",
"options": ["画蛇添足", "画龙点睛", "杯弓蛇影", "亡羊补牢"],
"hint": "多此一举"
},
{
"id": 2,
"image": "imgs/level_002",
"answer": "守株待兔",
"options": ["守株待兔", "刻舟求剑", "掩耳盗铃", "拔苗助长"],
"hint": "死等运气"
}
]
}
options 里必须包含 answer ,顺序在运行时打乱。 image 不带扩展名是 Cocos 的约定, resources.load 会自动匹配 .png 或 .jpg 。 hint 字段留给提示按钮用,可以消耗金币或看广告解锁。
注意:JSON 里的中文在打包成微信小程序时不会乱码,但如果用 Excel 转 JSON,务必存成 UTF-8 无 BOM,否则首字符会带
\ufeff,导致answer比对失败。
2.2 场景节点树怎么摆才不返工
在 Cocos Creator 里新建一个 Game 场景,节点树建议这样组织:
Canvas
├── BgLayer (Sprite,纯色或渐变背景)
├── GameLayer
│ ├── LevelLabel (Label,显示"第 1 关")
│ ├── IdiomImage (Sprite,题目图)
│ ├── OptionGrid (Layout,Grid 模式,2x2)
│ │ ├── OptionBtn_0 (Button + Label)
│ │ ├── OptionBtn_1
│ │ ├── OptionBtn_2
│ │ └── OptionBtn_3
│ └── HintBtn (Button)
└── PopupLayer (空节点,弹窗挂这里)
OptionGrid 用 Layout 组件, Type 选 GRID , Resize Mode 选 CONTAINER , Start Axis 选 HORIZONTAL ,这样四个选项自动排成两行两列。每个 OptionBtn 的 Label 节点要设 Overflow 为 SHRINK ,防止四字成语在窄屏被截断。
2.3 用 resources.load 动态加载题目图
题目图不要全部拖进场景,那样包体会爆炸。用 resources.load 按需加载,配合 assetManager 的缓存机制。
// scripts/LevelLoader.ts
import { _decorator, Component, resources, SpriteFrame, Sprite } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('LevelLoader')
export class LevelLoader extends Component {
@property(Sprite)
idiomSprite: Sprite = null!;
// 加载指定关卡的图片并赋值给 Sprite
loadLevelImage(imagePath: string) {
resources.load(imagePath + '/spriteFrame', SpriteFrame, (err, frame) => {
if (err) {
console.error('图片加载失败:', imagePath, err);
return;
}
this.idiomSprite.spriteFrame = frame;
});
}
}
resources.load 的第一个参数是相对 resources 目录的路径,第二个是资源类型。图片资源要取 SpriteFrame 子资源,所以路径后面拼 /spriteFrame 。加载失败时不要静默,打印路径方便排查——最常见的原因是图片没放在 resources 目录下,或者路径大小写不一致。
3. 猜成语核心玩法:选项打乱、判定与事件解绑
3.1 选项打乱用 Fisher-Yates,别用 sort 随机
很多人用 options.sort(() => Math.random() - 0.5) 打乱选项,这在 V8 引擎里分布不均匀,某些选项会长期排在前面。正确做法是 Fisher-Yates:
// utils/shuffle.ts
export function shuffle<T>(arr: T[]): T[] {
const a = arr.slice(); // 不改原数组
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
slice() 保证原数组不被污染,因为题库数据是全局缓存的,改一次后面全乱。 j 的范围是 [0, i] ,包含 i 本身,这样每个位置被选中的概率才相等。
3.2 按钮点击判定与防连点
四个选项按钮共用一个回调,靠 index 区分。判定逻辑要处理两件事:答对进下一关,答错给反馈但不扣命(猜成语一般不做失败惩罚,做广告变现的除外)。
// scripts/GameController.ts
import { _decorator, Component, Label, Node } from 'cc';
import { shuffle } from '../utils/shuffle';
const { ccclass, property } = _decorator;
@ccclass('GameController')
export class GameController extends Component {
@property([Node])
optionNodes: Node[] = [];
@property(Label)
levelLabel: Label = null!;
private currentAnswer: string = '';
private locked: boolean = false; // 防连点锁
onOptionClick(event: Event, customIndex: string) {
if (this.locked) return;
this.locked = true;
const idx = parseInt(customIndex);
const label = this.optionNodes[idx].getComponentInChildren(Label)!;
if (label.string === this.currentAnswer) {
console.log('答对');
this.scheduleOnce(() => this.nextLevel(), 0.4);
} else {
console.log('答错');
this.locked = false; // 答错解锁,允许重选
}
}
}
locked 标志位是关键。答对后要等 0.4 秒再切关,这期间如果玩家连点,会触发多次 nextLevel ,导致跳关。答错则立即解锁,因为猜成语允许反复试。 scheduleOnce 比 setTimeout 安全,组件销毁时会自动清理。
3.3 事件监听必须在 onDestroy 里解绑
Cocos Creator 的按钮事件如果在编辑器里绑定,组件销毁时引擎会自动处理。但如果你用 node.on('click', ...) 动态注册,就必须手动 off ,否则切场景后旧节点还在响应,出现「点了没反应但日志在跑」的怪象。
onEnable() {
this.node.on('level-complete', this.onLevelComplete, this);
}
onDisable() {
this.node.off('level-complete', this.onLevelComplete, this);
}
用 onEnable / onDisable 配对而不是 onLoad / onDestroy ,是因为节点可能被反复启用禁用(比如弹窗复用)。 off 的第三个参数 this 必须和 on 时一致,否则解绑不掉。
提示:热搜里提到的「游戏开发 事件锁」本质就是这类状态标志位加事件生命周期管理。猜成语项目里,
locked是逻辑锁,on/off是事件锁,两者缺一不可。
4. 关卡进度、本地存储与微信小程序适配
4.1 用 sys.localStorage 存进度,键名带版本号
进度存储最简单的是 sys.localStorage ,微信小程序和原生平台都支持。键名建议带版本前缀,方便以后改数据结构时做迁移。
// utils/SaveManager.ts
import { sys } from 'cc';
const KEY = 'idiom_save_v1';
export interface SaveData {
maxLevel: number; // 已解锁到第几关
coins: number; // 金币数
}
export function loadSave(): SaveData {
const raw = sys.localStorage.getItem(KEY);
if (!raw) return { maxLevel: 1, coins: 0 };
try {
return JSON.parse(raw) as SaveData;
} catch {
return { maxLevel: 1, coins: 0 };
}
}
export function writeSave(data: SaveData) {
sys.localStorage.setItem(KEY, JSON.stringify(data));
}
try/catch 不能省。玩家手动改过存档、或者旧版本数据结构不兼容时, JSON.parse 会抛异常,不捕获的话整个游戏卡死。 KEY 里的 v1 是给未来留的退路,改结构时换成 v2 ,读不到就回默认值。
4.2 关卡列表用 ScrollView + 动态实例化
关卡选择界面不要手动摆 100 个按钮。用 ScrollView 加 Layout ,运行时根据 maxLevel 实例化。
// scripts/LevelSelect.ts
import { _decorator, Component, Prefab, instantiate, Node } from 'cc';
import { loadSave } from '../utils/SaveManager';
const { ccclass, property } = _decorator;
@ccclass('LevelSelect')
export class LevelSelect extends Component {
@property(Prefab)
levelBtnPrefab: Prefab = null!;
@property(Node)
content: Node = null!;
start() {
const save = loadSave();
for (let i = 1; i <= save.maxLevel; i++) {
const btn = instantiate(this.levelBtnPrefab);
btn.getComponentInChildren(Label)!.string = `${i}`;
this.content.addChild(btn);
}
}
}
只实例化到 maxLevel ,后面的关卡不生成,既省性能又防止玩家点到未解锁关卡。 content 节点要挂 Layout 组件, Type 选 GRID , Resize Mode 选 CONTAINER ,这样按钮多了会自动撑高, ScrollView 才能滚动。
4.3 微信小程序下的资源路径与分包
打包微信小程序时, resources 目录下的所有资源会进主包,主包限制 4MB。猜成语的图片如果超过这个数,必须做分包。常见做法是把题目图放 resources/subpackages/ 下,在 game.json 里配置 subpackages 。
// 微信小游戏 game.json 片段
{
"subpackages": [
{
"name": "levels",
"root": "resources/subpackages/levels/"
}
]
}
分包加载要用 assetManager.loadBundle ,不能再用 resources.load 。加载时机放在进入关卡前,加个 loading 转圈。主包只留 UI 图和前 10 关的图,后面的按需下载。
注意:Cocos Creator 打包微信小程序时,
resources目录名会被保留,但分包根路径要写相对assets的路径,写错会报bundle not found。
5. 打包 APK 与性能优化的几个实操参数
5.1 构建 APK 前必须改的 3 个参数
在 Cocos Creator 的「构建发布」面板选 Android,有几个参数直接决定能不能装、跑不跑得动。
| 参数 | 推荐值 | 原因 |
|---|---|---|
| Package Name | com.yourname.idiom | 不能带中文和大写,否则打包失败 |
| Target API Level | 33 及以上 | 低版本在 Android 13+ 上装不上 |
| Render Backend | GLES2 | 猜成语是 2D,GLES2 兼容性最好,包体也小 |
| Orientation | Portrait | 竖屏,猜成语都是竖屏玩 |
Package Name 一旦上架就不能改,第一次就要定好。 Render Backend 选 GLES2 而不是 GLES3,是因为 2D 游戏用不到 GLES3 的特性,GLES2 在老机型上崩溃率更低。
5.2 图片压缩与图集:把包体从 20MB 压到 5MB
猜成语的题目图是包体大头。常见做法是:所有图先压成 WebP 或高质量 JPG,尺寸控制在 720x720 以内,然后用 TexturePacker 或 Cocos 自带的自动图集打成一张大图。
# 用 cwebp 批量压缩,质量 80
for f in imgs/*.png; do
cwebp -q 80 "$f" -o "${f%.png}.webp"
done
-q 80 是质量和体积的平衡点,再低会有明显块状。压完在 Cocos 里把图片的 Type 设为 sprite-frame , Packable 勾上,构建时会自动打进图集。图集能把几十次 draw call 降到几次,低端机上帧率提升明显。
5.3 用 profiler 定位卡顿:先看 draw call 再看 GC
Cocos Creator 自带的 profiler 在「开发者」→「显示 FPS」里打开。猜成语卡顿通常两个原因:draw call 过高,或者每帧 new 对象触发 GC。
draw call 高就查图集有没有生效, OptionGrid 里的四个按钮如果用了不同图集的图,就会各占一次 draw call。GC 问题多半出在 update 里拼字符串或 JSON.parse ,把这类操作挪到事件回调里,别放帧循环。
// 反例:每帧拼字符串,触发 GC
update() {
this.label.string = '第 ' + this.level + ' 关';
}
// 正例:只在关卡变化时更新
onLevelChanged(level: number) {
this.label.string = `第 ${level} 关`;
}
update 每帧执行,里面任何字符串拼接、数组创建都会产生垃圾对象。猜成语不需要每帧刷新 UI,所有更新都应由事件驱动。
5.4 一个容易忽略的坑:Label 的 Cache Mode
Cocos Creator 的 Label 组件有个 Cache Mode 属性,默认是 NONE ,每次改文字都会重新生成纹理。猜成语的选项文字频繁变化,建议设为 BITMAP ,用位图字体缓存,改文字时只换 UV 不重建纹理。
在编辑器里选中 Label , Cache Mode 选 BITMAP , Font 选一个系统字体或自定义 BMFont。代价是文字缩放会有点糊,但猜成语的字号固定,影响不大。这个改动在低端安卓机上能省 10% 左右的 CPU。
最后一章收在一个具体技巧上:如果你想让题库支持热更新,把 idioms.json 放到远程 CDN,用 assetManager.loadRemote 加载,本地只留一份兜底。加载前先比对版本号,版本没变就用缓存。这样加新关卡不用重新发版,对猜成语这种靠内容量撑留存的产品来说,是性价比最高的一步。
274



被折叠的 条评论
为什么被折叠?



