@mclawnet/agent 0.6.19 → 0.6.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-RO47ET27.js → chunk-M2CDVPQF.js} +2 -2
- package/dist/{chunk-CBZIH6FY.js → chunk-PJ5M6Q36.js} +2 -2
- package/dist/chunk-PJ5M6Q36.js.map +1 -0
- package/dist/{chunk-MSDIRBXF.js → chunk-RIK7IXSW.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{linux-6AR7SXHW.js → linux-IHA4O633.js} +3 -3
- package/dist/{macos-BTP5JW3U.js → macos-G4VK2253.js} +8 -3
- package/dist/macos-G4VK2253.js.map +1 -0
- package/dist/service/index.js +5 -5
- package/dist/service/macos.d.ts.map +1 -1
- package/dist/start.js +2 -2
- package/dist/{windows-IQNSUMN6.js → windows-P6U3JLUZ.js} +3 -3
- package/package.json +4 -4
- package/skills/cocos-creator-3x-cn/SKILL.md +475 -0
- package/skills/cocos-creator-3x-cn/references/framework/asset-management.md +322 -0
- package/skills/cocos-creator-3x-cn/references/framework/component-system.md +348 -0
- package/skills/cocos-creator-3x-cn/references/framework/event-patterns.md +410 -0
- package/skills/cocos-creator-3x-cn/references/framework/playable-optimization.md +257 -0
- package/skills/cocos-creator-3x-cn/references/language/performance.md +363 -0
- package/skills/cocos-creator-3x-cn/references/language/quality-hygiene.md +307 -0
- package/skills/cocos-creator-3x-cn/references/review/architecture-review.md +183 -0
- package/skills/cocos-creator-3x-cn/references/review/quality-review.md +251 -0
- package/skills/cocos-performance-optimizer/SKILL.md +214 -0
- package/skills/game-development/2d-games/SKILL.md +129 -0
- package/skills/game-development/3d-games/SKILL.md +145 -0
- package/skills/game-development/SKILL.md +175 -0
- package/skills/game-development/game-art/SKILL.md +195 -0
- package/skills/game-development/game-audio/SKILL.md +200 -0
- package/skills/game-development/game-design/SKILL.md +139 -0
- package/skills/game-development/mobile-games/SKILL.md +118 -0
- package/skills/game-development/multiplayer/SKILL.md +142 -0
- package/skills/game-development/pc-games/SKILL.md +154 -0
- package/skills/game-development/vr-ar/SKILL.md +133 -0
- package/skills/game-development/web-games/SKILL.md +160 -0
- package/skills/game-engine/SKILL.md +140 -0
- package/skills/game-engine/assets/2d-maze-game.md +528 -0
- package/skills/game-engine/assets/2d-platform-game.md +1855 -0
- package/skills/game-engine/assets/gameBase-template-repo.md +310 -0
- package/skills/game-engine/assets/paddle-game-template.md +1528 -0
- package/skills/game-engine/assets/simple-2d-engine.md +507 -0
- package/skills/game-engine/references/3d-web-games.md +754 -0
- package/skills/game-engine/references/algorithms.md +843 -0
- package/skills/game-engine/references/basics.md +343 -0
- package/skills/game-engine/references/game-control-mechanisms.md +617 -0
- package/skills/game-engine/references/game-engine-core-principles.md +695 -0
- package/skills/game-engine/references/game-publishing.md +352 -0
- package/skills/game-engine/references/techniques.md +894 -0
- package/skills/game-engine/references/terminology.md +354 -0
- package/skills/game-engine/references/web-apis.md +1394 -0
- package/skills/theone-cocos-standards/SKILL.md +557 -0
- package/skills/theone-cocos-standards/references/framework/component-system.md +645 -0
- package/skills/theone-cocos-standards/references/framework/event-patterns.md +433 -0
- package/skills/theone-cocos-standards/references/framework/playable-optimization.md +429 -0
- package/skills/theone-cocos-standards/references/framework/size-optimization.md +308 -0
- package/skills/theone-cocos-standards/references/language/modern-typescript.md +658 -0
- package/skills/theone-cocos-standards/references/language/performance.md +580 -0
- package/skills/theone-cocos-standards/references/language/quality-hygiene.md +582 -0
- package/skills/theone-cocos-standards/references/review/architecture-review.md +250 -0
- package/skills/theone-cocos-standards/references/review/performance-review.md +288 -0
- package/skills/theone-cocos-standards/references/review/quality-review.md +239 -0
- package/dist/chunk-CBZIH6FY.js.map +0 -1
- package/dist/macos-BTP5JW3U.js.map +0 -1
- /package/dist/{chunk-RO47ET27.js.map → chunk-M2CDVPQF.js.map} +0 -0
- /package/dist/{chunk-MSDIRBXF.js.map → chunk-RIK7IXSW.js.map} +0 -0
- /package/dist/{linux-6AR7SXHW.js.map → linux-IHA4O633.js.map} +0 -0
- /package/dist/{windows-IQNSUMN6.js.map → windows-P6U3JLUZ.js.map} +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Cocos Creator Architecture Review
|
|
2
|
+
|
|
3
|
+
This review focuses on Cocos Creator-specific architectural issues including component lifecycle violations, event management problems, and performance issues specific to playable ads.
|
|
4
|
+
|
|
5
|
+
## Component Lifecycle Violations
|
|
6
|
+
|
|
7
|
+
### Accessing Components in onLoad
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// ❌ CRITICAL: Accessing other components in onLoad
|
|
11
|
+
@ccclass('BadLifecycle')
|
|
12
|
+
export class BadLifecycle extends Component {
|
|
13
|
+
@property(Node)
|
|
14
|
+
private playerNode: Node | null = null;
|
|
15
|
+
|
|
16
|
+
protected onLoad(): void {
|
|
17
|
+
// WRONG: Other components may not be loaded yet
|
|
18
|
+
const controller = this.playerNode!.getComponent(PlayerController);
|
|
19
|
+
controller.initialize(); // May be undefined!
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ✅ CORRECT: Access components in start()
|
|
24
|
+
@ccclass('GoodLifecycle')
|
|
25
|
+
export class GoodLifecycle extends Component {
|
|
26
|
+
@property(Node)
|
|
27
|
+
private readonly playerNode: Node | null = null;
|
|
28
|
+
|
|
29
|
+
private playerController!: PlayerController;
|
|
30
|
+
|
|
31
|
+
protected onLoad(): void {
|
|
32
|
+
if (!this.playerNode) {
|
|
33
|
+
throw new Error('GoodLifecycle: playerNode is required');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected start(): void {
|
|
38
|
+
const controller = this.playerNode!.getComponent(PlayerController);
|
|
39
|
+
if (!controller) {
|
|
40
|
+
throw new Error('PlayerController not found');
|
|
41
|
+
}
|
|
42
|
+
this.playerController = controller;
|
|
43
|
+
this.playerController.initialize();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Severity: 🔴 Critical
|
|
48
|
+
// Impact: Undefined behavior, crashes
|
|
49
|
+
// Fix: Move component access from onLoad() to start()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Event Listener Memory Leaks
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// ❌ CRITICAL: Not unregistering event listeners
|
|
56
|
+
@ccclass('EventLeakBad')
|
|
57
|
+
export class EventLeakBad extends Component {
|
|
58
|
+
protected onEnable(): void {
|
|
59
|
+
this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
|
|
60
|
+
EventManager.on(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// MISSING: onDisable() - memory leak!
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ✅ CORRECT: Always unregister in onDisable
|
|
67
|
+
@ccclass('EventLeakGood')
|
|
68
|
+
export class EventLeakGood extends Component {
|
|
69
|
+
protected onEnable(): void {
|
|
70
|
+
this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
|
|
71
|
+
EventManager.on(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
protected onDisable(): void {
|
|
75
|
+
this.node.off(Node.EventType.TOUCH_START, this.onTouchStart, this);
|
|
76
|
+
EventManager.off(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private onTouchStart(event: EventTouch): void {}
|
|
80
|
+
private onScoreChanged(data: ScoreChangedEvent): void {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Severity: 🔴 Critical
|
|
84
|
+
// Impact: Memory leaks, performance degradation
|
|
85
|
+
// Fix: Always implement onDisable() to unregister listeners
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Missing Required Reference Validation
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// ❌ CRITICAL: No validation of required references
|
|
92
|
+
@ccclass('NoValidation')
|
|
93
|
+
export class NoValidation extends Component {
|
|
94
|
+
@property(Node)
|
|
95
|
+
private targetNode: Node | null = null;
|
|
96
|
+
|
|
97
|
+
protected onLoad(): void {
|
|
98
|
+
this.targetNode!.setPosition(0, 0, 0); // Will crash if null
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ✅ CORRECT: Validate in onLoad
|
|
103
|
+
@ccclass('WithValidation')
|
|
104
|
+
export class WithValidation extends Component {
|
|
105
|
+
@property(Node)
|
|
106
|
+
private readonly targetNode: Node | null = null;
|
|
107
|
+
|
|
108
|
+
protected onLoad(): void {
|
|
109
|
+
if (!this.targetNode) {
|
|
110
|
+
throw new Error('WithValidation: targetNode is required');
|
|
111
|
+
}
|
|
112
|
+
this.targetNode.setPosition(0, 0, 0);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Severity: 🔴 Critical
|
|
117
|
+
// Impact: Runtime crashes with unhelpful errors
|
|
118
|
+
// Fix: Validate all required @property references in onLoad()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Resource Cleanup Violations
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// ❌ CRITICAL: Not releasing resources
|
|
125
|
+
@ccclass('ResourceLeakBad')
|
|
126
|
+
export class ResourceLeakBad extends Component {
|
|
127
|
+
private readonly loadedAssets: Map<string, Asset> = new Map();
|
|
128
|
+
|
|
129
|
+
protected onDestroy(): void {
|
|
130
|
+
// MISSING: decRef() and clear()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ✅ CORRECT: Complete cleanup
|
|
135
|
+
@ccclass('ResourceLeakGood')
|
|
136
|
+
export class ResourceLeakGood extends Component {
|
|
137
|
+
private readonly loadedAssets: Map<string, Asset> = new Map();
|
|
138
|
+
|
|
139
|
+
protected onDestroy(): void {
|
|
140
|
+
for (const [id, asset] of this.loadedAssets) {
|
|
141
|
+
asset.decRef();
|
|
142
|
+
}
|
|
143
|
+
this.loadedAssets.clear();
|
|
144
|
+
this.unscheduleAllCallbacks();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Severity: 🔴 Critical
|
|
149
|
+
// Impact: Memory leaks
|
|
150
|
+
// Fix: Release resources and clear collections in onDestroy()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Performance Violations (Playable-Specific)
|
|
154
|
+
|
|
155
|
+
### Allocations in Update Loop
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// ❌ CRITICAL: Allocating every frame
|
|
159
|
+
@ccclass('UpdateAllocationsBad')
|
|
160
|
+
export class UpdateAllocationsBad extends Component {
|
|
161
|
+
protected update(dt: number): void {
|
|
162
|
+
const pos = this.node.position.clone(); // 60 allocations/second
|
|
163
|
+
pos.y += 10 * dt;
|
|
164
|
+
this.node.setPosition(pos);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ✅ CORRECT: Preallocate and reuse
|
|
169
|
+
@ccclass('UpdateAllocationsGood')
|
|
170
|
+
export class UpdateAllocationsGood extends Component {
|
|
171
|
+
private readonly tempVec3: Vec3 = new Vec3();
|
|
172
|
+
|
|
173
|
+
protected update(dt: number): void {
|
|
174
|
+
this.node.getPosition(this.tempVec3);
|
|
175
|
+
this.tempVec3.y += 10 * dt;
|
|
176
|
+
this.node.setPosition(this.tempVec3);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Severity: 🔴 Critical
|
|
181
|
+
// Impact: Frame drops, GC pauses
|
|
182
|
+
// Fix: Preallocate objects, reuse in update
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Component Lookup in Update
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// ❌ IMPORTANT: getComponent in update
|
|
189
|
+
@ccclass('ComponentLookupBad')
|
|
190
|
+
export class ComponentLookupBad extends Component {
|
|
191
|
+
@property(Node)
|
|
192
|
+
private playerNode: Node | null = null;
|
|
193
|
+
|
|
194
|
+
protected update(dt: number): void {
|
|
195
|
+
const controller = this.playerNode!.getComponent(PlayerController); // Expensive!
|
|
196
|
+
controller?.update(dt);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ✅ CORRECT: Cache component reference
|
|
201
|
+
@ccclass('ComponentLookupGood')
|
|
202
|
+
export class ComponentLookupGood extends Component {
|
|
203
|
+
@property(Node)
|
|
204
|
+
private readonly playerNode: Node | null = null;
|
|
205
|
+
|
|
206
|
+
private playerController!: PlayerController;
|
|
207
|
+
|
|
208
|
+
protected start(): void {
|
|
209
|
+
if (!this.playerNode) {
|
|
210
|
+
throw new Error('playerNode is required');
|
|
211
|
+
}
|
|
212
|
+
const controller = this.playerNode.getComponent(PlayerController);
|
|
213
|
+
if (!controller) {
|
|
214
|
+
throw new Error('PlayerController not found');
|
|
215
|
+
}
|
|
216
|
+
this.playerController = controller;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
protected update(dt: number): void {
|
|
220
|
+
this.playerController.update(dt);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Severity: 🟡 Important
|
|
225
|
+
// Impact: Significant performance overhead
|
|
226
|
+
// Fix: Cache component references in start()
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Summary: Architecture Review Checklist
|
|
230
|
+
|
|
231
|
+
**🔴 Critical (Must Fix):**
|
|
232
|
+
- [ ] No component access in onLoad() (use start())
|
|
233
|
+
- [ ] All event listeners unregistered in onDisable()
|
|
234
|
+
- [ ] Required @property references validated in onLoad()
|
|
235
|
+
- [ ] Resources released in onDestroy()
|
|
236
|
+
- [ ] Zero allocations in update() loop
|
|
237
|
+
- [ ] readonly used for @property fields not reassigned
|
|
238
|
+
|
|
239
|
+
**🟡 Important (Should Fix):**
|
|
240
|
+
- [ ] Component references cached (not getComponent in update)
|
|
241
|
+
- [ ] Expensive operations throttled (every N frames)
|
|
242
|
+
- [ ] Node references cached (not find() in update)
|
|
243
|
+
- [ ] Arrays cleared with .length = 0 (not new array)
|
|
244
|
+
|
|
245
|
+
**🟢 Nice to Have:**
|
|
246
|
+
- [ ] Object pooling for frequent spawn/despawn
|
|
247
|
+
- [ ] WeakMap for auto-cleanup caches
|
|
248
|
+
- [ ] Disposable pattern for subscription management
|
|
249
|
+
|
|
250
|
+
**Always fix lifecycle and event cleanup issues - they cause crashes and memory leaks.**
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Playable Ads Performance Review
|
|
2
|
+
|
|
3
|
+
This review focuses on performance issues specific to playable ads including DrawCall optimization, bundle size, update loop performance, and resource management.
|
|
4
|
+
|
|
5
|
+
## DrawCall Explosion (Critical for Playables)
|
|
6
|
+
|
|
7
|
+
**Target: <10 DrawCalls for 60fps playables**
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// ❌ CRITICAL: Individual textures (multiple DrawCalls)
|
|
11
|
+
@ccclass('DrawCallBad')
|
|
12
|
+
export class DrawCallBad extends Component {
|
|
13
|
+
@property(SpriteFrame)
|
|
14
|
+
private sprite1: SpriteFrame | null = null; // DrawCall 1
|
|
15
|
+
|
|
16
|
+
@property(SpriteFrame)
|
|
17
|
+
private sprite2: SpriteFrame | null = null; // DrawCall 2
|
|
18
|
+
|
|
19
|
+
@property(SpriteFrame)
|
|
20
|
+
private sprite3: SpriteFrame | null = null; // DrawCall 3
|
|
21
|
+
|
|
22
|
+
// 10 sprites = 10 DrawCalls! (BAD)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ✅ CORRECT: Sprite atlas (single DrawCall)
|
|
26
|
+
@ccclass('DrawCallGood')
|
|
27
|
+
export class DrawCallGood extends Component {
|
|
28
|
+
@property(SpriteAtlas)
|
|
29
|
+
private readonly characterAtlas: SpriteAtlas | null = null; // 1 DrawCall for all
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Severity: 🔴 Critical
|
|
33
|
+
// Impact: Frame drops, poor performance
|
|
34
|
+
// Target: <10 DrawCalls total
|
|
35
|
+
// Fix: Use sprite atlases for all sprites
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Update Loop Allocations
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// ❌ CRITICAL: Allocating in update
|
|
42
|
+
@ccclass('UpdateAllocationsBad')
|
|
43
|
+
export class UpdateAllocationsBad extends Component {
|
|
44
|
+
protected update(dt: number): void {
|
|
45
|
+
// Creates new Vec3 every frame
|
|
46
|
+
const pos = this.node.position.clone(); // 60 allocations/second!
|
|
47
|
+
pos.y += 10 * dt;
|
|
48
|
+
this.node.setPosition(pos);
|
|
49
|
+
|
|
50
|
+
// Creates array every frame
|
|
51
|
+
const enemies = this.getAllEnemies().filter(e => e.active); // 60 arrays/second!
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ✅ CORRECT: Zero allocations
|
|
56
|
+
@ccclass('UpdateAllocationsGood')
|
|
57
|
+
export class UpdateAllocationsGood extends Component {
|
|
58
|
+
private readonly tempVec3: Vec3 = new Vec3();
|
|
59
|
+
private readonly activeEnemies: Enemy[] = [];
|
|
60
|
+
private cacheRDirty: boolean = true;
|
|
61
|
+
|
|
62
|
+
protected update(dt: number): void {
|
|
63
|
+
// Reuse preallocated vector
|
|
64
|
+
this.node.getPosition(this.tempVec3);
|
|
65
|
+
this.tempVec3.y += 10 * dt;
|
|
66
|
+
this.node.setPosition(this.tempVec3);
|
|
67
|
+
|
|
68
|
+
// Use cached array
|
|
69
|
+
const enemies = this.getActiveEnemies();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private getActiveEnemies(): Enemy[] {
|
|
73
|
+
if (this.cacheDirty) {
|
|
74
|
+
this.activeEnemies.length = 0;
|
|
75
|
+
// Rebuild cache
|
|
76
|
+
this.cacheDirty = false;
|
|
77
|
+
}
|
|
78
|
+
return this.activeEnemies;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Severity: 🔴 Critical
|
|
83
|
+
// Impact: Frame drops, GC pauses
|
|
84
|
+
// Fix: Preallocate objects, reuse in update
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## No Object Pooling
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// ❌ IMPORTANT: instantiate/destroy in gameplay
|
|
91
|
+
@ccclass('NoPoolingBad')
|
|
92
|
+
export class NoPoolingBad extends Component {
|
|
93
|
+
public shoot(): void {
|
|
94
|
+
const bullet = instantiate(this.bulletPrefab!); // Allocates
|
|
95
|
+
this.scheduleOnce(() => {
|
|
96
|
+
bullet.destroy(); // GC overhead
|
|
97
|
+
}, 2.0);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ✅ CORRECT: Object pooling
|
|
102
|
+
@ccclass('NoPoolingGood')
|
|
103
|
+
export class NoPoolingGood extends Component {
|
|
104
|
+
private readonly bulletPool: NodePool = new NodePool();
|
|
105
|
+
|
|
106
|
+
protected onLoad(): void {
|
|
107
|
+
// Prewarm pool
|
|
108
|
+
for (let i = 0; i < 20; i++) {
|
|
109
|
+
const bullet = instantiate(this.bulletPrefab!);
|
|
110
|
+
this.bulletPool.put(bullet);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public shoot(): void {
|
|
115
|
+
const bullet = this.bulletPool.get() ?? instantiate(this.bulletPrefab!);
|
|
116
|
+
this.scheduleOnce(() => {
|
|
117
|
+
this.bulletPool.put(bullet);
|
|
118
|
+
}, 2.0);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Severity: 🟡 Important
|
|
123
|
+
// Impact: Allocations, GC pauses
|
|
124
|
+
// Fix: Implement object pooling for frequent spawn/despawn
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Unthrottled Expensive Operations
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// ❌ IMPORTANT: Expensive operations every frame
|
|
131
|
+
@ccclass('UnthrottledBad')
|
|
132
|
+
export class UnthrottledBad extends Component {
|
|
133
|
+
protected update(dt: number): void {
|
|
134
|
+
this.recalculatePathfinding(); // A* every frame (60 times/second)!
|
|
135
|
+
this.updateComplexAI(); // Expensive every frame!
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ✅ CORRECT: Throttle expensive operations
|
|
140
|
+
@ccclass('UnthrottledGood')
|
|
141
|
+
export class UnthrottledGood extends Component {
|
|
142
|
+
private frameCount: number = 0;
|
|
143
|
+
|
|
144
|
+
protected update(dt: number): void {
|
|
145
|
+
this.frameCount++;
|
|
146
|
+
|
|
147
|
+
// Pathfinding once per second
|
|
148
|
+
if (this.frameCount % 60 === 0) {
|
|
149
|
+
this.recalculatePathfinding();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// AI 6 times per second
|
|
153
|
+
if (this.frameCount % 10 === 0) {
|
|
154
|
+
this.updateComplexAI();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Cheap operations every frame
|
|
158
|
+
this.moveTowardsTarget(dt);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Severity: 🟡 Important
|
|
163
|
+
// Impact: Poor performance, frame drops
|
|
164
|
+
// Fix: Throttle to every N frames (10-60)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Bundle Size >5MB
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// ❌ CRITICAL: Bundle exceeds playable limit
|
|
171
|
+
// Build output: 7.2MB (too large for most ad networks!)
|
|
172
|
+
|
|
173
|
+
// Common causes:
|
|
174
|
+
// 1. Uncompressed textures → Enable compression
|
|
175
|
+
// 2. Oversized textures → Reduce to 512x512 max
|
|
176
|
+
// 3. Uncompressed audio → Use MP3/OGG at 64-128kbps
|
|
177
|
+
// 4. Unused assets → Remove from project
|
|
178
|
+
// 5. No code minification → Enable in build settings
|
|
179
|
+
|
|
180
|
+
// ✅ CORRECT: Optimized to <5MB
|
|
181
|
+
// - Enable texture compression (Project Settings)
|
|
182
|
+
// - Use sprite atlases (combine textures)
|
|
183
|
+
// - Compress audio (64-128kbps)
|
|
184
|
+
// - Remove unused assets
|
|
185
|
+
// - Enable code minification (drop_console, dead_code)
|
|
186
|
+
|
|
187
|
+
// Severity: 🔴 Critical
|
|
188
|
+
// Impact: Playable rejected by ad networks
|
|
189
|
+
// Target: <5MB total bundle
|
|
190
|
+
// Fix: Apply size optimization techniques
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Loading Resources During Gameplay
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// ❌ IMPORTANT: Loading during gameplay
|
|
197
|
+
@ccclass('LoadingInGameplayBad')
|
|
198
|
+
export class LoadingInGameplayBad extends Component {
|
|
199
|
+
protected update(dt: number): void {
|
|
200
|
+
if (this.shouldSpawnEnemy()) {
|
|
201
|
+
// Loading causes frame drop!
|
|
202
|
+
resources.load('sprites/enemy', SpriteFrame, (err, sprite) => {
|
|
203
|
+
this.spawnEnemy(sprite);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ✅ CORRECT: Preload at startup
|
|
210
|
+
@ccclass('LoadingInGameplayGood')
|
|
211
|
+
export class LoadingInGameplayGood extends Component {
|
|
212
|
+
private enemySprite: SpriteFrame | null = null;
|
|
213
|
+
|
|
214
|
+
protected start(): void {
|
|
215
|
+
// Preload once
|
|
216
|
+
resources.load('sprites/enemy', SpriteFrame, (err, sprite) => {
|
|
217
|
+
if (!err) {
|
|
218
|
+
this.enemySprite = sprite;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
protected update(dt: number): void {
|
|
224
|
+
if (this.shouldSpawnEnemy() && this.enemySprite) {
|
|
225
|
+
this.spawnEnemy(this.enemySprite); // Instant, no loading
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Severity: 🟡 Important
|
|
231
|
+
// Impact: Frame drops during loading
|
|
232
|
+
// Fix: Preload all resources at startup
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## GPU Skinning Disabled
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// ❌ IMPORTANT: CPU skinning (slower)
|
|
239
|
+
@ccclass('CPUSkinningBad')
|
|
240
|
+
export class CPUSkinningBad extends Component {
|
|
241
|
+
@property(SkeletalAnimation)
|
|
242
|
+
private skeleton: SkeletalAnimation | null = null;
|
|
243
|
+
|
|
244
|
+
protected onLoad(): void {
|
|
245
|
+
// Using default CPU skinning (slower)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ✅ CORRECT: Enable GPU skinning
|
|
250
|
+
@ccclass('GPUSkinningGood')
|
|
251
|
+
export class GPUSkinningGood extends Component {
|
|
252
|
+
@property(SkeletalAnimation)
|
|
253
|
+
private readonly skeleton: SkeletalAnimation | null = null;
|
|
254
|
+
|
|
255
|
+
protected onLoad(): void {
|
|
256
|
+
if (this.skeleton) {
|
|
257
|
+
// GPU handles bone transformations (faster)
|
|
258
|
+
this.skeleton.useBakedAnimation = true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Severity: 🟢 Nice to Have
|
|
264
|
+
// Impact: Better performance for skeletal animations
|
|
265
|
+
// Fix: Enable useBakedAnimation for GPU skinning
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Summary: Performance Review Checklist
|
|
269
|
+
|
|
270
|
+
**🔴 Critical (Must Fix):**
|
|
271
|
+
- [ ] DrawCall count <10 (use sprite atlases)
|
|
272
|
+
- [ ] Zero allocations in update() loop
|
|
273
|
+
- [ ] Bundle size <5MB total
|
|
274
|
+
- [ ] No loading resources during gameplay
|
|
275
|
+
|
|
276
|
+
**🟡 Important (Should Fix):**
|
|
277
|
+
- [ ] Object pooling for bullets, effects, enemies
|
|
278
|
+
- [ ] Expensive operations throttled (every 10-60 frames)
|
|
279
|
+
- [ ] Component references cached (not getComponent in update)
|
|
280
|
+
- [ ] Node references cached (not find() in update)
|
|
281
|
+
|
|
282
|
+
**🟢 Nice to Have:**
|
|
283
|
+
- [ ] GPU skinning enabled (useBakedAnimation = true)
|
|
284
|
+
- [ ] Texture dimensions optimized (512x512 max)
|
|
285
|
+
- [ ] Audio compressed (64-128kbps)
|
|
286
|
+
- [ ] WeakMap for auto-cleanup caches
|
|
287
|
+
|
|
288
|
+
**Performance targets: 60fps, <10 DrawCalls, <5MB bundle for playable ads.**
|