大一那会儿,第一次在终端里敲出printf("hello world")然后看着屏幕亮起一行字,那种"我居然让计算机听话了"的兴奋感,估计每个写过代码的人都记得。后来我把这句话搬到 Cocos Creator 里,想看看引擎里是不是也能一行搞定时,才发现事情完全不是一个量级——Cocos Creator 的 Hello World,考验的不是你会不会写字,而是你能不能在"场景、节点、组件、脚本、构建"这条链路上,把每一个环节都接对。这篇内容就是把我从零做一个 Cocos Creator Hello World 的全过程拆开讲,包括版本怎么选、Hello World 有几种做法、脚本为什么挂了不报错、打包 APK 时到底踩了哪些坑。适合刚接触引擎的新手,也适合从其他语言转过来、想快速建立引擎心智模型的朋友。
1. 为什么引擎里的 Hello World 是完全另一回事
1.1 从一行 printf 到一帧渲染,中间隔了多少层
在 C 语言里,printf之所以能一行出结果,是因为编译器帮你把标准库、运行时、终端输出设备全接好了,你只需要关心字符串本身。Cocos Creator 不一样,它跑的是一个游戏循环:引擎每一帧都会去遍历场景里的节点树,检查每个节点上挂的组件,决定哪些需要重新计算变换、哪些需要重绘。你想在屏幕中央显示一个 "Hello World",实际发生的是这么一串动作:场景被加载 → 节点树被构建 → Label 组件被激活 → 字体资源被解析 → 字形被排版成顶点数据 → 渲染管线把这些顶点提交给 GPU → 屏幕刷新。
所以"Hello World"在这里真正的含义是:你成功让引擎从资源加载到渲染这条链路完整地跑通了一帧。这也是为什么新手最容易卡住的地方,往往不是代码写错,而是资源没挂上、场景没保存、脚本没绑定、构建参数没配。我见过太多人在编辑器里预览得好好的,一打包 APK 就黑屏,问题根本不在逻辑,而在链路中间断了某一环。
理解这一点之后,你看待 Hello World 的心态就会变——它不再是一个"验证语法"的小练习,而是一次全链路的最小闭环验证。把这条链路走通一次,后面做真正的项目时,你就知道每一环该去哪找问题。
1.2 这个项目到底适合谁上手
我在社区里看过很多提问,能明显分成几类人,他们做 Hello World 的目的其实完全不同。
第一类是零基础新手,可能连编程都没系统学过,冲着"做游戏"来的。对他们来说,Hello World 的意义是建立"我能操作这个工具"的信心,重点应该放在编辑器可视化操作上,先别碰复杂脚本。
第二类是有编程基础但没接触过游戏引擎的人,比如写过后端、写过前端、写过 C 语言课设。这类人容易犯的毛病是"想用代码解决一切",一上来就想用纯脚本动态创建所有东西,结果因为不熟悉引擎的生命周期和资源系统,反而绕远路。对他们来说,Hello World 的价值在于搞清声明式(编辑器拖拽)和命令式(代码创建)的关系。
第三类是从 Unity 或者 Cocos 2.x 转过来的人。他们概念都有,就是 API 和工具链变了。这类人需要的是版本差异对照表,而不是从零科普。
提示:先想清楚自己属于哪一类,再决定 Hello World 怎么练。用错方法不是浪费时间,是给自己建立错误的第一印象。
1.3 建立正确的引擎心智模型:场景树、节点、组件
在做任何操作之前,这个概念必须掰清楚,不然你会一直处于"照着教程点,但不知道为什么这么点"的状态。
Cocos Creator 用的是一个组件化实体系统。你可以这样类比:整个游戏是一个舞台(Scene,场景),舞台上有演员(Node,节点),演员身上可以贴标签、穿衣服、戴道具(Component,组件)。演员本身不会自己动,是身上的"行为组件"在驱使它动。一个节点可以挂多个组件,比如同时挂 Label(显示文字)和自定义脚本(控制逻辑)。
这个模型带来一个非常关键的推论:节点是容器,组件才是功能。很多新手会问"Label 节点怎么用代码改文字",其实 Label 是组件不是节点,你拿到的this.label是组件引用,改的是组件的string属性。搞混节点和组件,是后面所有坑的源头。
再补一个容易忽略的点:场景树是有层级的。父节点移动,所有子节点跟着移动,坐标系是相对的。这意味着你把 Label 放在 Canvas 下面和直接放在场景根下,表现会完全不同。Canvas 是 UI 渲染的容器,所有 UI 元素基本都应该放在它下面,否则可能出现"节点明明在场景里但屏幕上就是不显示"的情况。
2. 环境与版本:新手最容易折在这一步
2.1 版本怎么选,2.x 还是 3.x
这个问题我被问过无数次,直接给结论:新项目一律选 3.x 的稳定版,除非你有明确的存量项目维护需求。原因不复杂。
3.x 用了 TypeScript 作为主要脚本语言,装饰器写法更接近现代前端开发习惯,渲染管线重构过,对新平台的支持也更及时。2.x 大量项目用的是 JavaScript 的cc.Class写法,这套 API 在 3.x 里基本被替换掉了,两者代码不通用。如果你在网上搜教程,会看到大量 2.x 的内容,复制过来在 3.x 里报错,这是新手最大的困惑源之一。
| 对比项 | Cocos Creator 2.x | Cocos Creator 3.x |
|---|---|---|
| 主要脚本语言 | JavaScript(cc.Class) | TypeScript(装饰器) |
| 组件声明方式 | properties 字段 | @property 装饰器 |
| 命名空间 | cc.xxx | 从 'cc' 模块按需导入 |
| 渲染管线 | 旧管线 | 重构后的管线 |
| 新特性支持 | 逐步停止 | 持续推进 |
我个人的建议是:认准一个版本,配套教程也认准同一个大版本。跨版本抄代码比抄错代码更浪费时间,因为你连报错都看不懂。
2.2 安装与项目创建的实际步骤
现在 Cocos Creator 的安装走的是 Dashboard 路线,Dashboard 是个类似"版本管理器 + 项目启动器"的东西。步骤大致如下:
- 去官网下载 Dashboard 安装包,装完打开。
- 在 Dashboard 的"编辑器"页签里,下载你需要的引擎版本。这里注意,Dashboard 本身和编辑器版本是分开的,你可以同时装多个编辑器版本。
- 切到"项目"页签,点新建,选择模板。
- 填项目名、选存储路径,点创建,等编辑器启动。
第一次启动编辑器会比较慢,因为它要初始化项目缓存、编译引擎脚本。如果卡在"正在加载",先别急着强退,看下控制台有没有进度。真正卡死的概率不高,多数是磁盘慢。
注意:项目路径不要带中文、不要带空格、不要放在同步盘目录里。这三个是原生构建阶段报错的经典来源,尤其是 Android 打包时,路径里有中文会直接导致编译工具报无法识别的路径。
2.3 项目模板怎么选,目录结构长什么样
新建项目时会给几个模板:Empty(空白)、Hello World、2D 示例、3D 示例等。我的建议是第一次直接选 Hello World 模板,先把它跑起来看效果,再回头新建一个空白项目自己从零搭一遍。
这样做的好处是:你先看到"成功的状态"长什么样,再去自己搭,遇到问题时有对照。纯从零开始搭,新手很容易在某个看不见的配置上出错,然后陷入"我明明都做了为什么不行"的循环。
创建完之后,项目目录会长这样:
项目根目录/ ├── assets/ # 你的资源,脚本、图片、场景、预制体都在这 ├── library/ # 引擎导入资源后生成的缓存,不要手动改 ├── local/ # 本地配置 ├── profiles/ # 编辑器配置 ├── settings/ # 项目设置 ├── temp/ # 临时文件 ├── package.json # 项目依赖信息 └── tsconfig.json # TypeScript 配置这里最关键的一条经验:你所有的工作都发生在assets目录里,其他目录都可以理解成"引擎自动生成的,别碰"。library和temp删掉重建是安全的,引擎会重新生成;但如果你手动往里面塞东西,下次导入就可能被清掉。我见过有人把图片直接放到library里,然后到处问"为什么编辑器里看不到这个资源"。
3. 三种做法把 Hello World 显示出来
3.1 方式一:纯编辑器操作,不写一行代码
这是最适合零基础的方式,全程可视化。
- 在
assets里右键新建一个场景,命名Main。 - 双击打开场景,层级管理器里应该已经有一个 Canvas 节点。
- 选中 Canvas,右键 → 创建 → UI 组件 → Label。这时 Canvas 下多了一个 Label 节点。
- 选中这个 Label 节点,在属性检查器里找到 Label 组件的 String 属性,把内容改成
Hello World。 - 再调一下位置,把 Position 的 X、Y 都设成 0,让它居中。
- 点编辑器上方的预览按钮,浏览器里就能看到文字了。
这套操作看起来简单,但里面有三个关键点值得说清楚。
第一,Label 必须放在 Canvas 下。因为 UI 渲染依赖 Canvas 提供的渲染上下文和适配规则,你把 Label 放到场景根节点下,它可能渲染出来但不受屏幕适配影响,换个分辨率就跑到屏幕外去了。
第二,Position 是相对父节点的。Canvas 默认在屏幕中心,所以 Label 设成 (0,0) 就是相对 Canvas 居中。这个坐标系逻辑要一开始就建立,不然做复杂布局时会晕。
第三,String 属性支持多行但不自动换行排版,长文本需要配合 Overflow 属性设置成 CLAMP 或 RESIZE_HEIGHT,再指定 ContentSize。这个细节新手经常忽略,导致文字超出屏幕。
3.2 方式二:挂脚本,用代码控制文字
纯编辑器方式能出效果,但不算真正"写代码"。接下来把文字交给脚本控制。
先在assets下新建一个文件夹scripts,右键新建 TypeScript 脚本,命名HelloWorld。3.x 生成的模板大概长这样:
import { _decorator, Component, Node } from 'cc'; const { ccclass, property } = _decorator; @ccclass('HelloWorld') export class HelloWorld extends Component { start() { } update(deltaTime: number) { } }然后把它改成控制 Label 的版本:
import { _decorator, Component, Label, log } from 'cc'; const { ccclass, property } = _decorator; @ccclass('HelloWorld') export class HelloWorld extends Component { @property(Label) label: Label = null!; start() { log('Hello World from script'); if (this.label) { this.label.string = 'Hello World,我是脚本写进来的'; } } }写完保存,回到编辑器,把脚本拖到 Canvas 或者 Label 节点上,然后在属性检查器里,把 Label 节点拖到脚本组件的label属性槽里。这一步叫绑定引用,很多人漏掉,结果运行时报Cannot read property 'string' of null。
这里的@property(Label)是关键。它告诉引擎,这个字段需要序列化,并且在编辑器里暴露成一个可以拖拽的插槽。label: Label = null!里的null!是 TypeScript 的非空断言语法,只是为了让类型检查通过,实际值会在编辑器绑定后注入。你要是写 JavaScript,那用 2.x 的cc.Class写法也是一样的逻辑,只是声明方式变成properties: { label: cc.Label }。
3.3 方式三:纯代码动态创建,理解引擎的运行时
如果你想把"引擎到底怎么组织场景"这件事彻底搞明白,就试试全代码创建,一个节点都不在编辑器里预先摆。
import { _decorator, Component, Node, Label, UITransform, Color } from 'cc'; const { ccclass } = _decorator; @ccclass('DynamicHello') export class DynamicHello extends Component { start() { const node = new Node('DynamicLabel'); this.node.addChild(node); const uiTransform = node.addComponent(UITransform); uiTransform.setContentSize(400, 100); const label = node.addComponent(Label); label.string = 'Hello World,动态创建'; label.fontSize = 40; label.lineHeight = 50; label.color = new Color(255, 255, 255, 255); node.setPosition(0, 0, 0); } }这段代码的信息量很大,拆开看:
new Node('DynamicLabel')创建了一个空节点,此时它什么都没有。this.node.addChild(node)把它挂到当前脚本所在节点下面,这一步不做,节点就不在场景树里,渲染系统根本不会管它,你连错误都看不到,就是纯黑屏。这是新手最容易踩的静默失败。
node.addComponent(UITransform)给节点加上 UI 变换组件,UI 元素必须有它才能参与 UI 布局计算。addComponent(Label)加上文字组件,之后通过label.string设置内容。setPosition用的是相对父节点的坐标。
实操心得:动态创建的节点,一定要检查它有没有被加进场景树。判断方法很简单,看它的
parent是不是 null。如果 null,说明它是个"孤儿节点",跑在内存里但永远不会渲染。我最早写代码时被这个坑了整整一个下午。
4. 打包 APK:从预览到真机的完整链路
4.1 构建面板里每个参数到底在填什么
编辑器预览只是跑在浏览器里,真正的产物是原生包。打开"项目 → 构建发布",平台选 Android,会看到一堆参数。逐项说几个决定成败的:
- 发布路径:构建产物的输出目录,默认在项目根下的
build。这个路径和项目路径一样,别带中文和空格。 - 初始场景:勾选你的
Main场景。如果不勾,打开就是黑屏,因为引擎不知道先加载哪个场景。 - 包名(Package Name):Android 应用的唯一标识,格式类似
com.company.game,必须是反向域名风格,至少两段,每段只能用字母、数字、下划线,不能有中划线,不能以数字开头。这个填错,打包必失败。 - API Level / 目标 SDK:决定了运行环境的最低要求。简单说,目标 SDK 太高,老设备装不上;最低 SDK 太低,会缺一些新 API。新手建议先用默认值跑通,再按需调整。
- 签名(Keystore):调试阶段可以用引擎自带的调试签名,正式发布必须用自己的 keystore。签名信息包括路径、密码、别名、别名密码四项,缺一项就签不出来。
- 加密脚本 / 压缩纹理:这些属于优化项,第一次跑通可以先不开,减少变量。
我建议第一次构建时所有能关的优化全关掉,只求跑通。等你确认链路没问题了,再一项一项开启去调优。一次改一堆参数然后出问题,你会连排查方向都找不到。
4.2 从构建产物到能装的 APK
点"构建"之后,引擎会做几件事:编译脚本、合并资源、生成原生工程(在build/android/proj目录下)。构建本身成功不代表能出 APK,它只是把工程准备好了,真正的编译要靠 Android 原生的构建工具链。
拿到原生工程之后有两条路:
一是用 Android Studio 打开build/android/proj,然后 Build → Generate Signed Bundle/APK,按向导走。这条路直观,适合不熟命令行的人。
二是命令行直接编:
cd build/android/proj ./gradlew assembleDebug编完之后 APK 一般在proj/build/outputs/apk/debug/下面。assembleDebug出的是调试包,能装能跑,但不能上架。要出正式包就用assembleRelease,但它要求你配好签名,否则 gradle 会直接拒绝。
命令行这条路我更喜欢,因为它报错信息完整、可复现、能写进脚本。Android Studio 有时候会把 gradle 的原始错误包装一遍,反而不好定位。
4.3 原生工具链的准备工作
这一步是新手淘汰率最高的地方。要编 APK,你的机器上至少需要三样东西:
| 依赖 | 作用 | 常见问题 |
|---|---|---|
| JDK | 编译 Java/Kotlin 部分 | 版本过高或过低都可能导致 gradle 不兼容 |
| Android SDK | 提供构建工具和平台库 | 路径没配,或没装对应 API Level 的 SDK |
| NDK | 编译 C++ 引擎部分 | 版本不匹配会直接编译失败 |
这三个东西版本是强绑定的,不是说随便装个最新的就行。我的经验是:以你当前引擎版本官方文档推荐的组合为准,别自作主张升级。引擎发布时会声明适配过的 JDK、NDK 版本,照着配,能省掉九成玄学报错。
环境变量也要配到位,ANDROID_SDK_ROOT或者ANDROID_HOME指到 SDK 目录,JAVA_HOME指到 JDK 目录。配完之后在新终端里echo $JAVA_HOME验证一下,确认生效。很多"找不到命令"其实就是环境变量写在了一个没生效的终端会话里。
提示:如果你用的是 macOS 或者 Linux,路径里的空格和权限问题更常见;Windows 上路径反斜杠和编码问题更常见。跨平台的坑不太一样,排查思路却是一致的——看构建日志里的第一条错误,从那里往下读。
5. 常见问题与排查速查
5.1 脚本、文字、渲染这三类典型的"看起来没反应"
我做这个 Hello World 的过程中,遇到最多的不是崩溃,而是静默失败:不报错,但就是没效果。这类问题分三种,对应三种排查思路。
- 脚本不执行:先确认脚本有没有挂到节点上。脚本文件存在不等于参与运行,只有挂到场景里的节点上,生命周期才会被调用。还要确认脚本里的类名和文件名对得上,3.x 里装饰器
@ccclass('HelloWorld')的名字要和引用一致。 - 中文显示不出来或显示成方框:这是字体问题。Label 用的是位图字体或者系统字体,默认字体可能不包含中文字形。解决办法是导入一个包含中文的 TTF 字体,在 Label 的 Font 属性里指定它。我第一次显示中文时满屏方框,折腾了半天才发现是字体子集化的问题。
- 文字有值但不显示:检查三件事——节点是否在 Canvas 下、UITransform 的 ContentSize 是否够大、Label 的 Color 里 Alpha 是不是 0。这三条是渲染不出来的主要嫌疑。
// 一个快速自检的脚本片段 start() { log('脚本执行了'); // 验证脚本是否被调用 log('父节点是', this.node.parent?.name); // 验证节点是否在场景树里 if (this.label) { log('label 引用正常,当前文字:', this.label.string); } else { log('label 引用为 null,检查是否绑定了'); } }这几行打印看着朴素,但能在一分钟内把问题范围缩到三分之一。排查最重要的不是技巧,是先定位在哪一层断了。
5.2 构建阶段的报错分类
构建 APK 的报错看着吓人,其实能归类:
| 报错关键词 | 大概率原因 | 处理方向 |
|---|---|---|
| SDK location not found | SDK 路径没配 | 检查 local.properties 或环境变量 |
| Unsupported class file version | JDK 版本不匹配 | 换成引擎推荐版本 |
| package name invalid | 包名格式不符合规范 | 检查段数、字符、开头 |
| NDK not configured | NDK 缺失或版本不对 | 按引擎文档装对应版本 |
| keystore not found | 签名路径写错或文件不存在 | 用绝对路径重新指定 |
| 中文路径相关报错 | 项目或输出路径含中文 | 换到纯英文路径重新构建 |
这张表我建议存下来,构建报错时先扫一遍关键词,能省掉一大半百度时间。经验是:绝大多数构建失败都不是代码问题,是环境和路径问题。
还有一个很隐蔽的:构建成功但一打开就黑屏。这种情况八成是初始场景没勾选,或者勾了但场景文件没保存。我强烈建议养成习惯——构建前按一下 Ctrl+S,把当前场景保存了再点构建。编辑器里改的东西不保存是不会写进文件的,构建读的是文件。
5.3 几个从实战里攒下来的心得
第一,Hello World 一定要自己从零做一遍全链路。别满足于跑通官方示例,官方示例能跑通不代表你懂链路,自己搭一遍,会在每个环节都遇到问题,解决问题才是学到的部分。
第二,控制变量法排查。出问题时一次只改一个东西,改完立刻验证。我见过有人一次改五个配置然后重启、重构建、重装,最后也不知道是哪个改对了。
第三,把控制台当第一现场。浏览器的 F12 控制台、编辑器底部的日志、构建输出窗口、gradle 日志,这四个地方的信息按顺序看,基本没有定位不了的问题。新手最容易犯的错是"看到报错就跳过",结果在一个问题上卡三天。
第四,版本严格对齐。引擎版本、JDK、NDK、SDK、gradle 插件版本,这五个版本之间存在兼容矩阵。攒一套能跑通的版本组合,记录下来,下次直接复用,别每次重新试。
第五,给工程建个备份习惯。原生构建有时会把生成目录改得很乱,出问题时删掉build和library重新生成往往比修更省事。养成定期把assets目录单独备份的习惯,这样无论工程被折腾成什么样,你真正的劳动成果都在。
我个人的体会是,Cocos Creator 的 Hello World 真正的价值,不在于那行文字显示出来的一瞬间,而在于它逼着你把"场景怎么加载、节点怎么组织、组件怎么生效、资源怎么进包、原生怎么编译"这一整条链路都摸了一遍。这条链路走通一次,你后面做任何东西都有了坐标——出了问题,你知道该在哪个环节去找。
最后再说一个小技巧。如果你将来要从头做项目,不妨保留一个自己的"迷你工程",里面只有 Hello World 加一个空场景,构建参数全部配好、能一次成功出包。每次引擎或工具链升级后,先用这个迷你工程验证一下,确认没问题再动正式项目。用一个小工程当"探针",比在正式项目上试错靠谱得多。