一套代码发布到十几个平台:Cocos入门速览

绿泡泡、抖某音小游戏,一份工程都能出包。

摘要

Cocos Creator 是一款开源的 2D/3D 游戏引擎,用 TypeScript 写逻辑,一次开发可发布到手机、桌面、网页和各家小游戏平台。本文从安装讲到脚本、资源、打包,再到性能与版本选择。

它是什么

先理清一条脉络,不然看资料容易搞混:

  • Cocos2d-x:2010 年发布的 C++ 老引擎,官方手册写明 2019 年已停止更新。
  • Cocos Creator 2.x:2018 年发布,底层沿用 Cocos2d-x,2023 年停止更新。
  • Cocos Creator 3.x:完全放弃 Cocos2d-x 底层,换成全新的跨平台 3D 内核,与 2.x 的 API 并不完全兼容。

2026 年 1 月官方又推出了 COCOS 4(引擎与新编辑器 PinK 拆分),但其小游戏平台支持仍在陆续补齐,所以目标是小游戏的新项目,目前仍建议选 3.8 系列。引擎本体在 GitHub 上以 MIT 协议开源,底层与渲染用 C++,面向开发者的 API 是 TypeScript;编辑器基于 Electron,Windows 和 macOS 都能用。

核心优势

1. 发布目标多,而且覆盖国内入口。 官方手册列出的发布目标包括:

  • 原生:iOS、Android、Windows、macOS、华为鸿蒙、Google Play 等
  • 网页:Web Mobile(H5)与 Web Desktop
  • 小游戏:绿泡泡小游戏、抖某音小游戏、OPPO、vivo、荣耀、华为快游戏,以及海外社交平台的 Instant Games

另外,官方文档侧边栏里还能看到电商、支付和搜索类 App 的小游戏入口。OPPO、vivo、荣耀、华为的快游戏渠道都有对应的构建选项,这是它在国内生态里的实际价值:同一份工程,切换构建目标就能出对应平台的包,各渠道的适配和审核仍要单独处理。

2. 2D 和 3D 都能做。 同一个编辑器里,动画、物理、粒子、UI、地形编辑和即时预览都是内置的,Spine、DragonBones、TiledMap、Box2D 也有现成支持。3D 方面提供可扩展的前向与延迟渲染管线,以及基于物理的材质与光照。

3. 脚本语言友好。 JavaScript 和 TypeScript 可以混用,组件化写法和 Unity、Godot 思路相近,有前端或这类引擎经验的人上手较快。

4. 对小游戏的包体很上心。 3.8.5 起支持 WASM 模块延迟加载,Spine 模块体积比 3.8.4 缩小约 40%,3.8.6 继续优化包体和性能。小游戏有主包大小限制,这类改动直接决定能不能上线。

面向人群

  • 想做小游戏、轻量手游的个人和小团队
  • 有 TypeScript 或前端基础,想转游戏开发的人
  • 需要一次开发、多渠道分发的项目
  • 做过 Godot、Unity,想了解国内常用方案的人

如果你要做大型 3A 级 3D 项目,或需要非常深的主机平台支持,Cocos 不是首选。

快速上手

  1. 安装:从官网下载 Cocos Dashboard,在里面安装 Creator 3.8 系列的编辑器版本,再新建项目,模板选 2D 或 3D 空项目。
  2. 认识四个核心概念 :
    • 场景(Scene):一个关卡或一个界面
    • 节点(Node):场景里的一切对象
    • 组件(Component):挂在节点上的功能,脚本也是组件
    • 资源(Asset):图片、音频、预制体等
  3. 写第一个脚本:在资源管理器里新建 TypeScript 脚本,拖到节点上。
ts 复制代码
import { _decorator, Component } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('Mover')
export class Mover extends Component {
    @property
    speed = 100;

    start () {
        // 第一帧更新之前执行
    }

    update (dt: number) {
        const p = this.node.position;
        this.node.setPosition(p.x + this.speed * dt, p.y, p.z);
    }
}

@property 声明的变量会出现在属性检查器里,策划不改代码也能调数值。onLoad、start、update 是最常用的三个生命周期回调,update 的参数是上一帧耗时,移动要乘上它,才不会因为帧率不同而快慢不一。

  1. 预览:点编辑器顶部的预览按钮,直接在浏览器或模拟器里跑。

用法详解:做一个打飞机小游戏

下面用「打飞机」这个小游戏当线索,把常用功能一个个串起来。每个案例都是独立的组件,挂到对应节点上即可。

案例一:键盘与触摸控制玩家

输入统一走 input。在 onEnable 里注册,onDisable 里取消,参数必须和注册时一致,否则取消不掉,节点销毁后还会收到回调。

ts 复制代码
import { _decorator, Component, input, Input, EventKeyboard, EventTouch, KeyCode } from 'cc';
const { ccclass } = _decorator;

@ccclass('PlayerInput')
export class PlayerInput extends Component {
    onEnable () {
        input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
        input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
    }

    onDisable () {
        input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
        input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
    }

    onKeyDown (e: EventKeyboard) {
        if (e.keyCode === KeyCode.KEY_A) { /* 向左 */ }
        if (e.keyCode === KeyCode.KEY_D) { /* 向右 */ }
    }

    onTouchMove (e: EventTouch) {
        // 手指位置,用 UI 坐标可以直接对应界面上的位置
        const pos = e.getUILocation();
        console.log(pos.x, pos.y);
    }
}

做小游戏时以触摸为主,键盘只是方便在电脑上调试。

案例二:用缓动做动画,不用写 update

按钮缩放、飘字、移动这类一次性动画,用缓动(tween)比自己在 update 里算更省事:

ts 复制代码
import { tween, Vec3 } from 'cc';

// 受击时放大再复原,然后执行回调
tween(this.node)
    .to(0.1, { scale: new Vec3(1.2, 1.2, 1) })
    .to(0.1, { scale: new Vec3(1, 1, 1) })
    .call(() => console.log('动画结束'))
    .start();

to 是变到目标值,by 是在当前值上增加;还可以用 sequence 串联、repeat 重复、union 合并成一个整体,并通过 easing: 'backIn' 这类参数设置缓动曲线。

案例三:预制体发射子弹,加对象池

子弹要反复创建和销毁,直接 instantiate 再 destroy 会产生垃圾回收压力。做法是把子弹做成预制体,用 NodePool 回收复用:

ts 复制代码
import { _decorator, Component, Node, Prefab, NodePool, instantiate } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('Shooter')
export class Shooter extends Component {
    @property(Prefab)
    bulletPrefab: Prefab = null!;

    @property(Node)
    bulletRoot: Node = null!;   // 子弹挂在这个节点下

    private pool = new NodePool();

    shoot () {
        // 池里有就取,没有再新建
        const bullet = this.pool.size() > 0
            ? this.pool.get()!
            : instantiate(this.bulletPrefab);
        bullet.setParent(this.bulletRoot);
        bullet.setPosition(this.node.position);
    }

    recycle (bullet: Node) {
        // 出屏或命中后回收,而不是 destroy
        this.pool.put(bullet);
    }
}

@property(Prefab) 和 @property(Node) 会在属性检查器里出现一个拖拽框,把预制体和节点拖进去就绑定好了,不用写路径字符串。

案例四:2D 碰撞与计分

给子弹和敌机都加上 2D 碰撞体(子弹带刚体),就能在回调里处理命中:

ts 复制代码
import { _decorator, Component, Collider2D, Contact2DType, IPhysics2DContact } from 'cc';
const { ccclass } = _decorator;

@ccclass('Enemy')
export class Enemy extends Component {
    start () {
        const collider = this.getComponent(Collider2D);
        if (collider) {
            collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
        }
    }

    onBeginContact (self: Collider2D, other: Collider2D, contact: IPhysics2DContact | null) {
        // 两个碰撞体开始接触时调用一次
        console.log('命中', other.node.name);
        // 回调里不要立刻销毁或创建物理对象,改成标记,下一帧在 update 里处理
    }
}

要注意两点:用 Box2D 物理模块时,需要在刚体的属性检查器里勾选「EnabledContactListener」,否则收不到回调;在回调里创建的刚体、关节不会立刻生效,而是等本次物理更新结束后才创建。回调里拿到的碰撞信息也是缓存,不要长期持有,需要的话自己拷贝一份。

案例五:按钮与场景切换

主菜单点「开始」进入游戏场景:

ts 复制代码
import { _decorator, Component, Button, director } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('StartButton')
export class StartButton extends Component {
    @property(Button)
    startBtn: Button = null!;

    onEnable () {
        this.startBtn.node.on(Button.EventType.CLICK, this.onStart, this);
    }

    onDisable () {
        this.startBtn.node.off(Button.EventType.CLICK, this.onStart, this);
    }

    onStart () {
        // 参数是场景名,场景需要在构建里勾选
        director.loadScene('game');
    }
}

不想写代码也行:在编辑器里选中 Button 组件,直接给「Click Events」绑定目标节点、组件和方法名,效果一样。

案例六:本地存档

最高分、关卡进度、音效开关这类数据,用 sys.localStorage 保存,接口和网页的 localStorage 一致,在各个发布平台上都有对应实现:

ts 复制代码
import { sys } from 'cc';

const best = Number(sys.localStorage.getItem('bestScore') ?? 0);
if (score > best) {
    sys.localStorage.setItem('bestScore', String(score));
}

存的都是字符串,数字和对象需要自己转换。需要云端存档的话,要使用各渠道自己的接口,那部分属于渠道适配。

进阶用法

资源分包与动态加载

小游戏的主包有体积上限,做法是把关卡、大图、音频放进 Asset Bundle:先在编辑器里把某个文件夹设为 Bundle,运行时再按需加载。

ts 复制代码
import { assetManager, Prefab, instantiate, director } from 'cc';

assetManager.loadBundle('level2', (err, bundle) => {
    if (err) { console.error(err); return; }
    bundle.load('prefabs/boss', Prefab, (err2, prefab) => {
        if (err2) { console.error(err2); return; }
        const boss = instantiate(prefab);
        director.getScene()!.addChild(boss);
    });
});

加载了就要考虑释放:单个资源可以 assetManager.releaseAsset(asset),整个 Bundle 的某个路径用 bundle.release(path, type),bundle.releaseAll() 会释放全部,要谨慎使用。资源管理是 Cocos 项目里最容易出问题的地方,加载了不释放,内存会一路涨。

按平台写分支

同一份代码里,个别能力在不同平台上并不一致,比如只有小游戏才有的登录、分享接口。可以用 sys.platform 判断当前运行环境,再走各自的分支,其余逻辑保持统一:

ts 复制代码
import { sys } from 'cc';

if (sys.platform === sys.Platform.WECHAT_GAME) {
    // 绿泡泡小游戏专属逻辑,比如调用渠道的分享接口
} else {
    // 其他平台走通用逻辑
}

建议把这类分支集中在一个「平台适配层」里,业务代码只调用统一接口,换渠道时只改这一层。

构建发布

打开「项目 → 构建发布」(快捷键 Ctrl/Cmd + Shift + B),选择平台,填好对应平台的参数(比如小游戏的 AppID),点构建即可。构建产物还要用对应平台的开发者工具预览和上传,提前装好。

性能与渲染

3.8 起部分模块(Spine、Bullet 与 PhysX 物理、WebGPU 渲染后端)用 wasm 实现。WebGPU 后端对浏览器和设备要求较高,社区论坛里有升级后启用 WebGPU 导致无法运行的反馈,所以这一项在上线前一定要实机测试。

原生平台

在 iOS、Android 和鸿蒙上走的是 C++ 原生运行时,需要对应的 Xcode、Android Studio 或 DevEco 环境。3.8.5 起已适配华为 HarmonyOS Next。

几个常见的坑

  • 版本别混用。 2.x 和 3.x 的 API 不兼容,网上很多老教程是 2.x 的,抄代码前先看版本。
  • 小游戏先看包体。 先用空项目构建一次,量出基础包体,再决定怎么分包。
  • 各平台要分别实测。 同一份代码在不同渠道的表现并不完全一致,渠道的审核和接口也各有差异。

结尾:速查清单

  • 做小游戏先选 3.8 系列,TypeScript 写逻辑;关注 COCOS 4 的小游戏支持进度
  • 场景 → 节点 → 组件,脚本就是组件
  • 先量包体,再分包;每个发布平台都实机测一遍
  • 输入用 input,动画用 tween,重复对象用预制体加对象池
  • 回调里别直接改物理对象;资源加载了就要想好怎么释放

之前聊过 Godot,你会把下一个项目押在 Godot 还是 Cocos?

相关推荐
阿松爱学习4 小时前
【Unity开发】监听Scene场景事件
unity·c#
应用市场8 小时前
离线小游戏做“每日挑战”:日期当随机种子,整局存成 JSON,再加一个“我不是机器人”
android·kotlin·游戏开发
用户2788607285679 小时前
战争雷霆换区记录:字段里的「未知」不能写成正常
网络协议·游戏开发
前端小L10 小时前
typescript基础
typescript
天天代码码天天13 小时前
屏幕翻拍满是彩色条纹?用 C# + Vulkan 做了一个去摩尔纹工具,支持单图与文件夹批量
c#·去摩尔纹
2501_9307077814 小时前
使用C#代码在 Word 文档中添加、插入或删除页面
开发语言·c#·word
SmalBox14 小时前
04-05-源码-清单生成与构建报告
unity3d·游戏开发
淡海水15 小时前
03-03-架构篇-SGen GC架构解析
unity·架构·c#·gc·sgen
濮水大叔16 小时前
CabloyJS 的 AI 时刻:从 TypeScript 全栈框架到 AI 规范驱动开发
typescript·ai编程·全栈