@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,582 @@
|
|
|
1
|
+
# TypeScript Quality & Code Hygiene
|
|
2
|
+
|
|
3
|
+
## Enable TypeScript Strict Mode
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// ✅ GOOD: Enable strict mode in tsconfig.json
|
|
7
|
+
{
|
|
8
|
+
"compilerOptions": {
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noImplicitAny": true,
|
|
11
|
+
"strictNullChecks": true,
|
|
12
|
+
"strictFunctionTypes": true,
|
|
13
|
+
"strictBindCallApply": true,
|
|
14
|
+
"strictPropertyInitialization": true,
|
|
15
|
+
"noImplicitThis": true,
|
|
16
|
+
"alwaysStrict": true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Declare nullable explicitly
|
|
21
|
+
public playerName: string | null = null; // Can be null
|
|
22
|
+
public requiredName: string = ''; // Never null
|
|
23
|
+
|
|
24
|
+
// ❌ BAD: Ignoring nullability
|
|
25
|
+
public playerName: string; // Uninitialized, can be undefined
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Use Access Modifiers (public/private/protected)
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// ✅ GOOD: Explicit access modifiers
|
|
32
|
+
import { _decorator, Component, Node } from 'cc';
|
|
33
|
+
const { ccclass, property } = _decorator;
|
|
34
|
+
|
|
35
|
+
@ccclass('GameService')
|
|
36
|
+
export class GameService extends Component {
|
|
37
|
+
// Private implementation details
|
|
38
|
+
private readonly playerNodes: Node[] = [];
|
|
39
|
+
private currentLevel: number = 1;
|
|
40
|
+
|
|
41
|
+
// Protected for subclass access
|
|
42
|
+
protected readonly maxHealth: number = 100;
|
|
43
|
+
|
|
44
|
+
// Public API only when necessary
|
|
45
|
+
public getCurrentLevel(): number {
|
|
46
|
+
return this.currentLevel;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Private helper methods
|
|
50
|
+
private loadGameData(): void {
|
|
51
|
+
// Implementation
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ❌ BAD: No access modifiers (implicitly public)
|
|
56
|
+
@ccclass('GameService')
|
|
57
|
+
export class GameService extends Component {
|
|
58
|
+
playerNodes: Node[] = []; // Implicitly public
|
|
59
|
+
currentLevel: number = 1; // Implicitly public
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Enable ESLint with TypeScript Support
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
// ✅ GOOD: .eslintrc.json configuration
|
|
67
|
+
{
|
|
68
|
+
"parser": "@typescript-eslint/parser",
|
|
69
|
+
"extends": [
|
|
70
|
+
"eslint:recommended",
|
|
71
|
+
"plugin:@typescript-eslint/recommended"
|
|
72
|
+
],
|
|
73
|
+
"plugins": ["@typescript-eslint"],
|
|
74
|
+
"rules": {
|
|
75
|
+
"@typescript-eslint/explicit-function-return-type": "error",
|
|
76
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
77
|
+
"@typescript-eslint/no-unused-vars": "error",
|
|
78
|
+
"@typescript-eslint/explicit-member-accessibility": "error"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Throw Exceptions for Errors
|
|
84
|
+
|
|
85
|
+
**Critical Rule**: Throw exceptions instead of silent failures or returning undefined.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { _decorator, Component, Node } from 'cc';
|
|
89
|
+
const { ccclass, property } = _decorator;
|
|
90
|
+
|
|
91
|
+
@ccclass('PlayerService')
|
|
92
|
+
export class PlayerService extends Component {
|
|
93
|
+
@property(Node)
|
|
94
|
+
private readonly playerNode: Node | null = null;
|
|
95
|
+
|
|
96
|
+
// ✅ EXCELLENT: Throw exception for errors
|
|
97
|
+
protected onLoad(): void {
|
|
98
|
+
if (!this.playerNode) {
|
|
99
|
+
throw new Error('PlayerService: playerNode is required');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public getPlayer(id: string): Player {
|
|
104
|
+
const player = this.players.get(id);
|
|
105
|
+
if (!player) {
|
|
106
|
+
// Throw exception, don't return undefined
|
|
107
|
+
throw new Error(`Player not found: ${id}`);
|
|
108
|
+
}
|
|
109
|
+
return player;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public loadLevel(levelId: number): void {
|
|
113
|
+
if (levelId < 1 || levelId > 100) {
|
|
114
|
+
throw new RangeError(`Invalid level ID: ${levelId}. Must be 1-100.`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const levelData = this.loadLevelData(levelId);
|
|
118
|
+
if (!levelData) {
|
|
119
|
+
throw new Error(`Failed to load level data for level ${levelId}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.initializeLevel(levelData);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ❌ WRONG: Silent failure
|
|
127
|
+
public getPlayer(id: string): Player | undefined {
|
|
128
|
+
const player = this.players.get(id);
|
|
129
|
+
// Returning undefined - caller doesn't know why it failed
|
|
130
|
+
return player;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ❌ WRONG: Logging error instead of throwing
|
|
134
|
+
public loadLevel(levelId: number): void {
|
|
135
|
+
if (levelId < 1) {
|
|
136
|
+
console.error('Invalid level ID'); // Don't just log
|
|
137
|
+
return; // Silent failure
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Logging: console.log for Development Only
|
|
143
|
+
|
|
144
|
+
**Logging Guidelines:**
|
|
145
|
+
- **console.log**: Use ONLY for development debugging
|
|
146
|
+
- **Remove in production**: Wrap in `CC_DEBUG` or remove entirely
|
|
147
|
+
- **Performance impact**: console.log can slow down playable ads
|
|
148
|
+
- **Bundle size**: Logging strings increase bundle size
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { _decorator, Component } from 'cc';
|
|
152
|
+
const { ccclass } = _decorator;
|
|
153
|
+
|
|
154
|
+
@ccclass('GameManager')
|
|
155
|
+
export class GameManager extends Component {
|
|
156
|
+
private currentScore: number = 0;
|
|
157
|
+
|
|
158
|
+
// ✅ EXCELLENT: Conditional logging for development
|
|
159
|
+
protected onLoad(): void {
|
|
160
|
+
if (CC_DEBUG) {
|
|
161
|
+
console.log('GameManager initialized');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public addScore(points: number): void {
|
|
166
|
+
this.currentScore += points;
|
|
167
|
+
|
|
168
|
+
// ✅ GOOD: Development-only debug logging
|
|
169
|
+
if (CC_DEBUG) {
|
|
170
|
+
console.log(`Score updated: ${this.currentScore}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private loadGameData(): void {
|
|
175
|
+
try {
|
|
176
|
+
const data = this.fetchData();
|
|
177
|
+
this.processData(data);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
// ✅ GOOD: Log errors in development
|
|
180
|
+
if (CC_DEBUG) {
|
|
181
|
+
console.error('Failed to load game data:', error);
|
|
182
|
+
}
|
|
183
|
+
// Always throw for caller to handle
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ❌ WRONG: Unconditional console.log in production
|
|
190
|
+
public addScore(points: number): void {
|
|
191
|
+
console.log(`Adding ${points} points`); // Will be in production build
|
|
192
|
+
this.currentScore += points;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ❌ WRONG: Verbose logging everywhere
|
|
196
|
+
public update(dt: number): void {
|
|
197
|
+
console.log('Update called'); // Called every frame!
|
|
198
|
+
console.log(`Delta time: ${dt}`); // Performance impact
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ✅ BETTER: Remove logs in production or use build-time removal
|
|
202
|
+
// Configure build process to strip console.log in production builds
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Production Build Configuration:**
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
// Build configuration to remove console.log in production
|
|
209
|
+
// rollup.config.js or webpack.config.js
|
|
210
|
+
export default {
|
|
211
|
+
plugins: [
|
|
212
|
+
// Remove console statements in production
|
|
213
|
+
terser({
|
|
214
|
+
compress: {
|
|
215
|
+
drop_console: true, // Remove all console.* calls
|
|
216
|
+
pure_funcs: ['console.log', 'console.debug'], // Remove specific calls
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
]
|
|
220
|
+
};
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Use readonly for Immutable Fields
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { _decorator, Component, Node } from 'cc';
|
|
227
|
+
const { ccclass, property } = _decorator;
|
|
228
|
+
|
|
229
|
+
@ccclass('PlayerController')
|
|
230
|
+
export class PlayerController extends Component {
|
|
231
|
+
// ✅ GOOD: readonly for @property fields that aren't reassigned
|
|
232
|
+
@property(Node)
|
|
233
|
+
private readonly targetNode: Node | null = null;
|
|
234
|
+
|
|
235
|
+
@property(Number)
|
|
236
|
+
private readonly moveSpeed: number = 100;
|
|
237
|
+
|
|
238
|
+
// ✅ GOOD: readonly for injected dependencies
|
|
239
|
+
private readonly eventManager: EventManager;
|
|
240
|
+
|
|
241
|
+
// Regular mutable field
|
|
242
|
+
private currentHealth: number = 100;
|
|
243
|
+
|
|
244
|
+
constructor(eventManager: EventManager) {
|
|
245
|
+
super();
|
|
246
|
+
this.eventManager = eventManager;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ❌ WRONG: Can't reassign readonly field
|
|
250
|
+
public setTarget(node: Node): void {
|
|
251
|
+
// this.targetNode = node; // Error: Cannot assign to 'targetNode' because it is a read-only property
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ❌ BAD: Mutable when shouldn't be
|
|
256
|
+
@ccclass('GameConfig')
|
|
257
|
+
export class GameConfig extends Component {
|
|
258
|
+
@property(Number)
|
|
259
|
+
private maxHealth: number = 100; // Should be readonly
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Use const for Constants
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// ✅ GOOD: const for constants
|
|
267
|
+
const MAX_PLAYERS = 4;
|
|
268
|
+
const DEFAULT_PLAYER_NAME = 'Player';
|
|
269
|
+
const GAME_VERSION = '1.0.0';
|
|
270
|
+
|
|
271
|
+
// ✅ GOOD: Static readonly for class constants
|
|
272
|
+
@ccclass('GameRules')
|
|
273
|
+
export class GameRules extends Component {
|
|
274
|
+
private static readonly MAX_HEALTH: number = 100;
|
|
275
|
+
private static readonly MIN_LEVEL: number = 1;
|
|
276
|
+
private static readonly MAX_LEVEL: number = 50;
|
|
277
|
+
|
|
278
|
+
public static isValidLevel(level: number): boolean {
|
|
279
|
+
return level >= GameRules.MIN_LEVEL && level <= GameRules.MAX_LEVEL;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ✅ GOOD: Enum for related constants
|
|
284
|
+
export enum GameState {
|
|
285
|
+
LOADING = 'loading',
|
|
286
|
+
PLAYING = 'playing',
|
|
287
|
+
PAUSED = 'paused',
|
|
288
|
+
GAME_OVER = 'game_over',
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ❌ BAD: let for constants
|
|
292
|
+
let maxPlayers = 4; // Should be const
|
|
293
|
+
let defaultPlayerName = 'Player'; // Should be const
|
|
294
|
+
|
|
295
|
+
// ❌ BAD: Magic numbers without constants
|
|
296
|
+
public checkHealth(): boolean {
|
|
297
|
+
return this.health > 0 && this.health <= 100; // What is 100?
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ✅ BETTER: Named constant
|
|
301
|
+
private static readonly MAX_HEALTH: number = 100;
|
|
302
|
+
|
|
303
|
+
public checkHealth(): boolean {
|
|
304
|
+
return this.health > 0 && this.health <= GameRules.MAX_HEALTH;
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## No Inline Comments (Use Descriptive Names)
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import { _decorator, Component, Node } from 'cc';
|
|
312
|
+
const { ccclass, property } = _decorator;
|
|
313
|
+
|
|
314
|
+
// ✅ EXCELLENT: Self-explanatory code, no inline comments
|
|
315
|
+
@ccclass('PlayerController')
|
|
316
|
+
export class PlayerController extends Component {
|
|
317
|
+
@property(Node)
|
|
318
|
+
private readonly healthBarNode: Node | null = null;
|
|
319
|
+
|
|
320
|
+
private currentHealth: number = 100;
|
|
321
|
+
private static readonly MAX_HEALTH: number = 100;
|
|
322
|
+
private static readonly CRITICAL_HEALTH_THRESHOLD: number = 20;
|
|
323
|
+
|
|
324
|
+
public takeDamage(amount: number): void {
|
|
325
|
+
this.currentHealth = Math.max(0, this.currentHealth - amount);
|
|
326
|
+
this.updateHealthBar();
|
|
327
|
+
|
|
328
|
+
if (this.isHealthCritical()) {
|
|
329
|
+
this.triggerLowHealthWarning();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (this.isDead()) {
|
|
333
|
+
this.handlePlayerDeath();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private isHealthCritical(): boolean {
|
|
338
|
+
return this.currentHealth <= PlayerController.CRITICAL_HEALTH_THRESHOLD;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private isDead(): boolean {
|
|
342
|
+
return this.currentHealth === 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private triggerLowHealthWarning(): void {
|
|
346
|
+
// Implementation
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private handlePlayerDeath(): void {
|
|
350
|
+
// Implementation
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private updateHealthBar(): void {
|
|
354
|
+
if (!this.healthBarNode) return;
|
|
355
|
+
|
|
356
|
+
const healthPercentage = this.currentHealth / PlayerController.MAX_HEALTH;
|
|
357
|
+
this.healthBarNode.scale = new Vec3(healthPercentage, 1, 1);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ❌ BAD: Inline comments explaining unclear code
|
|
362
|
+
@ccclass('PlayerController')
|
|
363
|
+
export class PlayerController extends Component {
|
|
364
|
+
private h: number = 100; // health
|
|
365
|
+
|
|
366
|
+
public td(a: number): void { // take damage
|
|
367
|
+
this.h = Math.max(0, this.h - a); // subtract damage but don't go below 0
|
|
368
|
+
this.uh(); // update health bar
|
|
369
|
+
|
|
370
|
+
if (this.h <= 20) { // if health is critical
|
|
371
|
+
this.tlhw(); // trigger low health warning
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (this.h === 0) { // if dead
|
|
375
|
+
this.hpd(); // handle player death
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ❌ BAD: Comments explaining what code does (should be obvious)
|
|
381
|
+
public addScore(points: number): void {
|
|
382
|
+
// Add points to current score
|
|
383
|
+
this.currentScore += points;
|
|
384
|
+
|
|
385
|
+
// Check if score exceeds maximum
|
|
386
|
+
if (this.currentScore > MAX_SCORE) {
|
|
387
|
+
// Set score to maximum
|
|
388
|
+
this.currentScore = MAX_SCORE;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ✅ BETTER: Descriptive names make comments unnecessary
|
|
393
|
+
public addScore(points: number): void {
|
|
394
|
+
this.currentScore += points;
|
|
395
|
+
this.clampScoreToMaximum();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private clampScoreToMaximum(): void {
|
|
399
|
+
this.currentScore = Math.min(this.currentScore, MAX_SCORE);
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**When Comments ARE Appropriate:**
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
// ✅ GOOD: Documenting WHY, not WHAT
|
|
407
|
+
/**
|
|
408
|
+
* Calculates damage using quadratic formula to create smooth damage curve.
|
|
409
|
+
* Linear damage felt too harsh for new players during playtesting.
|
|
410
|
+
*/
|
|
411
|
+
private calculateDamage(baseAmount: number, level: number): number {
|
|
412
|
+
return baseAmount * Math.pow(level, 0.8);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ✅ GOOD: Documenting complex algorithms
|
|
416
|
+
/**
|
|
417
|
+
* A* pathfinding algorithm implementation.
|
|
418
|
+
* Uses Manhattan distance heuristic for grid-based movement.
|
|
419
|
+
* @see https://en.wikipedia.org/wiki/A*_search_algorithm
|
|
420
|
+
*/
|
|
421
|
+
private findPath(start: Vec2, end: Vec2): Vec2[] {
|
|
422
|
+
// Implementation
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ✅ GOOD: Documenting workarounds
|
|
426
|
+
/**
|
|
427
|
+
* WORKAROUND: Cocos Creator 3.8.x has a bug where sprite atlas
|
|
428
|
+
* frames aren't properly loaded on first access. Accessing once
|
|
429
|
+
* in onLoad() ensures they're cached for later use.
|
|
430
|
+
* @see https://github.com/cocos/cocos-engine/issues/12345
|
|
431
|
+
*/
|
|
432
|
+
protected onLoad(): void {
|
|
433
|
+
this.atlas?.getSpriteFrame('dummy');
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Proper Null/Undefined Handling
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import { _decorator, Component, Node } from 'cc';
|
|
441
|
+
const { ccclass, property } = _decorator;
|
|
442
|
+
|
|
443
|
+
@ccclass('PlayerManager')
|
|
444
|
+
export class PlayerManager extends Component {
|
|
445
|
+
@property(Node)
|
|
446
|
+
private readonly playerNode: Node | null = null;
|
|
447
|
+
|
|
448
|
+
// ✅ EXCELLENT: Explicit validation and error handling
|
|
449
|
+
protected onLoad(): void {
|
|
450
|
+
if (!this.playerNode) {
|
|
451
|
+
throw new Error('PlayerManager: playerNode is required');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ✅ GOOD: Optional chaining for safe access
|
|
456
|
+
public getPlayerName(): string {
|
|
457
|
+
return this.playerNode?.name ?? 'Unknown';
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ✅ GOOD: Nullish coalescing for default values
|
|
461
|
+
public getPlayerHealth(): number {
|
|
462
|
+
return this.playerNode?.getComponent(PlayerController)?.health ?? 0;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ✅ GOOD: Type guard for type safety
|
|
466
|
+
private isValidPlayer(node: Node | null): node is Node {
|
|
467
|
+
return node !== null && node.getComponent(PlayerController) !== null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
public updatePlayer(): void {
|
|
471
|
+
if (this.isValidPlayer(this.playerNode)) {
|
|
472
|
+
// TypeScript knows playerNode is Node (not null)
|
|
473
|
+
const controller = this.playerNode.getComponent(PlayerController)!;
|
|
474
|
+
controller.update();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ❌ BAD: No null checks
|
|
480
|
+
public updatePlayer(): void {
|
|
481
|
+
this.playerNode.position = new Vec3(0, 0, 0); // Can crash if null
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// ❌ BAD: Unsafe type assertions
|
|
485
|
+
public getController(): PlayerController {
|
|
486
|
+
return this.playerNode!.getComponent(PlayerController)!; // Unsafe!
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
## Avoid `any` Type
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// ✅ GOOD: Proper types and interfaces
|
|
494
|
+
interface PlayerData {
|
|
495
|
+
id: string;
|
|
496
|
+
name: string;
|
|
497
|
+
level: number;
|
|
498
|
+
health: number;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
@ccclass('PlayerService')
|
|
502
|
+
export class PlayerService extends Component {
|
|
503
|
+
private readonly players: Map<string, PlayerData> = new Map();
|
|
504
|
+
|
|
505
|
+
public addPlayer(data: PlayerData): void {
|
|
506
|
+
this.players.set(data.id, data);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
public getPlayer(id: string): PlayerData | undefined {
|
|
510
|
+
return this.players.get(id);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ❌ BAD: Using any type
|
|
515
|
+
@ccclass('PlayerService')
|
|
516
|
+
export class PlayerService extends Component {
|
|
517
|
+
private players: any = {}; // Type safety lost
|
|
518
|
+
|
|
519
|
+
public addPlayer(data: any): void { // No type checking
|
|
520
|
+
this.players[data.id] = data;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
public getPlayer(id: string): any { // Caller doesn't know structure
|
|
524
|
+
return this.players[id];
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ✅ GOOD: Use generics instead of any
|
|
529
|
+
class DataStore<T> {
|
|
530
|
+
private data: Map<string, T> = new Map();
|
|
531
|
+
|
|
532
|
+
public set(key: string, value: T): void {
|
|
533
|
+
this.data.set(key, value);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
public get(key: string): T | undefined {
|
|
537
|
+
return this.data.get(key);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ✅ GOOD: Use unknown for truly unknown types (safer than any)
|
|
542
|
+
function parseJSON(json: string): unknown {
|
|
543
|
+
return JSON.parse(json);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Then validate and narrow the type
|
|
547
|
+
const result = parseJSON('{"name": "Player"}');
|
|
548
|
+
if (isPlayerData(result)) {
|
|
549
|
+
// result is now typed as PlayerData
|
|
550
|
+
console.log(result.name);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function isPlayerData(obj: unknown): obj is PlayerData {
|
|
554
|
+
return (
|
|
555
|
+
typeof obj === 'object' &&
|
|
556
|
+
obj !== null &&
|
|
557
|
+
'id' in obj &&
|
|
558
|
+
'name' in obj &&
|
|
559
|
+
'level' in obj &&
|
|
560
|
+
'health' in obj
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
## Summary: Quality Checklist
|
|
566
|
+
|
|
567
|
+
**Before committing code, verify:**
|
|
568
|
+
|
|
569
|
+
- [ ] TypeScript strict mode enabled in tsconfig.json
|
|
570
|
+
- [ ] ESLint configuration active and passing
|
|
571
|
+
- [ ] All class members have access modifiers (public/private/protected)
|
|
572
|
+
- [ ] Exceptions thrown for errors (no silent failures)
|
|
573
|
+
- [ ] console.log removed or wrapped in CC_DEBUG
|
|
574
|
+
- [ ] readonly used for non-reassigned fields
|
|
575
|
+
- [ ] const used for constants (not let)
|
|
576
|
+
- [ ] No inline comments (code is self-explanatory)
|
|
577
|
+
- [ ] Optional chaining (?.) for safe property access
|
|
578
|
+
- [ ] Nullish coalescing (??) for default values
|
|
579
|
+
- [ ] No `any` types without justification
|
|
580
|
+
- [ ] Required references validated in onLoad()
|
|
581
|
+
|
|
582
|
+
**Quality is the foundation of all other patterns. Get this right first.**
|