TheOne Studio Cocos Creator Development Standards
⚠️ Cocos Creator 3.x (TypeScript 4.1+): All patterns and examples are compatible with Cocos Creator 3.x playable ads development.
Skill Purpose
This skill enforces TheOne Studio's comprehensive Cocos Creator development standards with CODE QUALITY FIRST:
Priority 1: Code Quality & Hygiene (MOST IMPORTANT)
-
TypeScript strict mode, ESLint configuration, access modifiers (public/private/protected)
-
Throw exceptions (never silent errors)
-
console.log for development, remove in production builds
-
readonly for immutable fields, const for constants
-
No inline comments (use descriptive names)
-
Proper error handling and type safety
Priority 2: Modern TypeScript Patterns
-
Array methods (map/filter/reduce) over loops
-
Arrow functions, destructuring, spread operators
-
Optional chaining, nullish coalescing
-
Type guards, utility types (Partial, Required, Readonly)
-
Modern TypeScript features
Priority 3: Cocos Creator Architecture
-
Component-based Entity-Component (EC) system
-
Lifecycle methods: onLoad→start→onEnable→update→onDisable→onDestroy
-
EventDispatcher pattern for custom events
-
Node event system (EventTouch, keyboard events)
-
Resource management and pooling for playables
Priority 4: Playable Ads Performance
-
DrawCall batching (<10 DrawCalls target)
-
Sprite atlas configuration (auto-atlas enabled)
-
GPU skinning for skeletal animations
-
Zero allocations in update() loop
-
Bundle size <5MB (texture compression, code minification)
When This Skill Triggers
-
Writing or refactoring Cocos Creator TypeScript code
-
Implementing playable ads features
-
Working with component lifecycle and events
-
Optimizing performance for playable ads
-
Reviewing code changes or pull requests
-
Setting up playable project architecture
-
Reducing bundle size or DrawCall counts
Quick Reference Guide
What Do You Need Help With?
Priority Task Reference
🔴 PRIORITY 1: Code Quality (Check FIRST)
1 TypeScript strict mode, ESLint, access modifiers Quality & Hygiene ⭐
1 Throw exceptions, proper error handling Quality & Hygiene ⭐
1 console.log (development only), remove in production Quality & Hygiene ⭐
1 readonly/const, no inline comments, descriptive names Quality & Hygiene ⭐
🟡 PRIORITY 2: Modern TypeScript Patterns
2 Array methods, arrow functions, destructuring Modern TypeScript
2 Optional chaining, nullish coalescing Modern TypeScript
2 Type guards, utility types Modern TypeScript
🟢 PRIORITY 3: Cocos Architecture
3 Component system, @property decorator Component System
3 Lifecycle methods (onLoad→start→update→onDestroy) Component System
3 EventDispatcher, Node events, cleanup Event Patterns
3 Resource loading, pooling, memory management Playable Optimization
🔵 PRIORITY 4: Performance & Review
4 DrawCall batching, sprite atlas, GPU skinning Playable Optimization
4 Update loop optimization, zero allocations Performance
4 Bundle size reduction (<5MB target) Size Optimization
4 Architecture review (components, lifecycle, events) Architecture Review
4 TypeScript quality review Quality Review
4 Performance review (DrawCalls, allocations) Performance Review
🔴 CRITICAL: Code Quality Rules (CHECK FIRST!)
⚠️ MANDATORY QUALITY STANDARDS
ALWAYS enforce these BEFORE writing any code:
-
Enable TypeScript strict mode - "strict": true in tsconfig.json
-
Use ESLint configuration - @typescript-eslint rules enabled
-
Use access modifiers - public/private/protected on all members
-
Throw exceptions for errors - NEVER silent failures or undefined returns
-
console.log for development only - Remove all console statements in production builds
-
Use readonly for immutable fields - Mark fields that aren't reassigned
-
Use const for constants - Constants should be const, not let
-
No inline comments - Use descriptive names; code should be self-explanatory
-
Proper null/undefined handling - Use optional chaining and nullish coalescing
-
Type safety - Avoid any type, use proper types and interfaces
Example: Enforce Quality First
// ✅ EXCELLENT: All quality rules enforced import { _decorator, Component, Node, EventTouch } from 'cc'; const { ccclass, property } = _decorator;
@ccclass('PlayerController') export class PlayerController extends Component { // 3. Access modifier, 6. readonly for immutable @property(Node) private readonly targetNode: Node | null = null;
// 7. const for constants
private static readonly MAX_HEALTH: number = 100;
private currentHealth: number = 100;
// Lifecycle: onLoad → start → onEnable
protected onLoad(): void {
// 4. Throw exception for errors
if (!this.targetNode) {
throw new Error('PlayerController: targetNode is not assigned');
}
// 9. Proper event listener setup
this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
}
protected onDestroy(): void {
// 9. Always cleanup event listeners
this.node.off(Node.EventType.TOUCH_START, this.onTouchStart, this);
}
private onTouchStart(event: EventTouch): void {
// 5. console.log only for development (remove in production)
if (CC_DEBUG) {
console.log('Touch detected');
}
this.takeDamage(10);
}
// 8. Descriptive method names (no inline comments needed)
private takeDamage(amount: number): void {
this.currentHealth -= amount;
if (this.currentHealth <= 0) {
this.handlePlayerDeath();
}
}
private handlePlayerDeath(): void {
// Death logic
}
}
⚠️ Cocos Creator Architecture Rules (AFTER Quality)
Component System Fundamentals
Entity-Component (EC) System:
-
Components extend Component class
-
Use @ccclass and @property decorators
-
Lifecycle: onLoad → start → onEnable → update → lateUpdate → onDisable → onDestroy
Execution Order:
-
onLoad() - Component initialization, one-time setup
-
start() - After all components loaded, can reference other components
-
onEnable() - When component/node enabled (can be called multiple times)
-
update(dt) - Every frame (use sparingly for playables)
-
lateUpdate(dt) - After all update() calls
-
onDisable() - When component/node disabled
-
onDestroy() - Cleanup, remove listeners, release resources
Universal Rules:
-
✅ Initialize in onLoad(), reference other components in start()
-
✅ Register events in onEnable(), unregister in onDisable()
-
✅ Always cleanup listeners in onDestroy()
-
✅ Avoid heavy logic in update() (performance critical for playables)
-
✅ Use readonly for @property fields that shouldn't be reassigned
-
✅ Throw exceptions for missing required references
Brief Examples
🔴 Code Quality First
// ✅ EXCELLENT: Quality rules enforced import { _decorator, Component, Node } from 'cc'; const { ccclass, property } = _decorator;
@ccclass('GameManager') export class GameManager extends Component { @property(Node) private readonly playerNode: Node | null = null;
private static readonly MAX_SCORE: number = 1000;
private currentScore: number = 0;
protected onLoad(): void {
// Throw exception for missing required references
if (!this.playerNode) {
throw new Error('GameManager: playerNode is required');
}
if (CC_DEBUG) {
console.log('GameManager initialized'); // Development only
}
}
public addScore(points: number): void {
if (points <= 0) {
throw new Error('GameManager.addScore: points must be positive');
}
this.currentScore = Math.min(
this.currentScore + points,
GameManager.MAX_SCORE
);
}
}
🟡 Modern TypeScript Patterns
// ✅ GOOD: Array methods instead of loops const activeEnemies = allEnemies.filter(e => e.isActive); const enemyPositions = activeEnemies.map(e => e.node.position);
// ✅ GOOD: Optional chaining and nullish coalescing const playerName = player?.name ?? 'Unknown';
// ✅ GOOD: Destructuring const { x, y } = this.node.position;
// ✅ GOOD: Arrow functions this.enemies.forEach(enemy => enemy.takeDamage(10));
// ✅ GOOD: Type guards function isPlayer(node: Node): node is PlayerNode { return node.getComponent(PlayerController) !== null; }
🟢 Cocos Creator Component Pattern
import { _decorator, Component, Node, EventTouch, Vec3 } from 'cc'; const { ccclass, property } = _decorator;
@ccclass('TouchHandler') export class TouchHandler extends Component { @property(Node) private readonly targetNode: Node | null = null;
private readonly tempVec3: Vec3 = new Vec3(); // Reusable vector
// 1. onLoad: Initialize component
protected onLoad(): void {
if (!this.targetNode) {
throw new Error('TouchHandler: targetNode is required');
}
}
// 2. start: Reference other components (if needed)
protected start(): void {
// Can safely access other components here
}
// 3. onEnable: Register event listeners
protected onEnable(): void {
this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
this.node.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
}
// 4. onDisable: Unregister event listeners
protected onDisable(): void {
this.node.off(Node.EventType.TOUCH_START, this.onTouchStart, this);
this.node.off(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
}
// 5. onDestroy: Final cleanup
protected onDestroy(): void {
// Release any additional resources
}
private onTouchStart(event: EventTouch): void {
// Handle touch
}
private onTouchMove(event: EventTouch): void {
// Reuse vector to avoid allocations
this.targetNode!.getPosition(this.tempVec3);
this.tempVec3.y += 10;
this.targetNode!.setPosition(this.tempVec3);
}
}
🟢 Event Dispatcher Pattern
import { _decorator, Component, EventTarget } from 'cc'; const { ccclass } = _decorator;
// Custom event types export enum GameEvent { SCORE_CHANGED = 'score_changed', LEVEL_COMPLETE = 'level_complete', PLAYER_DIED = 'player_died', }
export interface ScoreChangedEvent { oldScore: number; newScore: number; }
@ccclass('EventManager') export class EventManager extends Component { private static instance: EventManager | null = null; private readonly eventTarget: EventTarget = new EventTarget();
protected onLoad(): void {
if (EventManager.instance) {
throw new Error('EventManager: instance already exists');
}
EventManager.instance = this;
}
public static emit(event: GameEvent, data?: any): void {
if (!EventManager.instance) {
throw new Error('EventManager: instance not initialized');
}
EventManager.instance.eventTarget.emit(event, data);
}
public static on(event: GameEvent, callback: Function, target?: any): void {
if (!EventManager.instance) {
throw new Error('EventManager: instance not initialized');
}
EventManager.instance.eventTarget.on(event, callback, target);
}
public static off(event: GameEvent, callback: Function, target?: any): void {
if (!EventManager.instance) {
throw new Error('EventManager: instance not initialized');
}
EventManager.instance.eventTarget.off(event, callback, target);
}
}
// Usage in component @ccclass('ScoreDisplay') export class ScoreDisplay extends Component { protected onEnable(): void { EventManager.on(GameEvent.SCORE_CHANGED, this.onScoreChanged, this); }
protected onDisable(): void {
EventManager.off(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
}
private onScoreChanged(data: ScoreChangedEvent): void {
console.log(`Score: ${data.oldScore} → ${data.newScore}`);
}
}
🔵 Playable Performance Optimization
import { _decorator, Component, Node, Sprite, SpriteAtlas } from 'cc'; const { ccclass, property } = _decorator;
@ccclass('OptimizedSpriteManager') export class OptimizedSpriteManager extends Component { // Use sprite atlas for DrawCall batching @property(SpriteAtlas) private readonly characterAtlas: SpriteAtlas | null = null;
// Preallocate arrays to avoid allocations in update()
private readonly tempNodes: Node[] = [];
private frameCount: number = 0;
protected onLoad(): void {
if (!this.characterAtlas) {
throw new Error('OptimizedSpriteManager: characterAtlas is required');
}
// Prewarm sprite frames from atlas
this.prewarmSpriteFrames();
}
private prewarmSpriteFrames(): void {
// Load all sprites from atlas (batched in single DrawCall)
const spriteFrame = this.characterAtlas!.getSpriteFrame('character_idle');
if (!spriteFrame) {
throw new Error('Sprite frame not found in atlas');
}
}
// Optimize update: avoid allocations, use object pooling
protected update(dt: number): void {
// Run expensive operations every N frames instead of every frame
this.frameCount++;
if (this.frameCount % 10 === 0) {
this.updateExpensiveOperation();
}
}
private updateExpensiveOperation(): void {
// Reuse array instead of creating new one
this.tempNodes.length = 0;
// Batch operations to reduce DrawCalls
}
}
Code Review Checklist
Quick Validation (before committing)
🔴 Code Quality (CHECK FIRST):
-
TypeScript strict mode enabled in tsconfig.json
-
ESLint rules passing (no errors)
-
All access modifiers correct (public/private/protected)
-
Exceptions thrown for errors (no silent failures)
-
console.log removed or wrapped in CC_DEBUG
-
readonly used for non-reassigned fields
-
const used for constants
-
No inline comments (self-explanatory code)
-
Proper null/undefined handling
-
No any types (use proper types)
🟡 Modern TypeScript Patterns:
-
Array methods used instead of manual loops
-
Arrow functions for callbacks
-
Optional chaining (?.) for safe property access
-
Nullish coalescing (??) for default values
-
Destructuring for cleaner code
-
Type guards for type narrowing
🟢 Cocos Creator Architecture:
-
Component lifecycle methods in correct order
-
onLoad() for initialization, start() for references
-
Event listeners registered in onEnable()
-
Event listeners unregistered in onDisable()
-
Resources released in onDestroy()
-
@property decorator used correctly
-
Required references validated (throw if null)
🔵 Playable Performance:
-
No allocations in update() loop
-
Sprite atlas used for DrawCall batching
-
GPU skinning enabled for skeletal animations
-
Expensive operations throttled (not every frame)
-
Object pooling for frequently created objects
-
Texture compression enabled
-
Bundle size <5MB target
-
DrawCall count <10 target
Common Mistakes to Avoid
❌ DON'T:
-
Ignore TypeScript strict mode → Enable "strict": true
-
Silent error handling → Throw exceptions for errors
-
Leave console.log in production → Remove or wrap in CC_DEBUG
-
Skip access modifiers → Use public/private/protected
-
Use any type → Define proper types and interfaces
-
Add inline comments → Use descriptive names instead
-
Skip event cleanup → Always unregister in onDisable/onDestroy
-
Allocate in update() → Preallocate and reuse objects
-
Forget sprite atlas → Use atlas for DrawCall batching
-
Heavy logic in update() → Throttle expensive operations
-
Skip null checks → Validate required references in onLoad
-
Mutable @property fields → Use readonly when appropriate
-
Manual loops over arrays → Use map/filter/reduce
-
Ignore bundle size → Monitor and optimize (<5MB target)
✅ DO:
-
Enable TypeScript strict mode ("strict": true)
-
Throw exceptions for errors (never silent failures)
-
Use console.log for development only (remove in production)
-
Use access modifiers (public/private/protected)
-
Define proper types (avoid any )
-
Use descriptive names (no inline comments)
-
Always cleanup events (onDisable/onDestroy)
-
Preallocate objects (reuse in update())
-
Use sprite atlas (DrawCall batching)
-
Throttle expensive operations (not every frame)
-
Validate required references (throw in onLoad if null)
-
Use readonly for @property (when appropriate)
-
Use array methods (map/filter/reduce)
-
Monitor bundle size (<5MB target for playables)
Review Severity Levels
🔴 Critical (Must Fix)
-
TypeScript strict mode disabled - Must enable "strict": true
-
Silent error handling - Must throw exceptions for errors
-
console.log in production code - Remove or wrap in CC_DEBUG
-
Missing access modifiers - All members must have modifiers
-
Using any type without justification - Define proper types
-
Inline comments instead of descriptive names - Rename and remove comments
-
Event listeners not cleaned up - Memory leak, must unregister
-
Missing required reference validation - Must throw in onLoad if null
-
Allocations in update() loop - Performance critical, must preallocate
-
No sprite atlas for multiple sprites - DrawCall explosion, must use atlas
-
Bundle size >5MB - Exceeds playable limit, must optimize
🟡 Important (Should Fix)
-
Missing readonly on @property fields - Should be readonly when not reassigned
-
Missing const for constants - Should use const instead of let
-
Manual loops instead of array methods - Should use map/filter/reduce
-
Missing optional chaining - Should use ?. for safe access
-
Missing nullish coalescing - Should use ?? for default values
-
Heavy logic in update() - Should throttle expensive operations
-
No object pooling for frequent allocations - Should implement pooling
-
Texture compression not enabled - Should enable for smaller bundle
-
DrawCall count >10 - Should optimize batching
🟢 Nice to Have (Suggestion)
-
Could use arrow function for callback
-
Could destructure for cleaner code
-
Could use type guard for type safety
-
Could improve naming for clarity
-
Could add interface for better typing
-
Could optimize algorithm for better performance
Detailed References
TypeScript Language Standards
-
Quality & Hygiene - Strict mode, ESLint, access modifiers, error handling
-
Modern TypeScript - Array methods, optional chaining, type guards, utility types
-
Performance - Update loop optimization, zero allocations, caching
Cocos Creator Framework
-
Component System - EC system, lifecycle methods, @property decorator
-
Event Patterns - EventDispatcher, Node events, subscription cleanup
-
Playable Optimization - DrawCall batching, sprite atlas, GPU skinning, resource pooling
-
Size Optimization - Bundle size reduction, texture compression, build optimization
Code Review
-
Architecture Review - Component violations, lifecycle errors, event leaks
-
Quality Review - TypeScript quality issues, access modifiers, error handling
-
Performance Review - Playable-specific performance problems, DrawCalls, allocations
Summary
This skill provides comprehensive Cocos Creator development standards for TheOne Studio's playable ads team:
-
TypeScript Excellence: Strict mode, modern patterns, type safety
-
Cocos Architecture: Component lifecycle, event patterns, resource management
-
Playable Performance: DrawCall batching, GPU skinning, <5MB bundles
-
Code Quality: Enforced quality, hygiene, and performance rules
Use the Quick Reference Guide above to navigate to the specific pattern you need.