Cross-platform 2D/3D game engine development assistant for Cocos Creator. This file is theentry point and routing: the core workflow and quick-reference tables are here; deep content is loaded on demand from references/ to avoid filling the context all at once.
When to use
Applies to:
- Writing, refactoring, and debugging Cocos Creator TypeScript / JavaScript component scripts
- Explaining the
ccmodule APIs, the node and component system, lifecycle, events, animation, physics, UI, assets, and scene management - Troubleshooting build and publishing issues: WeChat Mini Game, Douyin Mini Game, H5, iOS, Android, HarmonyOS Next
- Performance optimization: DrawCall, batching, memory, object pools, package size, and first-screen loading
- API migration and version compatibility assessment from 3.x to COCOS 4
Not applicable to:
- Unity / Unreal / Godot / Cocos2d-x C++ questions (APIs are not interchangeable; do not apply this skill's patterns)
- Pure art and game-design numeric balancing (can serve as context, but is not this skill's responsibility)
Step 0: Confirm the version first, then write code
This step cannot be skipped. 3.x and 2.x APIs are incompatible; differences also exist between 3.8 and COCOS 4.
First confirm the project's actual version; do not assume from memory:
# 读取项目记录的目标版本
cat project.json 2>/dev/null | head -20 # 老项目在根目录
find . -maxdepth 2 -name "package.json" -not -path "*/node_modules/*" -exec grep -l '"cc"' {} \;
- Version obtained → write code against the corresponding API, and note "based on Cocos Creator X.Y.Z" in the reply.
- Version not obtainable → ask the user first, or explicitly state "the following code is based on 3.8.x; if your project is 2.x, the APIs need to be rewritten".
| Version | Status | Notes |
|---|---|---|
| 2.x | No longer evolving | Retains the cc.Class、cc.Node legacy API, incompatible with 3.x |
| 3.8.x | Long-term main stable version | 3.8.9 is the latest patch in 2026; 3.8.5+ supports HarmonyOS Next |
| COCOS 4 | Fully open-sourced under MIT in January 2026 | Evolution of 3.x, committed to forward compatibility; editor capabilities gradually migrating to Headless CLI |
Step 1: Determine where the code should go
Read the project's existing structure before acting; do not create directories from scratch based on a template. Typical Cocos project conventions:
assets/
├── scenes/ # .scene 场景
├── scripts/
│ ├── components/ # 挂载到节点的组件
│ ├── managers/ # 全局管理器(单例)
│ └── utils/ # 纯函数工具
├── prefabs/ # .prefab 预制体
├── textures/ audio/ animations/ materials/
└── resources/ # 只有此目录下资源可被 resources.load() 动态加载
Three hard constraints:
- Every asset has a same-named
.metafile. New assets must have the editor generate.meta; writing it by hand or omitting it will cause lost references and build errors. - Only assets under
assets/resources/can be loaded by path usingresources.load(). Assets in other directories must be linked in the editor via@property. - The mapping between scripts and component classes is determined by the UUID in
.meta. Do not rename or move script files that are already referenced by scenes; this will break the links.
Step 2: Write code
Component skeleton (3.8.x):
import { _decorator, Component, Node, Vec3 } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('PlayerController')
export class PlayerController extends Component {
@property(Node)
target: Node | null = null;
@property({ tooltip: '移动速度(像素/秒)' })
speed = 120;
private _dir = new Vec3();
onLoad() {
// 只做自身初始化;跨节点引用在这里可能还没就绪
}
start() {
// 场景内所有节点 onLoad 完成后调用,适合跨节点交互
}
update(dt: number) {
if (this.speed === 0) return;
const pos = this.node.position;
this.node.setPosition(
pos.x + this._dir.x * this.speed * dt,
pos.y + this._dir.y * this.speed * dt,
pos.z + this._dir.z * this.speed * dt,
);
}
onDestroy() {
// 在这里反注册所有全局监听,否则场景切换后回调仍会触发
}
}
Lifecycle order and applicable scenarios:
| Callback | Timing | What it is suitable for |
|---|---|---|
onLoad |
Only once when the node is activated and the component is enabled | Initialization of its own fields, cache getComponent results |
start |
The first time update before, only once |
Initialization that depends on other nodes |
update(dt) |
Every frame | Per-frame logic,dt unit is seconds |
lateUpdate(dt) |
After all update afterwards |
Camera follow (to avoid jitter) |
onEnable / onDisable |
Component enabled / disabled | Event registration and unregistration |
onDestroy |
Before destruction | Release resources, unregister global events |
For the complete API list, see references/api-quick-ref.md, and for directly reusable examples, see references/examples.md。
Step 3: Must verify
You cannot deliver directly after writing the code. Choose the lowest-cost effective verification according to the project situation:
# 1) 类型检查(最快,无需打开编辑器)
npx tsc --noEmit -p tsconfig.json
# 2) 项目体检(本技能自带:检查 .meta 缺失、脚本断链、resources 误放、大文件)
python skills/cocos-creator/scripts/cocos_doctor.py --project .
# 3) 编辑器 / CLI 构建
# 编辑器:预览运行(浏览器或模拟器)
# COCOS 4:cocos-cli(支持 Headless 构建)
If the verification does not pass, state it truthfully; do not deliver with "it should be fine".
Step 4: Self-check common pitfalls before submission
Node.positionuses reference semantics:this.node.positionreturns an object reused internally by the engine; do not hold it long-term, and do not use it as a temporary variable for calculations. When you need vector operations, use the static methods ofVec3with your own temporary object.resources.loadpaths do not include an extension:resources.load('prefabs/enemy', Prefab, cb). Writing it asprefabs/enemy.prefabwill fail.- Events must be unregistered in pairs:
this.node.on(...)goes withoff,input.on(...)goes withoff, and pass the samethisas the target; otherwise you cannot get the same callback reference. instantiateNodes created byaddChild; otherwise they do not participate in the scene tree and the components will not execute.- Frequent
instantiate/destroycauses GC jitter; use an object pool (NodePoolor a self-built pool). - Atlases and materials are prerequisites for batching: only Sprites in the same atlas and with the same material can be batched; interleaving UI hierarchy will break batching.
- Async race conditions: two consecutive
director.loadScene, or operating on an already destroyed node in a load callback, requiresisValid(node)protection.
For more "symptom → cause → solution", see references/troubleshooting.md。
Progressive reference index
Load on demand; do not pre-read everything at once:
| File | Content | When to load |
|---|---|---|
| references/api-quick-ref.md | Common module imports, quick reference for node / event / animation / physics / UI / resource / scene APIs | When you need to look up specific API signatures |
| references/examples.md | Complete examples such as player controller, object pool, UI list, state machine | When you need a directly reusable complete implementation |
| references/best-practices.md | Project architecture, directory conventions, singletons and event bus, code standards, resource management strategy | When designing architecture or doing Code Review |
| references/performance.md | Quantitative optimization paths for DrawCall, batching, memory, GC, package size, first-screen loading | When stuttering, frame drops, or package size overruns occur |
| references/publishing.md | WeChat / Douyin mini-game subpackaging, H5, iOS, Android, HarmonyOS Next release checklist | When there is a build error or you are preparing to launch |
| references/troubleshooting.md | Troubleshooting manual for editor, script compilation, resources, physics, UI, build and release | When encountering a specific error |
| references/cocos4-migration.md | 3.8 → COCOS 4 migration, cocos-cli / Headless, AI-Native capability changes | Evaluate upgrading or using COCOS 4 |
Quick reference: the most commonly used imports and snippets
import { _decorator, Component, Node, Vec3, Vec2, Prefab, instantiate, Label, Sprite,
Button, Widget, director, resources, tween, input, Input, EventKeyboard,
RigidBody, Collider, ICollisionEvent, Color, isValid, assetManager } from 'cc';
// 节点与组件
const child = this.node.getChildByName('Child');
const label = this.node.getComponent(Label);
const btn = this.node.getComponentInChildren(Button);
// 实例化预制体
resources.load('prefabs/enemy', Prefab, (err, prefab) => {
if (err || !isValid(this.node)) return;
const node = instantiate(prefab);
this.node.addChild(node);
});
// 事件(target 一致才能 off 掉)
this.node.on(Node.EventType.TOUCH_END, this.onTap, this);
this.node.off(Node.EventType.TOUCH_END, this.onTap, this);
// Tween
tween(this.node)
.to(0.3, { position: new Vec3(100, 0, 0) }, { easing: 'quadOut' })
.delay(0.2)
.call(() => console.log('done'))
.start();
// 场景切换
director.loadScene('Game', (err) => { if (err) console.error(err); });
// 物理碰撞
this.getComponent(Collider)?.on('onCollisionEnter', (e: ICollisionEvent) => { /* ... */ });
Anti-rationalization: common "lazy excuses" and rebuttals
- "Don't confirm the version yet, 3.8 syntax should be universal." → 2.x and 3.x are incompatible; writing the wrong version equals delivering dead code. Confirm first.
- "No need to run validation, the code looks right." → Type errors and missing
.metacan only be found by tools; the naked eye can't see them. - "
.metafiles aren't important, don't commit them yet." → Missing.metawill cause all resource references to be lost at build time; they must be committed. - "Assets can be placed anywhere and still
resources.load." → Onlyassets/resources/works; other directories must use@propertyto attach references. - "It's fine if events aren't
off." → After a scene switch, callbacks still hold references to destroyed nodes; this is a frequent source of crashes and memory leaks. - "Optimize performance problems at the end." → DrawCall and atlas strategy are structural decisions; the later you change them, the higher the cost.
Danger signals (Red Flags)
If any of the following occurs, stop and confirm before continuing to write:
- Code contains
cc.Class/cc.Nodeold-style APIs, but the file is organized according to the 3.x structure (version confusion) - Referenced a path outside
assets/resources/but usedresources.load - Added a new asset file but the corresponding
.meta - Modified a script file name / path, and that script is already referenced by
.sceneor.prefabQuote destroy()and then accessed that node's properties- Claimed "optimized" but gave no before/after comparison data for DrawCall / frame time / memory
Verification checklist (exit criteria)
Confirm each item before delivery, and back it up with evidence:
- Confirmed the Creator version corresponding to the project and noted it in the reply
-
npx tsc --noEmitpassed (or clearly explained why it could not be run) - All newly added assets have
.metafiles - All
on/offare paired, andtargetare consistent - Applied
isValidprotection for destroyed nodes in asynchronous callbacks - When performance changes are involved, provide quantitative before/after comparison
- When build and release are involved, specify the target platform and verification method
Official resources
- Documentation (3.8):https://docs.cocos.com/creator/3.8/manual/zh/
- API(3.8):https://docs.cocos.com/creator/3.8/api/zh/
- COCOS 4 open-source repository:https://github.com/cocos/cocos4
- COCOS CLI (Headless build):https://github.com/cocos/cocos-cli
- Sample projects:https://github.com/cocos/cocos-test-projects
- Chinese community:https://forum.cocos.org/
- Cocos Store:https://store.cocos.com
This skill is maintained by the community. Where it differs from official documentation,the official documentation prevails, and Issues for corrections are welcome.