@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
package/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
By downloading, copying, or using this template, you agree to the
|
|
2
|
-
RUN.game Terms of Service:
|
|
3
|
-
|
|
4
|
-
https://policy.run.game/eula.html
|
|
5
|
-
|
|
6
|
-
© Series Inc. All rights reserved.
|
|
1
|
+
By downloading, copying, or using this template, you agree to the
|
|
2
|
+
RUN.game Terms of Service:
|
|
3
|
+
|
|
4
|
+
https://policy.run.game/eula.html
|
|
5
|
+
|
|
6
|
+
© Series Inc. All rights reserved.
|
package/docs/core/Component.md
CHANGED
|
@@ -1,321 +1,321 @@
|
|
|
1
|
-
# Component
|
|
2
|
-
|
|
3
|
-
Component is the base class for all behaviors that can be attached to GameObjects. It provides lifecycle hooks and automatic update registration.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { Component } from "@series-ai/rundot-3d-engine"
|
|
9
|
-
|
|
10
|
-
class RotateComponent extends Component {
|
|
11
|
-
private speed: number = 1
|
|
12
|
-
|
|
13
|
-
constructor(speed: number = 1) {
|
|
14
|
-
super()
|
|
15
|
-
this.speed = speed
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
protected onCreate(): void {
|
|
19
|
-
console.log("Component initialized")
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public update(deltaTime: number): void {
|
|
23
|
-
// Rotate GameObject each frame
|
|
24
|
-
this.gameObject.rotation.y += this.speed * deltaTime
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
protected onCleanup(): void {
|
|
28
|
-
console.log("Component cleaned up")
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Use the component
|
|
33
|
-
const box = new GameObject("RotatingBox")
|
|
34
|
-
box.addComponent(new RotateComponent(2.0))
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Component Lifecycle
|
|
38
|
-
|
|
39
|
-
Components have a well-defined lifecycle with automatic hook calls:
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
class LifecycleExample extends Component {
|
|
43
|
-
protected onCreate(): void {
|
|
44
|
-
// Called when component is added to GameObject
|
|
45
|
-
// Use for initialization, getting other components
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
public onEnabled(): void {
|
|
49
|
-
// Called when GameObject becomes enabled
|
|
50
|
-
// Use to resume behavior, start effects
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public update(deltaTime: number): void {
|
|
54
|
-
// Called every frame while GameObject is enabled
|
|
55
|
-
// Use for per-frame logic, movement, checks
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
public lateUpdate(deltaTime: number): void {
|
|
59
|
-
// Called after all update() calls
|
|
60
|
-
// Use for camera following, UI updates
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public onDisabled(): void {
|
|
64
|
-
// Called when GameObject becomes disabled
|
|
65
|
-
// Use to pause behavior, hide effects
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
protected onCleanup(): void {
|
|
69
|
-
// Called when component is removed or GameObject disposed
|
|
70
|
-
// Use for cleanup, removing listeners, disposing resources
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Common Use Cases
|
|
76
|
-
|
|
77
|
-
### Accessing GameObject Properties
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
class MoveForward extends Component {
|
|
81
|
-
private speed: number = 5
|
|
82
|
-
|
|
83
|
-
public update(deltaTime: number): void {
|
|
84
|
-
// Access GameObject's transform directly
|
|
85
|
-
this.gameObject.position.z += this.speed * deltaTime
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Getting Other Components
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
class HealthDisplay extends Component {
|
|
94
|
-
private meshRenderer?: MeshRenderer
|
|
95
|
-
|
|
96
|
-
protected onCreate(): void {
|
|
97
|
-
// Get component from same GameObject
|
|
98
|
-
this.meshRenderer = this.getComponent(MeshRenderer)
|
|
99
|
-
|
|
100
|
-
if (!this.meshRenderer) {
|
|
101
|
-
console.warn("No MeshRenderer found!")
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
public showDamage(): void {
|
|
106
|
-
if (this.meshRenderer) {
|
|
107
|
-
// Flash red or hide temporarily
|
|
108
|
-
this.meshRenderer.setVisible(false)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Enable/Disable Behavior
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
class EnemyAI extends Component {
|
|
118
|
-
private isActive: boolean = false
|
|
119
|
-
|
|
120
|
-
public onEnabled(): void {
|
|
121
|
-
this.isActive = true
|
|
122
|
-
console.log("AI activated")
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
public onDisabled(): void {
|
|
126
|
-
this.isActive = false
|
|
127
|
-
console.log("AI deactivated")
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public update(deltaTime: number): void {
|
|
131
|
-
if (this.isActive) {
|
|
132
|
-
// AI logic only runs when enabled
|
|
133
|
-
this.findTarget()
|
|
134
|
-
this.moveTowardsTarget(deltaTime)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Accessing Scene and Game
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
class SpawnManager extends Component {
|
|
144
|
-
public spawnEnemy(): void {
|
|
145
|
-
// Access the scene directly
|
|
146
|
-
const scene = this.scene
|
|
147
|
-
|
|
148
|
-
// Create new GameObjects
|
|
149
|
-
const enemy = new GameObject("Enemy")
|
|
150
|
-
enemy.position.set(10, 0, 10)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## API Overview
|
|
156
|
-
|
|
157
|
-
### Properties
|
|
158
|
-
|
|
159
|
-
- `gameObject: GameObject` - The GameObject this component is attached to (read-only)
|
|
160
|
-
- `scene: THREE.Scene` - The Three.js scene (convenience accessor)
|
|
161
|
-
|
|
162
|
-
### Methods
|
|
163
|
-
|
|
164
|
-
- `getGameObject(): GameObject` - Get the attached GameObject
|
|
165
|
-
- `getComponent<T>(type): T | undefined` - Get another component from same GameObject
|
|
166
|
-
- `isAttached(): boolean` - Check if component is attached
|
|
167
|
-
|
|
168
|
-
### Lifecycle Hooks (Override These)
|
|
169
|
-
|
|
170
|
-
- `onCreate(): void` - Initialization after attachment
|
|
171
|
-
- `onEnabled(): void` - Called when GameObject becomes enabled
|
|
172
|
-
- `onDisabled(): void` - Called when GameObject becomes disabled
|
|
173
|
-
- `update(deltaTime: number): void` - Per-frame update
|
|
174
|
-
- `lateUpdate(deltaTime: number): void` - Per-frame update after all update() calls
|
|
175
|
-
- `onCleanup(): void` - Cleanup before removal
|
|
176
|
-
|
|
177
|
-
## Patterns & Best Practices
|
|
178
|
-
|
|
179
|
-
### Cache Component References
|
|
180
|
-
|
|
181
|
-
```typescript
|
|
182
|
-
// Good - Cache in onCreate
|
|
183
|
-
class Follower extends Component {
|
|
184
|
-
private target?: Transform
|
|
185
|
-
|
|
186
|
-
protected onCreate(): void {
|
|
187
|
-
const targetObj = GameObject.find("Target")
|
|
188
|
-
this.target = targetObj // Cache reference
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
public update(deltaTime: number): void {
|
|
192
|
-
if (this.target) {
|
|
193
|
-
// Use cached reference - fast!
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Bad - Search every frame
|
|
199
|
-
class Follower extends Component {
|
|
200
|
-
public update(deltaTime: number): void {
|
|
201
|
-
const target = GameObject.find("Target") // Slow!
|
|
202
|
-
if (target) {
|
|
203
|
-
// ...
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Use update() vs lateUpdate()
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
// Player movement in update()
|
|
213
|
-
class PlayerController extends Component {
|
|
214
|
-
public update(deltaTime: number): void {
|
|
215
|
-
// Move player based on input
|
|
216
|
-
this.movePlayer(deltaTime)
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Camera follows player in lateUpdate()
|
|
221
|
-
class CameraFollow extends Component {
|
|
222
|
-
public lateUpdate(deltaTime: number): void {
|
|
223
|
-
// Follow player AFTER they've moved
|
|
224
|
-
this.followTarget(deltaTime)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Proper Event Listener Cleanup
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
class ClickHandler extends Component {
|
|
233
|
-
private boundOnClick: (e: MouseEvent) => void
|
|
234
|
-
|
|
235
|
-
protected onCreate(): void {
|
|
236
|
-
this.boundOnClick = this.onClick.bind(this)
|
|
237
|
-
document.addEventListener("click", this.boundOnClick)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
protected onCleanup(): void {
|
|
241
|
-
// Always remove listeners!
|
|
242
|
-
document.removeEventListener("click", this.boundOnClick)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private onClick(e: MouseEvent): void {
|
|
246
|
-
console.log("Clicked!")
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### Optional Update Methods
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
// Only implement update if you need it
|
|
255
|
-
class StaticData extends Component {
|
|
256
|
-
// No update() - won't be registered for updates (more efficient)
|
|
257
|
-
|
|
258
|
-
protected onCreate(): void {
|
|
259
|
-
// One-time initialization only
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
## Anti-Patterns
|
|
265
|
-
|
|
266
|
-
### Don't Access GameObject Before Attached
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
// Bad - gameObject doesn't exist yet!
|
|
270
|
-
class BadComponent extends Component {
|
|
271
|
-
private position = this.gameObject.position // ERROR!
|
|
272
|
-
|
|
273
|
-
constructor() {
|
|
274
|
-
super()
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Good - Access in onCreate or later
|
|
279
|
-
class GoodComponent extends Component {
|
|
280
|
-
private position?: THREE.Vector3
|
|
281
|
-
|
|
282
|
-
protected onCreate(): void {
|
|
283
|
-
this.position = this.gameObject.position.clone()
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
### Don't Store References to Disposed GameObjects
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
// Bad - holding reference to disposed object
|
|
292
|
-
class Spawner extends Component {
|
|
293
|
-
private spawnedObjects: GameObject[] = []
|
|
294
|
-
|
|
295
|
-
public spawnObject(): void {
|
|
296
|
-
const obj = new GameObject("Spawned")
|
|
297
|
-
this.spawnedObjects.push(obj)
|
|
298
|
-
// If obj.dispose() is called elsewhere, array has dead reference
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Good - Remove from array when disposing
|
|
303
|
-
class Spawner extends Component {
|
|
304
|
-
private spawnedObjects: GameObject[] = []
|
|
305
|
-
|
|
306
|
-
public despawnObject(obj: GameObject): void {
|
|
307
|
-
obj.dispose()
|
|
308
|
-
const index = this.spawnedObjects.indexOf(obj)
|
|
309
|
-
if (index > -1) {
|
|
310
|
-
this.spawnedObjects.splice(index, 1)
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
## Related Systems
|
|
317
|
-
|
|
318
|
-
- [GameObject](GameObject.md) - Entity class that components attach to
|
|
319
|
-
- [VenusGame](VenusGame.md) - Main game class with lifecycle
|
|
320
|
-
- [ComponentUpdater](../../src/engine/core/ComponentUpdater.ts) - Internal update manager
|
|
321
|
-
|
|
1
|
+
# Component
|
|
2
|
+
|
|
3
|
+
Component is the base class for all behaviors that can be attached to GameObjects. It provides lifecycle hooks and automatic update registration.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Component } from "@series-ai/rundot-3d-engine"
|
|
9
|
+
|
|
10
|
+
class RotateComponent extends Component {
|
|
11
|
+
private speed: number = 1
|
|
12
|
+
|
|
13
|
+
constructor(speed: number = 1) {
|
|
14
|
+
super()
|
|
15
|
+
this.speed = speed
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protected onCreate(): void {
|
|
19
|
+
console.log("Component initialized")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public update(deltaTime: number): void {
|
|
23
|
+
// Rotate GameObject each frame
|
|
24
|
+
this.gameObject.rotation.y += this.speed * deltaTime
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected onCleanup(): void {
|
|
28
|
+
console.log("Component cleaned up")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Use the component
|
|
33
|
+
const box = new GameObject("RotatingBox")
|
|
34
|
+
box.addComponent(new RotateComponent(2.0))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Component Lifecycle
|
|
38
|
+
|
|
39
|
+
Components have a well-defined lifecycle with automatic hook calls:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
class LifecycleExample extends Component {
|
|
43
|
+
protected onCreate(): void {
|
|
44
|
+
// Called when component is added to GameObject
|
|
45
|
+
// Use for initialization, getting other components
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public onEnabled(): void {
|
|
49
|
+
// Called when GameObject becomes enabled
|
|
50
|
+
// Use to resume behavior, start effects
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public update(deltaTime: number): void {
|
|
54
|
+
// Called every frame while GameObject is enabled
|
|
55
|
+
// Use for per-frame logic, movement, checks
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public lateUpdate(deltaTime: number): void {
|
|
59
|
+
// Called after all update() calls
|
|
60
|
+
// Use for camera following, UI updates
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public onDisabled(): void {
|
|
64
|
+
// Called when GameObject becomes disabled
|
|
65
|
+
// Use to pause behavior, hide effects
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected onCleanup(): void {
|
|
69
|
+
// Called when component is removed or GameObject disposed
|
|
70
|
+
// Use for cleanup, removing listeners, disposing resources
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Common Use Cases
|
|
76
|
+
|
|
77
|
+
### Accessing GameObject Properties
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
class MoveForward extends Component {
|
|
81
|
+
private speed: number = 5
|
|
82
|
+
|
|
83
|
+
public update(deltaTime: number): void {
|
|
84
|
+
// Access GameObject's transform directly
|
|
85
|
+
this.gameObject.position.z += this.speed * deltaTime
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Getting Other Components
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
class HealthDisplay extends Component {
|
|
94
|
+
private meshRenderer?: MeshRenderer
|
|
95
|
+
|
|
96
|
+
protected onCreate(): void {
|
|
97
|
+
// Get component from same GameObject
|
|
98
|
+
this.meshRenderer = this.getComponent(MeshRenderer)
|
|
99
|
+
|
|
100
|
+
if (!this.meshRenderer) {
|
|
101
|
+
console.warn("No MeshRenderer found!")
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public showDamage(): void {
|
|
106
|
+
if (this.meshRenderer) {
|
|
107
|
+
// Flash red or hide temporarily
|
|
108
|
+
this.meshRenderer.setVisible(false)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Enable/Disable Behavior
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
class EnemyAI extends Component {
|
|
118
|
+
private isActive: boolean = false
|
|
119
|
+
|
|
120
|
+
public onEnabled(): void {
|
|
121
|
+
this.isActive = true
|
|
122
|
+
console.log("AI activated")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public onDisabled(): void {
|
|
126
|
+
this.isActive = false
|
|
127
|
+
console.log("AI deactivated")
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public update(deltaTime: number): void {
|
|
131
|
+
if (this.isActive) {
|
|
132
|
+
// AI logic only runs when enabled
|
|
133
|
+
this.findTarget()
|
|
134
|
+
this.moveTowardsTarget(deltaTime)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Accessing Scene and Game
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
class SpawnManager extends Component {
|
|
144
|
+
public spawnEnemy(): void {
|
|
145
|
+
// Access the scene directly
|
|
146
|
+
const scene = this.scene
|
|
147
|
+
|
|
148
|
+
// Create new GameObjects
|
|
149
|
+
const enemy = new GameObject("Enemy")
|
|
150
|
+
enemy.position.set(10, 0, 10)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## API Overview
|
|
156
|
+
|
|
157
|
+
### Properties
|
|
158
|
+
|
|
159
|
+
- `gameObject: GameObject` - The GameObject this component is attached to (read-only)
|
|
160
|
+
- `scene: THREE.Scene` - The Three.js scene (convenience accessor)
|
|
161
|
+
|
|
162
|
+
### Methods
|
|
163
|
+
|
|
164
|
+
- `getGameObject(): GameObject` - Get the attached GameObject
|
|
165
|
+
- `getComponent<T>(type): T | undefined` - Get another component from same GameObject
|
|
166
|
+
- `isAttached(): boolean` - Check if component is attached
|
|
167
|
+
|
|
168
|
+
### Lifecycle Hooks (Override These)
|
|
169
|
+
|
|
170
|
+
- `onCreate(): void` - Initialization after attachment
|
|
171
|
+
- `onEnabled(): void` - Called when GameObject becomes enabled
|
|
172
|
+
- `onDisabled(): void` - Called when GameObject becomes disabled
|
|
173
|
+
- `update(deltaTime: number): void` - Per-frame update
|
|
174
|
+
- `lateUpdate(deltaTime: number): void` - Per-frame update after all update() calls
|
|
175
|
+
- `onCleanup(): void` - Cleanup before removal
|
|
176
|
+
|
|
177
|
+
## Patterns & Best Practices
|
|
178
|
+
|
|
179
|
+
### Cache Component References
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// Good - Cache in onCreate
|
|
183
|
+
class Follower extends Component {
|
|
184
|
+
private target?: Transform
|
|
185
|
+
|
|
186
|
+
protected onCreate(): void {
|
|
187
|
+
const targetObj = GameObject.find("Target")
|
|
188
|
+
this.target = targetObj // Cache reference
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public update(deltaTime: number): void {
|
|
192
|
+
if (this.target) {
|
|
193
|
+
// Use cached reference - fast!
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Bad - Search every frame
|
|
199
|
+
class Follower extends Component {
|
|
200
|
+
public update(deltaTime: number): void {
|
|
201
|
+
const target = GameObject.find("Target") // Slow!
|
|
202
|
+
if (target) {
|
|
203
|
+
// ...
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Use update() vs lateUpdate()
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
// Player movement in update()
|
|
213
|
+
class PlayerController extends Component {
|
|
214
|
+
public update(deltaTime: number): void {
|
|
215
|
+
// Move player based on input
|
|
216
|
+
this.movePlayer(deltaTime)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Camera follows player in lateUpdate()
|
|
221
|
+
class CameraFollow extends Component {
|
|
222
|
+
public lateUpdate(deltaTime: number): void {
|
|
223
|
+
// Follow player AFTER they've moved
|
|
224
|
+
this.followTarget(deltaTime)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Proper Event Listener Cleanup
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
class ClickHandler extends Component {
|
|
233
|
+
private boundOnClick: (e: MouseEvent) => void
|
|
234
|
+
|
|
235
|
+
protected onCreate(): void {
|
|
236
|
+
this.boundOnClick = this.onClick.bind(this)
|
|
237
|
+
document.addEventListener("click", this.boundOnClick)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
protected onCleanup(): void {
|
|
241
|
+
// Always remove listeners!
|
|
242
|
+
document.removeEventListener("click", this.boundOnClick)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private onClick(e: MouseEvent): void {
|
|
246
|
+
console.log("Clicked!")
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Optional Update Methods
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// Only implement update if you need it
|
|
255
|
+
class StaticData extends Component {
|
|
256
|
+
// No update() - won't be registered for updates (more efficient)
|
|
257
|
+
|
|
258
|
+
protected onCreate(): void {
|
|
259
|
+
// One-time initialization only
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Anti-Patterns
|
|
265
|
+
|
|
266
|
+
### Don't Access GameObject Before Attached
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// Bad - gameObject doesn't exist yet!
|
|
270
|
+
class BadComponent extends Component {
|
|
271
|
+
private position = this.gameObject.position // ERROR!
|
|
272
|
+
|
|
273
|
+
constructor() {
|
|
274
|
+
super()
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Good - Access in onCreate or later
|
|
279
|
+
class GoodComponent extends Component {
|
|
280
|
+
private position?: THREE.Vector3
|
|
281
|
+
|
|
282
|
+
protected onCreate(): void {
|
|
283
|
+
this.position = this.gameObject.position.clone()
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Don't Store References to Disposed GameObjects
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// Bad - holding reference to disposed object
|
|
292
|
+
class Spawner extends Component {
|
|
293
|
+
private spawnedObjects: GameObject[] = []
|
|
294
|
+
|
|
295
|
+
public spawnObject(): void {
|
|
296
|
+
const obj = new GameObject("Spawned")
|
|
297
|
+
this.spawnedObjects.push(obj)
|
|
298
|
+
// If obj.dispose() is called elsewhere, array has dead reference
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Good - Remove from array when disposing
|
|
303
|
+
class Spawner extends Component {
|
|
304
|
+
private spawnedObjects: GameObject[] = []
|
|
305
|
+
|
|
306
|
+
public despawnObject(obj: GameObject): void {
|
|
307
|
+
obj.dispose()
|
|
308
|
+
const index = this.spawnedObjects.indexOf(obj)
|
|
309
|
+
if (index > -1) {
|
|
310
|
+
this.spawnedObjects.splice(index, 1)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Related Systems
|
|
317
|
+
|
|
318
|
+
- [GameObject](GameObject.md) - Entity class that components attach to
|
|
319
|
+
- [VenusGame](VenusGame.md) - Main game class with lifecycle
|
|
320
|
+
- [ComponentUpdater](../../src/engine/core/ComponentUpdater.ts) - Internal update manager
|
|
321
|
+
|