@mclawnet/agent 0.6.20 → 0.6.22
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/cli.js +63 -0
- package/dist/__tests__/checkpoint.test.d.ts +2 -0
- package/dist/__tests__/checkpoint.test.d.ts.map +1 -0
- package/dist/__tests__/fs-handler-decode.test.d.ts +2 -0
- package/dist/__tests__/fs-handler-decode.test.d.ts.map +1 -0
- package/dist/__tests__/idle-sweeper.test.d.ts +2 -0
- package/dist/__tests__/idle-sweeper.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-config.test.d.ts +2 -0
- package/dist/__tests__/mcp-config.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-runtime-spawn.test.d.ts +2 -0
- package/dist/__tests__/schedule-runtime-spawn.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-runtime.test.d.ts +2 -0
- package/dist/__tests__/schedule-runtime.test.d.ts.map +1 -0
- package/dist/__tests__/session-limit.test.d.ts +2 -0
- package/dist/__tests__/session-limit.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-cli-client.test.d.ts +2 -0
- package/dist/__tests__/swarm-cli-client.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-control-dispatch.test.d.ts +2 -0
- package/dist/__tests__/swarm-control-dispatch.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-session-bridge.test.d.ts +2 -0
- package/dist/__tests__/swarm-session-bridge.test.d.ts.map +1 -0
- package/dist/backend-adapter.d.ts +43 -0
- package/dist/backend-adapter.d.ts.map +1 -1
- package/dist/checkpoint.d.ts +67 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/{chunk-RIK7IXSW.js → chunk-WJWCYGLQ.js} +1130 -147
- package/dist/chunk-WJWCYGLQ.js.map +1 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/fs-handler.d.ts.map +1 -1
- package/dist/hub-connection.d.ts +13 -0
- package/dist/hub-connection.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/schedule-runtime.d.ts +125 -0
- package/dist/schedule-runtime.d.ts.map +1 -0
- package/dist/session-manager.d.ts +102 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/skill-loader.d.ts +20 -0
- package/dist/skill-loader.d.ts.map +1 -1
- package/dist/start.d.ts +2 -0
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +1 -1
- package/dist/swarm-cli-client.d.ts +24 -0
- package/dist/swarm-cli-client.d.ts.map +1 -0
- package/dist/swarm-cli-client.js +83 -0
- package/dist/swarm-cli-client.js.map +1 -0
- package/dist/swarm-control-dispatch.d.ts +47 -0
- package/dist/swarm-control-dispatch.d.ts.map +1 -0
- package/dist/swarm-session-bridge.d.ts +22 -0
- package/dist/swarm-session-bridge.d.ts.map +1 -0
- package/package.json +6 -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-RIK7IXSW.js.map +0 -1
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
# Modern TypeScript Patterns
|
|
2
|
+
|
|
3
|
+
## Array Methods Over Loops
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { _decorator, Component, Node } from 'cc';
|
|
7
|
+
const { ccclass } = _decorator;
|
|
8
|
+
|
|
9
|
+
interface Enemy {
|
|
10
|
+
node: Node;
|
|
11
|
+
isActive: boolean;
|
|
12
|
+
health: number;
|
|
13
|
+
damage: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@ccclass('EnemyManager')
|
|
17
|
+
export class EnemyManager extends Component {
|
|
18
|
+
private readonly enemies: Enemy[] = [];
|
|
19
|
+
|
|
20
|
+
// ✅ EXCELLENT: Array methods for filtering
|
|
21
|
+
public getActiveEnemies(): Enemy[] {
|
|
22
|
+
return this.enemies.filter(enemy => enemy.isActive);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ✅ EXCELLENT: Array methods for mapping
|
|
26
|
+
public getEnemyPositions(): Vec3[] {
|
|
27
|
+
return this.enemies.map(enemy => enemy.node.position.clone());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ✅ EXCELLENT: Array methods for reduction
|
|
31
|
+
public getTotalDamage(): number {
|
|
32
|
+
return this.enemies.reduce((total, enemy) => total + enemy.damage, 0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ✅ EXCELLENT: Chaining array methods
|
|
36
|
+
public getActiveEnemyDamage(): number {
|
|
37
|
+
return this.enemies
|
|
38
|
+
.filter(enemy => enemy.isActive)
|
|
39
|
+
.reduce((total, enemy) => total + enemy.damage, 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ✅ EXCELLENT: find instead of manual loop
|
|
43
|
+
public findEnemyById(id: string): Enemy | undefined {
|
|
44
|
+
return this.enemies.find(enemy => enemy.node.uuid === id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ✅ EXCELLENT: some/every for existence checks
|
|
48
|
+
public hasActiveEnemies(): boolean {
|
|
49
|
+
return this.enemies.some(enemy => enemy.isActive);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public areAllEnemiesDead(): boolean {
|
|
53
|
+
return this.enemies.every(enemy => enemy.health <= 0);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ❌ BAD: Manual loops
|
|
58
|
+
public getActiveEnemies(): Enemy[] {
|
|
59
|
+
const active: Enemy[] = [];
|
|
60
|
+
for (let i = 0; i < this.enemies.length; i++) {
|
|
61
|
+
if (this.enemies[i].isActive) {
|
|
62
|
+
active.push(this.enemies[i]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return active;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ❌ BAD: Manual accumulation
|
|
69
|
+
public getTotalDamage(): number {
|
|
70
|
+
let total = 0;
|
|
71
|
+
for (const enemy of this.enemies) {
|
|
72
|
+
total += enemy.damage;
|
|
73
|
+
}
|
|
74
|
+
return total;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Arrow Functions and Callbacks
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { _decorator, Component, Node, EventTouch } from 'cc';
|
|
82
|
+
const { ccclass } = _decorator;
|
|
83
|
+
|
|
84
|
+
@ccclass('InputHandler')
|
|
85
|
+
export class InputHandler extends Component {
|
|
86
|
+
private readonly buttons: Node[] = [];
|
|
87
|
+
|
|
88
|
+
// ✅ EXCELLENT: Arrow functions for callbacks
|
|
89
|
+
protected onEnable(): void {
|
|
90
|
+
this.buttons.forEach(button => {
|
|
91
|
+
button.on(Node.EventType.TOUCH_START, this.onButtonClick, this);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected onDisable(): void {
|
|
96
|
+
this.buttons.forEach(button => {
|
|
97
|
+
button.off(Node.EventType.TOUCH_START, this.onButtonClick, this);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ✅ GOOD: Arrow function preserves this context
|
|
102
|
+
private readonly onButtonClick = (event: EventTouch): void => {
|
|
103
|
+
const button = event.target as Node;
|
|
104
|
+
this.handleButtonClick(button);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// ✅ GOOD: Arrow function for event handling
|
|
108
|
+
private setupAsyncOperation(): void {
|
|
109
|
+
setTimeout(() => {
|
|
110
|
+
this.processData();
|
|
111
|
+
}, 1000);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ✅ GOOD: Arrow function in Promise chains
|
|
115
|
+
private async loadData(): Promise<void> {
|
|
116
|
+
fetch('data.json')
|
|
117
|
+
.then(response => response.json())
|
|
118
|
+
.then(data => this.processData(data))
|
|
119
|
+
.catch(error => this.handleError(error));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ❌ BAD: Function expression loses this context
|
|
124
|
+
protected onEnable(): void {
|
|
125
|
+
this.buttons.forEach(function(button) {
|
|
126
|
+
// 'this' is undefined or wrong context
|
|
127
|
+
button.on(Node.EventType.TOUCH_START, this.onButtonClick, this);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ❌ BAD: Verbose function syntax
|
|
132
|
+
private setupAsyncOperation(): void {
|
|
133
|
+
const self = this;
|
|
134
|
+
setTimeout(function() {
|
|
135
|
+
self.processData();
|
|
136
|
+
}, 1000);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Destructuring
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { _decorator, Component, Node, Vec3 } from 'cc';
|
|
144
|
+
const { ccclass, property } = _decorator;
|
|
145
|
+
|
|
146
|
+
interface PlayerData {
|
|
147
|
+
id: string;
|
|
148
|
+
name: string;
|
|
149
|
+
level: number;
|
|
150
|
+
health: number;
|
|
151
|
+
position: { x: number; y: number; z: number };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@ccclass('PlayerController')
|
|
155
|
+
export class PlayerController extends Component {
|
|
156
|
+
// ✅ EXCELLENT: Destructuring in parameters
|
|
157
|
+
public updatePlayer({ id, name, level, health, position }: PlayerData): void {
|
|
158
|
+
console.log(`Updating ${name} (${id}) at level ${level}`);
|
|
159
|
+
|
|
160
|
+
// ✅ EXCELLENT: Nested destructuring
|
|
161
|
+
const { x, y, z } = position;
|
|
162
|
+
this.node.setPosition(x, y, z);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ✅ EXCELLENT: Destructuring with defaults
|
|
166
|
+
public loadConfig({ speed = 100, jumpHeight = 50, maxHealth = 100 } = {}): void {
|
|
167
|
+
this.speed = speed;
|
|
168
|
+
this.jumpHeight = jumpHeight;
|
|
169
|
+
this.maxHealth = maxHealth;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ✅ EXCELLENT: Array destructuring
|
|
173
|
+
public getPlayerPosition(): Vec3 {
|
|
174
|
+
const [x, y, z] = [this.node.position.x, this.node.position.y, this.node.position.z];
|
|
175
|
+
return new Vec3(x, y, z);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ✅ EXCELLENT: Rest operator with destructuring
|
|
179
|
+
public handleInput({ type, ...eventData }: InputEvent): void {
|
|
180
|
+
switch (type) {
|
|
181
|
+
case 'touch':
|
|
182
|
+
this.handleTouch(eventData);
|
|
183
|
+
break;
|
|
184
|
+
case 'key':
|
|
185
|
+
this.handleKey(eventData);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ❌ BAD: No destructuring
|
|
192
|
+
public updatePlayer(playerData: PlayerData): void {
|
|
193
|
+
console.log(`Updating ${playerData.name} (${playerData.id}) at level ${playerData.level}`);
|
|
194
|
+
this.node.setPosition(playerData.position.x, playerData.position.y, playerData.position.z);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ❌ BAD: Verbose property access
|
|
198
|
+
public loadConfig(config: Config): void {
|
|
199
|
+
this.speed = config.speed !== undefined ? config.speed : 100;
|
|
200
|
+
this.jumpHeight = config.jumpHeight !== undefined ? config.jumpHeight : 50;
|
|
201
|
+
this.maxHealth = config.maxHealth !== undefined ? config.maxHealth : 100;
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Spread Operator
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { _decorator, Component } from 'cc';
|
|
209
|
+
const { ccclass } = _decorator;
|
|
210
|
+
|
|
211
|
+
interface GameConfig {
|
|
212
|
+
playerName: string;
|
|
213
|
+
difficulty: string;
|
|
214
|
+
soundEnabled: boolean;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@ccclass('GameManager')
|
|
218
|
+
export class GameManager extends Component {
|
|
219
|
+
private readonly defaultConfig: GameConfig = {
|
|
220
|
+
playerName: 'Player',
|
|
221
|
+
difficulty: 'normal',
|
|
222
|
+
soundEnabled: true,
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// ✅ EXCELLENT: Spread for object merging
|
|
226
|
+
public createConfig(overrides: Partial<GameConfig>): GameConfig {
|
|
227
|
+
return { ...this.defaultConfig, ...overrides };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ✅ EXCELLENT: Spread for array concatenation
|
|
231
|
+
private readonly baseEnemies: string[] = ['goblin', 'orc'];
|
|
232
|
+
private readonly bossEnemies: string[] = ['dragon', 'demon'];
|
|
233
|
+
|
|
234
|
+
public getAllEnemies(): string[] {
|
|
235
|
+
return [...this.baseEnemies, ...this.bossEnemies];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ✅ EXCELLENT: Spread for array cloning
|
|
239
|
+
public cloneEnemyList(): string[] {
|
|
240
|
+
return [...this.baseEnemies];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ✅ EXCELLENT: Spread in function calls
|
|
244
|
+
public calculateMaxValue(...values: number[]): number {
|
|
245
|
+
return Math.max(...values);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ✅ EXCELLENT: Spread for immutable updates
|
|
249
|
+
public addEnemy(enemy: string): void {
|
|
250
|
+
this.baseEnemies = [...this.baseEnemies, enemy];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ❌ BAD: Manual merging
|
|
255
|
+
public createConfig(overrides: Partial<GameConfig>): GameConfig {
|
|
256
|
+
const config: GameConfig = {
|
|
257
|
+
playerName: overrides.playerName ?? this.defaultConfig.playerName,
|
|
258
|
+
difficulty: overrides.difficulty ?? this.defaultConfig.difficulty,
|
|
259
|
+
soundEnabled: overrides.soundEnabled ?? this.defaultConfig.soundEnabled,
|
|
260
|
+
};
|
|
261
|
+
return config;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ❌ BAD: Manual concatenation
|
|
265
|
+
public getAllEnemies(): string[] {
|
|
266
|
+
const enemies: string[] = [];
|
|
267
|
+
for (const enemy of this.baseEnemies) {
|
|
268
|
+
enemies.push(enemy);
|
|
269
|
+
}
|
|
270
|
+
for (const enemy of this.bossEnemies) {
|
|
271
|
+
enemies.push(enemy);
|
|
272
|
+
}
|
|
273
|
+
return enemies;
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Optional Chaining (?.)
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { _decorator, Component, Node } from 'cc';
|
|
281
|
+
const { ccclass, property } = _decorator;
|
|
282
|
+
|
|
283
|
+
interface Player {
|
|
284
|
+
name: string;
|
|
285
|
+
stats?: {
|
|
286
|
+
health?: number;
|
|
287
|
+
level?: number;
|
|
288
|
+
};
|
|
289
|
+
inventory?: {
|
|
290
|
+
items?: Item[];
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@ccclass('PlayerManager')
|
|
295
|
+
export class PlayerManager extends Component {
|
|
296
|
+
@property(Node)
|
|
297
|
+
private readonly playerNode: Node | null = null;
|
|
298
|
+
|
|
299
|
+
// ✅ EXCELLENT: Optional chaining for safe access
|
|
300
|
+
public getPlayerName(): string | undefined {
|
|
301
|
+
return this.playerNode?.name;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ✅ EXCELLENT: Deep optional chaining
|
|
305
|
+
public getPlayerHealth(player: Player): number | undefined {
|
|
306
|
+
return player?.stats?.health;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ✅ EXCELLENT: Optional chaining with arrays
|
|
310
|
+
public getFirstItem(player: Player): Item | undefined {
|
|
311
|
+
return player?.inventory?.items?.[0];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ✅ EXCELLENT: Optional chaining with methods
|
|
315
|
+
public getComponentName(): string | undefined {
|
|
316
|
+
return this.playerNode?.getComponent(PlayerController)?.getName?.();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ✅ EXCELLENT: Combining with nullish coalescing
|
|
320
|
+
public getDisplayName(): string {
|
|
321
|
+
return this.playerNode?.name ?? 'Unknown Player';
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ❌ BAD: Manual null checking
|
|
326
|
+
public getPlayerName(): string | undefined {
|
|
327
|
+
if (this.playerNode !== null && this.playerNode !== undefined) {
|
|
328
|
+
return this.playerNode.name;
|
|
329
|
+
}
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ❌ BAD: Nested null checks
|
|
334
|
+
public getPlayerHealth(player: Player): number | undefined {
|
|
335
|
+
if (player) {
|
|
336
|
+
if (player.stats) {
|
|
337
|
+
if (player.stats.health !== undefined) {
|
|
338
|
+
return player.stats.health;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Nullish Coalescing (??)
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import { _decorator, Component } from 'cc';
|
|
350
|
+
const { ccclass } = _decorator;
|
|
351
|
+
|
|
352
|
+
interface GameConfig {
|
|
353
|
+
playerName?: string;
|
|
354
|
+
maxHealth?: number;
|
|
355
|
+
soundVolume?: number;
|
|
356
|
+
enableTutorial?: boolean;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@ccclass('ConfigManager')
|
|
360
|
+
export class ConfigManager extends Component {
|
|
361
|
+
// ✅ EXCELLENT: Nullish coalescing for defaults
|
|
362
|
+
public loadConfig(config: GameConfig): void {
|
|
363
|
+
const playerName = config.playerName ?? 'Player';
|
|
364
|
+
const maxHealth = config.maxHealth ?? 100;
|
|
365
|
+
const soundVolume = config.soundVolume ?? 0.5;
|
|
366
|
+
const enableTutorial = config.enableTutorial ?? true;
|
|
367
|
+
|
|
368
|
+
console.log({ playerName, maxHealth, soundVolume, enableTutorial });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ✅ EXCELLENT: Nullish coalescing preserves falsy values
|
|
372
|
+
public getVolume(volume?: number): number {
|
|
373
|
+
// Returns 0 if volume is 0 (not using || which would return 1)
|
|
374
|
+
return volume ?? 1;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ✅ EXCELLENT: Chaining nullish coalescing
|
|
378
|
+
public getPlayerName(primaryName?: string, secondaryName?: string): string {
|
|
379
|
+
return primaryName ?? secondaryName ?? 'Unknown';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ✅ EXCELLENT: Nullish coalescing with optional chaining
|
|
383
|
+
public getHealthDisplay(player?: Player): string {
|
|
384
|
+
const health = player?.stats?.health ?? 0;
|
|
385
|
+
return `Health: ${health}`;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ❌ BAD: Using || operator (treats 0, '', false as null)
|
|
390
|
+
public getVolume(volume?: number): number {
|
|
391
|
+
return volume || 1; // Returns 1 even if volume is 0
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ❌ BAD: Manual null/undefined checks
|
|
395
|
+
public loadConfig(config: GameConfig): void {
|
|
396
|
+
const playerName = config.playerName !== null && config.playerName !== undefined
|
|
397
|
+
? config.playerName
|
|
398
|
+
: 'Player';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ❌ BAD: Verbose ternary
|
|
402
|
+
public getPlayerName(name?: string): string {
|
|
403
|
+
return name !== undefined && name !== null ? name : 'Unknown';
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Type Guards
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
import { _decorator, Component, Node } from 'cc';
|
|
411
|
+
const { ccclass } = _decorator;
|
|
412
|
+
|
|
413
|
+
// ✅ EXCELLENT: Type guard for interface
|
|
414
|
+
interface Player {
|
|
415
|
+
type: 'player';
|
|
416
|
+
health: number;
|
|
417
|
+
level: number;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
interface Enemy {
|
|
421
|
+
type: 'enemy';
|
|
422
|
+
health: number;
|
|
423
|
+
damage: number;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
type Entity = Player | Enemy;
|
|
427
|
+
|
|
428
|
+
function isPlayer(entity: Entity): entity is Player {
|
|
429
|
+
return entity.type === 'player';
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function isEnemy(entity: Entity): entity is Enemy {
|
|
433
|
+
return entity.type === 'enemy';
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
@ccclass('CombatManager')
|
|
437
|
+
export class CombatManager extends Component {
|
|
438
|
+
public handleEntity(entity: Entity): void {
|
|
439
|
+
if (isPlayer(entity)) {
|
|
440
|
+
// TypeScript knows entity is Player
|
|
441
|
+
console.log(`Player level: ${entity.level}`);
|
|
442
|
+
} else if (isEnemy(entity)) {
|
|
443
|
+
// TypeScript knows entity is Enemy
|
|
444
|
+
console.log(`Enemy damage: ${entity.damage}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ✅ EXCELLENT: Type guard for null/undefined
|
|
449
|
+
private isValidNode(node: Node | null | undefined): node is Node {
|
|
450
|
+
return node !== null && node !== undefined;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
public processNode(node: Node | null): void {
|
|
454
|
+
if (this.isValidNode(node)) {
|
|
455
|
+
// TypeScript knows node is Node (not null)
|
|
456
|
+
node.setPosition(0, 0, 0);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ✅ EXCELLENT: Type guard for component
|
|
461
|
+
private hasPlayerController(node: Node): node is Node & { getComponent(PlayerController): PlayerController } {
|
|
462
|
+
return node.getComponent(PlayerController) !== null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
public updatePlayer(node: Node): void {
|
|
466
|
+
if (this.hasPlayerController(node)) {
|
|
467
|
+
// TypeScript knows component exists
|
|
468
|
+
const controller = node.getComponent(PlayerController)!;
|
|
469
|
+
controller.update();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ❌ BAD: No type guards, type assertions everywhere
|
|
475
|
+
public handleEntity(entity: Entity): void {
|
|
476
|
+
if (entity.type === 'player') {
|
|
477
|
+
console.log(`Player level: ${(entity as Player).level}`); // Type assertion
|
|
478
|
+
} else {
|
|
479
|
+
console.log(`Enemy damage: ${(entity as Enemy).damage}`); // Type assertion
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
## Utility Types
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
import { _decorator, Component } from 'cc';
|
|
488
|
+
const { ccclass } = _decorator;
|
|
489
|
+
|
|
490
|
+
interface GameConfig {
|
|
491
|
+
playerName: string;
|
|
492
|
+
maxHealth: number;
|
|
493
|
+
difficulty: string;
|
|
494
|
+
soundEnabled: boolean;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
@ccclass('ConfigManager')
|
|
498
|
+
export class ConfigManager extends Component {
|
|
499
|
+
// ✅ EXCELLENT: Partial for optional properties
|
|
500
|
+
public updateConfig(updates: Partial<GameConfig>): void {
|
|
501
|
+
// All properties are optional
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ✅ EXCELLENT: Required for mandatory properties
|
|
505
|
+
public validateConfig(config: Required<GameConfig>): void {
|
|
506
|
+
// All properties are required
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// ✅ EXCELLENT: Readonly for immutable objects
|
|
510
|
+
private readonly defaultConfig: Readonly<GameConfig> = {
|
|
511
|
+
playerName: 'Player',
|
|
512
|
+
maxHealth: 100,
|
|
513
|
+
difficulty: 'normal',
|
|
514
|
+
soundEnabled: true,
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// ✅ EXCELLENT: Pick for selecting properties
|
|
518
|
+
public getDisplayInfo(config: GameConfig): Pick<GameConfig, 'playerName' | 'difficulty'> {
|
|
519
|
+
return {
|
|
520
|
+
playerName: config.playerName,
|
|
521
|
+
difficulty: config.difficulty,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ✅ EXCELLENT: Omit for excluding properties
|
|
526
|
+
public getPublicConfig(config: GameConfig): Omit<GameConfig, 'soundEnabled'> {
|
|
527
|
+
const { soundEnabled, ...publicConfig } = config;
|
|
528
|
+
return publicConfig;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ✅ EXCELLENT: Record for key-value mappings
|
|
532
|
+
private readonly difficultyMultipliers: Record<string, number> = {
|
|
533
|
+
easy: 0.5,
|
|
534
|
+
normal: 1.0,
|
|
535
|
+
hard: 1.5,
|
|
536
|
+
expert: 2.0,
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// ✅ EXCELLENT: ReturnType for function return types
|
|
540
|
+
private createPlayer(): { name: string; level: number } {
|
|
541
|
+
return { name: 'Player', level: 1 };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
type PlayerType = ReturnType<typeof this.createPlayer>;
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
## Async/Await Patterns
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
import { _decorator, Component } from 'cc';
|
|
552
|
+
const { ccclass } = _decorator;
|
|
553
|
+
|
|
554
|
+
@ccclass('DataManager')
|
|
555
|
+
export class DataManager extends Component {
|
|
556
|
+
// ✅ EXCELLENT: Async/await for sequential operations
|
|
557
|
+
public async loadGameData(): Promise<void> {
|
|
558
|
+
try {
|
|
559
|
+
const playerData = await this.fetchPlayerData();
|
|
560
|
+
const levelData = await this.fetchLevelData(playerData.currentLevel);
|
|
561
|
+
await this.initializeGame(playerData, levelData);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
console.error('Failed to load game data:', error);
|
|
564
|
+
throw error;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ✅ EXCELLENT: Promise.all for parallel operations
|
|
569
|
+
public async loadAllData(): Promise<void> {
|
|
570
|
+
try {
|
|
571
|
+
const [playerData, configData, assetsData] = await Promise.all([
|
|
572
|
+
this.fetchPlayerData(),
|
|
573
|
+
this.fetchConfigData(),
|
|
574
|
+
this.fetchAssetsData(),
|
|
575
|
+
]);
|
|
576
|
+
|
|
577
|
+
this.initializeWithData(playerData, configData, assetsData);
|
|
578
|
+
} catch (error) {
|
|
579
|
+
console.error('Failed to load data:', error);
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// ✅ EXCELLENT: Promise.allSettled for partial failures
|
|
585
|
+
public async loadDataWithFallback(): Promise<void> {
|
|
586
|
+
const results = await Promise.allSettled([
|
|
587
|
+
this.fetchPlayerData(),
|
|
588
|
+
this.fetchConfigData(),
|
|
589
|
+
this.fetchAssetsData(),
|
|
590
|
+
]);
|
|
591
|
+
|
|
592
|
+
results.forEach((result, index) => {
|
|
593
|
+
if (result.status === 'fulfilled') {
|
|
594
|
+
console.log(`Data ${index} loaded:`, result.value);
|
|
595
|
+
} else {
|
|
596
|
+
console.error(`Data ${index} failed:`, result.reason);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// ✅ EXCELLENT: Error handling with async/await
|
|
602
|
+
public async savePlayerData(data: PlayerData): Promise<boolean> {
|
|
603
|
+
try {
|
|
604
|
+
await this.validateData(data);
|
|
605
|
+
await this.uploadData(data);
|
|
606
|
+
return true;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
if (error instanceof ValidationError) {
|
|
609
|
+
console.error('Invalid data:', error.message);
|
|
610
|
+
} else if (error instanceof NetworkError) {
|
|
611
|
+
console.error('Network error:', error.message);
|
|
612
|
+
} else {
|
|
613
|
+
console.error('Unknown error:', error);
|
|
614
|
+
}
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
private async fetchPlayerData(): Promise<PlayerData> {
|
|
620
|
+
// Implementation
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
private async fetchLevelData(level: number): Promise<LevelData> {
|
|
624
|
+
// Implementation
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// ❌ BAD: Promise chains (callback hell)
|
|
629
|
+
public loadGameData(): void {
|
|
630
|
+
this.fetchPlayerData()
|
|
631
|
+
.then(playerData => {
|
|
632
|
+
return this.fetchLevelData(playerData.currentLevel);
|
|
633
|
+
})
|
|
634
|
+
.then(levelData => {
|
|
635
|
+
return this.initializeGame(playerData, levelData); // playerData not in scope!
|
|
636
|
+
})
|
|
637
|
+
.catch(error => {
|
|
638
|
+
console.error('Failed:', error);
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
## Summary: Modern TypeScript Checklist
|
|
644
|
+
|
|
645
|
+
**Use these patterns for cleaner, more maintainable code:**
|
|
646
|
+
|
|
647
|
+
- [ ] Array methods (map/filter/reduce) instead of manual loops
|
|
648
|
+
- [ ] Arrow functions for callbacks and event handlers
|
|
649
|
+
- [ ] Destructuring for cleaner parameter handling
|
|
650
|
+
- [ ] Spread operator for object/array operations
|
|
651
|
+
- [ ] Optional chaining (?.) for safe property access
|
|
652
|
+
- [ ] Nullish coalescing (??) for default values
|
|
653
|
+
- [ ] Type guards for type-safe narrowing
|
|
654
|
+
- [ ] Utility types (Partial, Required, Readonly, Pick, Omit, Record)
|
|
655
|
+
- [ ] Async/await for asynchronous operations
|
|
656
|
+
- [ ] Promise.all/allSettled for parallel operations
|
|
657
|
+
|
|
658
|
+
**Modern TypeScript makes code more concise, readable, and type-safe.**
|