@series-inc/rundot-3d-engine 0.3.0 → 0.4.0
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/LICENSE.txt +6 -6
- package/docs/core/Component.md +321 -321
- package/docs/core/GameObject.md +204 -204
- package/docs/core/VenusGame.md +316 -316
- package/docs/patterns/ComponentCommunication.md +337 -337
- package/docs/patterns/CreatingGameObjects.md +290 -290
- package/docs/patterns/MeshColliders.md +338 -338
- package/docs/patterns/MeshLoading.md +316 -316
- package/docs/physics/Colliders.md +249 -249
- package/docs/physics/PhysicsSystem.md +151 -151
- package/docs/physics/RigidBodyComponent.md +201 -201
- package/docs/rendering/AssetManager.md +308 -308
- package/docs/rendering/InstancedRenderer.md +286 -286
- package/docs/rendering/MeshRenderer.md +286 -286
- package/docs/rendering/SkeletalRenderer.md +308 -308
- package/docs/systems/AnimationSystem.md +75 -75
- package/docs/systems/AudioSystem.md +79 -79
- package/docs/systems/InputManager.md +101 -101
- package/docs/systems/LightingSystem.md +101 -101
- package/docs/systems/ParticleSystem.md +44 -44
- package/docs/systems/PrefabSystem.md +60 -60
- package/docs/systems/StowKitSystem.md +77 -77
- package/docs/systems/TweenSystem.md +132 -132
- package/docs/systems/UISystem.md +73 -73
- package/package.json +1 -1
- package/scripts/postinstall.mjs +63 -51
|
@@ -1,337 +1,337 @@
|
|
|
1
|
-
# Component Communication
|
|
2
|
-
|
|
3
|
-
Patterns for components to interact and share data.
|
|
4
|
-
|
|
5
|
-
## Getting Components
|
|
6
|
-
|
|
7
|
-
### Same GameObject
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
class WeaponComponent extends Component {
|
|
11
|
-
private meshRenderer?: MeshRenderer
|
|
12
|
-
|
|
13
|
-
protected onCreate(): void {
|
|
14
|
-
// Get component from same GameObject
|
|
15
|
-
this.meshRenderer = this.getComponent(MeshRenderer)
|
|
16
|
-
|
|
17
|
-
if (this.meshRenderer) {
|
|
18
|
-
console.log("Found MeshRenderer!")
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Parent/Child GameObjects
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
class ChildComponent extends Component {
|
|
28
|
-
private parentController?: ParentController
|
|
29
|
-
|
|
30
|
-
protected onCreate(): void {
|
|
31
|
-
// Get component from parent
|
|
32
|
-
if (this.gameObject.parent instanceof GameObject) {
|
|
33
|
-
this.parentController = this.gameObject.parent.getComponent(ParentController)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Finding by Name
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
class TargetFinder extends Component {
|
|
43
|
-
private target?: GameObject
|
|
44
|
-
|
|
45
|
-
protected onCreate(): void {
|
|
46
|
-
// Search scene for GameObject by name
|
|
47
|
-
const scene = VenusGame.scene
|
|
48
|
-
scene.traverse((obj) => {
|
|
49
|
-
if (obj instanceof GameObject && obj.name === "Target") {
|
|
50
|
-
this.target = obj
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Communication Patterns
|
|
58
|
-
|
|
59
|
-
### Direct Method Calls
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
class HealthComponent extends Component {
|
|
63
|
-
private health: number = 100
|
|
64
|
-
|
|
65
|
-
public takeDamage(amount: number): void {
|
|
66
|
-
this.health -= amount
|
|
67
|
-
if (this.health <= 0) {
|
|
68
|
-
this.die()
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private die(): void {
|
|
73
|
-
console.log("Dead!")
|
|
74
|
-
this.gameObject.dispose()
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
class WeaponComponent extends Component {
|
|
79
|
-
public fire(target: GameObject): void {
|
|
80
|
-
const health = target.getComponent(HealthComponent)
|
|
81
|
-
if (health) {
|
|
82
|
-
health.takeDamage(25)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Event System
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
// Simple event emitter
|
|
92
|
-
class EventBus {
|
|
93
|
-
private static listeners = new Map<string, Function[]>()
|
|
94
|
-
|
|
95
|
-
static on(event: string, callback: Function): void {
|
|
96
|
-
if (!this.listeners.has(event)) {
|
|
97
|
-
this.listeners.set(event, [])
|
|
98
|
-
}
|
|
99
|
-
this.listeners.get(event)!.push(callback)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
static emit(event: string, ...args: any[]): void {
|
|
103
|
-
const callbacks = this.listeners.get(event)
|
|
104
|
-
if (callbacks) {
|
|
105
|
-
for (const callback of callbacks) {
|
|
106
|
-
callback(...args)
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
static off(event: string, callback: Function): void {
|
|
112
|
-
const callbacks = this.listeners.get(event)
|
|
113
|
-
if (callbacks) {
|
|
114
|
-
const index = callbacks.indexOf(callback)
|
|
115
|
-
if (index > -1) {
|
|
116
|
-
callbacks.splice(index, 1)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Use it
|
|
123
|
-
class ScoreComponent extends Component {
|
|
124
|
-
protected onCreate(): void {
|
|
125
|
-
EventBus.on("enemy_killed", this.onEnemyKilled.bind(this))
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private onEnemyKilled(points: number): void {
|
|
129
|
-
this.score += points
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
protected onCleanup(): void {
|
|
133
|
-
EventBus.off("enemy_killed", this.onEnemyKilled)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
class Enemy extends Component {
|
|
138
|
-
private die(): void {
|
|
139
|
-
EventBus.emit("enemy_killed", 100)
|
|
140
|
-
this.gameObject.dispose()
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Shared State
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
// Global game state
|
|
149
|
-
class GameState {
|
|
150
|
-
static score: number = 0
|
|
151
|
-
static level: number = 1
|
|
152
|
-
static playerHealth: number = 100
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Components access shared state
|
|
156
|
-
class ScoreDisplay extends Component {
|
|
157
|
-
public update(deltaTime: number): void {
|
|
158
|
-
this.updateUI(GameState.score)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
class Enemy extends Component {
|
|
163
|
-
private die(): void {
|
|
164
|
-
GameState.score += 100
|
|
165
|
-
this.gameObject.dispose()
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Component References
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
class PlayerManager extends Component {
|
|
174
|
-
public static instance?: PlayerManager
|
|
175
|
-
private health: number = 100
|
|
176
|
-
|
|
177
|
-
protected onCreate(): void {
|
|
178
|
-
PlayerManager.instance = this
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
public getHealth(): number {
|
|
182
|
-
return this.health
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
protected onCleanup(): void {
|
|
186
|
-
if (PlayerManager.instance === this) {
|
|
187
|
-
PlayerManager.instance = undefined
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Access from anywhere
|
|
193
|
-
class HealthBar extends Component {
|
|
194
|
-
public update(deltaTime: number): void {
|
|
195
|
-
if (PlayerManager.instance) {
|
|
196
|
-
const health = PlayerManager.instance.getHealth()
|
|
197
|
-
this.updateBar(health)
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
## Patterns & Best Practices
|
|
204
|
-
|
|
205
|
-
### Cache Component References
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
// GOOD - Cache in onCreate
|
|
209
|
-
class Follower extends Component {
|
|
210
|
-
private target?: Transform
|
|
211
|
-
|
|
212
|
-
protected onCreate(): void {
|
|
213
|
-
// Cache reference once
|
|
214
|
-
const targetObj = this.findTarget()
|
|
215
|
-
this.target = targetObj
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
public update(deltaTime: number): void {
|
|
219
|
-
if (this.target) {
|
|
220
|
-
// Use cached reference
|
|
221
|
-
this.followTarget(this.target)
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// BAD - Search every frame
|
|
227
|
-
class Follower extends Component {
|
|
228
|
-
public update(deltaTime: number): void {
|
|
229
|
-
// Slow! Searches scene every frame
|
|
230
|
-
const target = this.findTarget()
|
|
231
|
-
if (target) {
|
|
232
|
-
this.followTarget(target)
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Null Checks
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
class SafeComponent extends Component {
|
|
242
|
-
private dependency?: OtherComponent
|
|
243
|
-
|
|
244
|
-
protected onCreate(): void {
|
|
245
|
-
this.dependency = this.getComponent(OtherComponent)
|
|
246
|
-
|
|
247
|
-
if (!this.dependency) {
|
|
248
|
-
console.warn("Missing required component!")
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
public update(deltaTime: number): void {
|
|
253
|
-
// Always check before using
|
|
254
|
-
if (this.dependency) {
|
|
255
|
-
this.dependency.doSomething()
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### Clean Up Event Listeners
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
class EventComponent extends Component {
|
|
265
|
-
private boundHandler: () => void
|
|
266
|
-
|
|
267
|
-
protected onCreate(): void {
|
|
268
|
-
this.boundHandler = this.handleEvent.bind(this)
|
|
269
|
-
EventBus.on("game_event", this.boundHandler)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
protected onCleanup(): void {
|
|
273
|
-
// Always remove listeners!
|
|
274
|
-
EventBus.off("game_event", this.boundHandler)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private handleEvent(): void {
|
|
278
|
-
console.log("Event received")
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## Anti-Patterns
|
|
284
|
-
|
|
285
|
-
### Don't Store Disposed References
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
// BAD - Holding reference to disposed object
|
|
289
|
-
class BadManager extends Component {
|
|
290
|
-
private enemies: GameObject[] = []
|
|
291
|
-
|
|
292
|
-
public addEnemy(enemy: GameObject): void {
|
|
293
|
-
this.enemies.push(enemy)
|
|
294
|
-
// If enemy.dispose() called elsewhere, array has dead reference
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// GOOD - Remove from tracking
|
|
299
|
-
class GoodManager extends Component {
|
|
300
|
-
private enemies: GameObject[] = []
|
|
301
|
-
|
|
302
|
-
public removeEnemy(enemy: GameObject): void {
|
|
303
|
-
enemy.dispose()
|
|
304
|
-
const index = this.enemies.indexOf(enemy)
|
|
305
|
-
if (index > -1) {
|
|
306
|
-
this.enemies.splice(index, 1)
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
### Don't Use Global Variables
|
|
313
|
-
|
|
314
|
-
```typescript
|
|
315
|
-
// BAD - Global mutable state
|
|
316
|
-
let globalScore = 0
|
|
317
|
-
|
|
318
|
-
// GOOD - Encapsulated state
|
|
319
|
-
class GameState {
|
|
320
|
-
private static _score: number = 0
|
|
321
|
-
|
|
322
|
-
static getScore(): number {
|
|
323
|
-
return this._score
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
static addScore(points: number): void {
|
|
327
|
-
this._score += points
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
## Related Patterns
|
|
333
|
-
|
|
334
|
-
- [Creating GameObjects](CreatingGameObjects.md) - GameObject creation patterns
|
|
335
|
-
- [Component](../core/Component.md) - Component documentation
|
|
336
|
-
- [GameObject](../core/GameObject.md) - GameObject documentation
|
|
337
|
-
|
|
1
|
+
# Component Communication
|
|
2
|
+
|
|
3
|
+
Patterns for components to interact and share data.
|
|
4
|
+
|
|
5
|
+
## Getting Components
|
|
6
|
+
|
|
7
|
+
### Same GameObject
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
class WeaponComponent extends Component {
|
|
11
|
+
private meshRenderer?: MeshRenderer
|
|
12
|
+
|
|
13
|
+
protected onCreate(): void {
|
|
14
|
+
// Get component from same GameObject
|
|
15
|
+
this.meshRenderer = this.getComponent(MeshRenderer)
|
|
16
|
+
|
|
17
|
+
if (this.meshRenderer) {
|
|
18
|
+
console.log("Found MeshRenderer!")
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Parent/Child GameObjects
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
class ChildComponent extends Component {
|
|
28
|
+
private parentController?: ParentController
|
|
29
|
+
|
|
30
|
+
protected onCreate(): void {
|
|
31
|
+
// Get component from parent
|
|
32
|
+
if (this.gameObject.parent instanceof GameObject) {
|
|
33
|
+
this.parentController = this.gameObject.parent.getComponent(ParentController)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Finding by Name
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
class TargetFinder extends Component {
|
|
43
|
+
private target?: GameObject
|
|
44
|
+
|
|
45
|
+
protected onCreate(): void {
|
|
46
|
+
// Search scene for GameObject by name
|
|
47
|
+
const scene = VenusGame.scene
|
|
48
|
+
scene.traverse((obj) => {
|
|
49
|
+
if (obj instanceof GameObject && obj.name === "Target") {
|
|
50
|
+
this.target = obj
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Communication Patterns
|
|
58
|
+
|
|
59
|
+
### Direct Method Calls
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
class HealthComponent extends Component {
|
|
63
|
+
private health: number = 100
|
|
64
|
+
|
|
65
|
+
public takeDamage(amount: number): void {
|
|
66
|
+
this.health -= amount
|
|
67
|
+
if (this.health <= 0) {
|
|
68
|
+
this.die()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private die(): void {
|
|
73
|
+
console.log("Dead!")
|
|
74
|
+
this.gameObject.dispose()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class WeaponComponent extends Component {
|
|
79
|
+
public fire(target: GameObject): void {
|
|
80
|
+
const health = target.getComponent(HealthComponent)
|
|
81
|
+
if (health) {
|
|
82
|
+
health.takeDamage(25)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Event System
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Simple event emitter
|
|
92
|
+
class EventBus {
|
|
93
|
+
private static listeners = new Map<string, Function[]>()
|
|
94
|
+
|
|
95
|
+
static on(event: string, callback: Function): void {
|
|
96
|
+
if (!this.listeners.has(event)) {
|
|
97
|
+
this.listeners.set(event, [])
|
|
98
|
+
}
|
|
99
|
+
this.listeners.get(event)!.push(callback)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static emit(event: string, ...args: any[]): void {
|
|
103
|
+
const callbacks = this.listeners.get(event)
|
|
104
|
+
if (callbacks) {
|
|
105
|
+
for (const callback of callbacks) {
|
|
106
|
+
callback(...args)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static off(event: string, callback: Function): void {
|
|
112
|
+
const callbacks = this.listeners.get(event)
|
|
113
|
+
if (callbacks) {
|
|
114
|
+
const index = callbacks.indexOf(callback)
|
|
115
|
+
if (index > -1) {
|
|
116
|
+
callbacks.splice(index, 1)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Use it
|
|
123
|
+
class ScoreComponent extends Component {
|
|
124
|
+
protected onCreate(): void {
|
|
125
|
+
EventBus.on("enemy_killed", this.onEnemyKilled.bind(this))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private onEnemyKilled(points: number): void {
|
|
129
|
+
this.score += points
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
protected onCleanup(): void {
|
|
133
|
+
EventBus.off("enemy_killed", this.onEnemyKilled)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
class Enemy extends Component {
|
|
138
|
+
private die(): void {
|
|
139
|
+
EventBus.emit("enemy_killed", 100)
|
|
140
|
+
this.gameObject.dispose()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Shared State
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Global game state
|
|
149
|
+
class GameState {
|
|
150
|
+
static score: number = 0
|
|
151
|
+
static level: number = 1
|
|
152
|
+
static playerHealth: number = 100
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Components access shared state
|
|
156
|
+
class ScoreDisplay extends Component {
|
|
157
|
+
public update(deltaTime: number): void {
|
|
158
|
+
this.updateUI(GameState.score)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
class Enemy extends Component {
|
|
163
|
+
private die(): void {
|
|
164
|
+
GameState.score += 100
|
|
165
|
+
this.gameObject.dispose()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Component References
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
class PlayerManager extends Component {
|
|
174
|
+
public static instance?: PlayerManager
|
|
175
|
+
private health: number = 100
|
|
176
|
+
|
|
177
|
+
protected onCreate(): void {
|
|
178
|
+
PlayerManager.instance = this
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public getHealth(): number {
|
|
182
|
+
return this.health
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
protected onCleanup(): void {
|
|
186
|
+
if (PlayerManager.instance === this) {
|
|
187
|
+
PlayerManager.instance = undefined
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Access from anywhere
|
|
193
|
+
class HealthBar extends Component {
|
|
194
|
+
public update(deltaTime: number): void {
|
|
195
|
+
if (PlayerManager.instance) {
|
|
196
|
+
const health = PlayerManager.instance.getHealth()
|
|
197
|
+
this.updateBar(health)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Patterns & Best Practices
|
|
204
|
+
|
|
205
|
+
### Cache Component References
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// GOOD - Cache in onCreate
|
|
209
|
+
class Follower extends Component {
|
|
210
|
+
private target?: Transform
|
|
211
|
+
|
|
212
|
+
protected onCreate(): void {
|
|
213
|
+
// Cache reference once
|
|
214
|
+
const targetObj = this.findTarget()
|
|
215
|
+
this.target = targetObj
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public update(deltaTime: number): void {
|
|
219
|
+
if (this.target) {
|
|
220
|
+
// Use cached reference
|
|
221
|
+
this.followTarget(this.target)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// BAD - Search every frame
|
|
227
|
+
class Follower extends Component {
|
|
228
|
+
public update(deltaTime: number): void {
|
|
229
|
+
// Slow! Searches scene every frame
|
|
230
|
+
const target = this.findTarget()
|
|
231
|
+
if (target) {
|
|
232
|
+
this.followTarget(target)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Null Checks
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
class SafeComponent extends Component {
|
|
242
|
+
private dependency?: OtherComponent
|
|
243
|
+
|
|
244
|
+
protected onCreate(): void {
|
|
245
|
+
this.dependency = this.getComponent(OtherComponent)
|
|
246
|
+
|
|
247
|
+
if (!this.dependency) {
|
|
248
|
+
console.warn("Missing required component!")
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public update(deltaTime: number): void {
|
|
253
|
+
// Always check before using
|
|
254
|
+
if (this.dependency) {
|
|
255
|
+
this.dependency.doSomething()
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Clean Up Event Listeners
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
class EventComponent extends Component {
|
|
265
|
+
private boundHandler: () => void
|
|
266
|
+
|
|
267
|
+
protected onCreate(): void {
|
|
268
|
+
this.boundHandler = this.handleEvent.bind(this)
|
|
269
|
+
EventBus.on("game_event", this.boundHandler)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
protected onCleanup(): void {
|
|
273
|
+
// Always remove listeners!
|
|
274
|
+
EventBus.off("game_event", this.boundHandler)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private handleEvent(): void {
|
|
278
|
+
console.log("Event received")
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Anti-Patterns
|
|
284
|
+
|
|
285
|
+
### Don't Store Disposed References
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// BAD - Holding reference to disposed object
|
|
289
|
+
class BadManager extends Component {
|
|
290
|
+
private enemies: GameObject[] = []
|
|
291
|
+
|
|
292
|
+
public addEnemy(enemy: GameObject): void {
|
|
293
|
+
this.enemies.push(enemy)
|
|
294
|
+
// If enemy.dispose() called elsewhere, array has dead reference
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// GOOD - Remove from tracking
|
|
299
|
+
class GoodManager extends Component {
|
|
300
|
+
private enemies: GameObject[] = []
|
|
301
|
+
|
|
302
|
+
public removeEnemy(enemy: GameObject): void {
|
|
303
|
+
enemy.dispose()
|
|
304
|
+
const index = this.enemies.indexOf(enemy)
|
|
305
|
+
if (index > -1) {
|
|
306
|
+
this.enemies.splice(index, 1)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Don't Use Global Variables
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// BAD - Global mutable state
|
|
316
|
+
let globalScore = 0
|
|
317
|
+
|
|
318
|
+
// GOOD - Encapsulated state
|
|
319
|
+
class GameState {
|
|
320
|
+
private static _score: number = 0
|
|
321
|
+
|
|
322
|
+
static getScore(): number {
|
|
323
|
+
return this._score
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
static addScore(points: number): void {
|
|
327
|
+
this._score += points
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Related Patterns
|
|
333
|
+
|
|
334
|
+
- [Creating GameObjects](CreatingGameObjects.md) - GameObject creation patterns
|
|
335
|
+
- [Component](../core/Component.md) - Component documentation
|
|
336
|
+
- [GameObject](../core/GameObject.md) - GameObject documentation
|
|
337
|
+
|