@funderforge/ecs 0.1.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 +21 -0
- package/README.md +412 -0
- package/dist/component.d.ts +135 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/component.js +176 -0
- package/dist/component.js.map +1 -0
- package/dist/entity.d.ts +222 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/entity.js +418 -0
- package/dist/entity.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# Component Framework
|
|
2
|
+
|
|
3
|
+
A generic component framework, optimized for performance and simplicity.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This framework implements an Entity-Component System (ECS) pattern that allows you to build flexible, hierarchical structures where entities can contain multiple components. The framework is designed with performance in mind, featuring efficient traversal algorithms, cached property access, and method-based architecture.
|
|
8
|
+
|
|
9
|
+
## How It Works
|
|
10
|
+
|
|
11
|
+
### Architecture
|
|
12
|
+
|
|
13
|
+
The framework consists of two main classes:
|
|
14
|
+
|
|
15
|
+
- **Entity**: A node in a hierarchical tree structure that can have children and components
|
|
16
|
+
- **Component**: Functionality attached to entities that can respond to lifecycle events through virtual methods
|
|
17
|
+
|
|
18
|
+
### Visual Structure
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
Entity Tree Structure:
|
|
22
|
+
═══════════════════════════════════════════════════════════
|
|
23
|
+
|
|
24
|
+
[Root Entity]
|
|
25
|
+
├─ Component A
|
|
26
|
+
├─ Component B
|
|
27
|
+
└─ Component C
|
|
28
|
+
│
|
|
29
|
+
┌───────┴───────┐
|
|
30
|
+
│ │
|
|
31
|
+
[Child Entity 1] [Child Entity 2]
|
|
32
|
+
├─ Component X ├─ Component Y
|
|
33
|
+
└─ Component Z └─ Component W
|
|
34
|
+
│ │
|
|
35
|
+
[Grandchild] [Grandchild]
|
|
36
|
+
├─ Component ├─ Component
|
|
37
|
+
|
|
38
|
+
═══════════════════════════════════════════════════════════
|
|
39
|
+
|
|
40
|
+
Key Concepts:
|
|
41
|
+
• Entities form a tree hierarchy (parent-child relationships)
|
|
42
|
+
• Each entity can have multiple components
|
|
43
|
+
• Components belong to exactly one entity
|
|
44
|
+
• Enabled/disabled state cascades down the tree
|
|
45
|
+
• Components respond to lifecycle events through virtual methods
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Core Features
|
|
49
|
+
|
|
50
|
+
- **Hierarchical Entity Tree**: Entities can have parent-child relationships
|
|
51
|
+
- **Component System**: Attach multiple components to entities for modular functionality
|
|
52
|
+
- **Enabled/Disabled State**: Cascading state management with efficient caching
|
|
53
|
+
- **Method-Based Architecture**: Components can override `onTick` and `onEnabledChanged` virtual methods
|
|
54
|
+
- **Efficient Traversal**: Optimized methods to traverse entities and components
|
|
55
|
+
- **Performance Optimized**: Cached property access, minimal allocations
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install @funderforge/ecs
|
|
61
|
+
# or
|
|
62
|
+
pnpm install @funderforge/ecs
|
|
63
|
+
# or
|
|
64
|
+
yarn add @funderforge/ecs
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Examples
|
|
68
|
+
|
|
69
|
+
### Basic Entity Creation
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { Entity } from '@funderforge/ecs';
|
|
73
|
+
|
|
74
|
+
// Create a root entity
|
|
75
|
+
const root = new Entity({ id: 'root' });
|
|
76
|
+
|
|
77
|
+
// Create child entities
|
|
78
|
+
const child1 = new Entity({ id: 'child1' });
|
|
79
|
+
const child2 = new Entity({ id: 'child2' });
|
|
80
|
+
|
|
81
|
+
// Build the hierarchy
|
|
82
|
+
root.addChild(child1);
|
|
83
|
+
root.addChild(child2);
|
|
84
|
+
|
|
85
|
+
console.log(root.childrenLength()); // 2
|
|
86
|
+
console.log(child1.parent === root); // true
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Creating Components
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Entity, Component } from '@funderforge/ecs';
|
|
93
|
+
|
|
94
|
+
// Define a custom component
|
|
95
|
+
class TransformComponent extends Component {
|
|
96
|
+
x: number = 0;
|
|
97
|
+
y: number = 0;
|
|
98
|
+
|
|
99
|
+
move(dx: number, dy: number) {
|
|
100
|
+
this.x += dx;
|
|
101
|
+
this.y += dy;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Create an entity and attach components
|
|
106
|
+
const entity = new Entity({ id: 'player' });
|
|
107
|
+
const transform = new TransformComponent(entity);
|
|
108
|
+
const renderer = new RenderComponent(entity);
|
|
109
|
+
|
|
110
|
+
console.log(entity.componentsLength()); // 2
|
|
111
|
+
|
|
112
|
+
// Access components with type safety
|
|
113
|
+
const transformComp = entity.getComponent<TransformComponent>(
|
|
114
|
+
c => c instanceof TransformComponent
|
|
115
|
+
);
|
|
116
|
+
transformComp?.move(10, 20);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Entity Hierarchy
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Create a game scene hierarchy
|
|
123
|
+
const scene = new Entity({ id: 'scene' });
|
|
124
|
+
const player = new Entity({ id: 'player', parent: scene });
|
|
125
|
+
const enemy1 = new Entity({ id: 'enemy1', parent: scene });
|
|
126
|
+
const enemy2 = new Entity({ id: 'enemy2', parent: scene });
|
|
127
|
+
|
|
128
|
+
// Nested hierarchy
|
|
129
|
+
const weapon = new Entity({ id: 'weapon', parent: player });
|
|
130
|
+
const bullet = new Entity({ id: 'bullet', parent: weapon });
|
|
131
|
+
|
|
132
|
+
// Get hierarchy level
|
|
133
|
+
console.log(bullet.hierarchyLevel()); // 3 (scene -> player -> weapon -> bullet)
|
|
134
|
+
|
|
135
|
+
// Remove from hierarchy
|
|
136
|
+
scene.removeChild(enemy1);
|
|
137
|
+
console.log(enemy1.parent); // null
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Enabled/Disabled State
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Create entity with initial state
|
|
144
|
+
const entity = new Entity({ id: 'entity', enabledSelf: true });
|
|
145
|
+
const child = new Entity({ id: 'child', parent: entity });
|
|
146
|
+
|
|
147
|
+
console.log(entity.enabled); // true
|
|
148
|
+
console.log(child.enabled); // true (inherits from parent)
|
|
149
|
+
|
|
150
|
+
// Disable parent - children are also disabled
|
|
151
|
+
entity.enabledSelf = false;
|
|
152
|
+
console.log(entity.enabled); // false
|
|
153
|
+
console.log(child.enabled); // false
|
|
154
|
+
|
|
155
|
+
// Re-enable
|
|
156
|
+
entity.enabledSelf = true;
|
|
157
|
+
console.log(child.enabled); // true
|
|
158
|
+
|
|
159
|
+
// Components respect entity enabled state
|
|
160
|
+
const component = new MyComponent(entity, { enabledSelf: true });
|
|
161
|
+
entity.enabledSelf = false;
|
|
162
|
+
console.log(component.enabled); // false (disabled because entity is disabled)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Handling Tick Events
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
class MovementComponent extends Component {
|
|
169
|
+
velocity: number = 5;
|
|
170
|
+
position: number = 0;
|
|
171
|
+
|
|
172
|
+
// Override the onTick virtual method
|
|
173
|
+
protected onTick(deltaTime: number): void {
|
|
174
|
+
// Update position based on deltaTime (in milliseconds)
|
|
175
|
+
this.position += this.velocity * (deltaTime / 1000);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Create entities and components
|
|
180
|
+
const root = new Entity({ id: 'root' });
|
|
181
|
+
const entity = new Entity({ id: 'moving-entity', parent: root });
|
|
182
|
+
const movement = new MovementComponent(entity);
|
|
183
|
+
|
|
184
|
+
// Tick the root entity (traverses the entire tree)
|
|
185
|
+
// This will call onTick on all enabled components
|
|
186
|
+
(root as any)._tick(16.67); // ~60fps deltaTime
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Handling Enabled State Changes
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
class AudioComponent extends Component {
|
|
193
|
+
// Override the onEnabledChanged virtual method
|
|
194
|
+
protected onEnabledChanged(newValue: boolean): void {
|
|
195
|
+
if (newValue) {
|
|
196
|
+
console.log('Component enabled, starting audio');
|
|
197
|
+
// Start audio playback
|
|
198
|
+
} else {
|
|
199
|
+
console.log('Component disabled, stopping audio');
|
|
200
|
+
// Stop audio playback
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const entity = new Entity({ id: 'audio-entity' });
|
|
206
|
+
const audio = new AudioComponent(entity);
|
|
207
|
+
|
|
208
|
+
// Disable the component
|
|
209
|
+
audio.enabledSelf = false; // Calls onEnabledChanged(false)
|
|
210
|
+
|
|
211
|
+
// Disable the entity (also calls onEnabledChanged for all components)
|
|
212
|
+
entity.enabledSelf = false;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Destroying Components
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const entity = new Entity({ id: 'entity' });
|
|
219
|
+
const component = new MyComponent(entity);
|
|
220
|
+
|
|
221
|
+
// Use the component...
|
|
222
|
+
component.doSomething();
|
|
223
|
+
|
|
224
|
+
// Destroy the component when no longer needed
|
|
225
|
+
component.destroy();
|
|
226
|
+
|
|
227
|
+
// CRITICAL: After destroy(), the component is "dead" and must not be accessed
|
|
228
|
+
// Remove all references and never use it again
|
|
229
|
+
// component = null; // or let it go out of scope
|
|
230
|
+
|
|
231
|
+
// ❌ WRONG - Never do this after destroy():
|
|
232
|
+
// component.doSomething(); // Undefined behavior - component is dead
|
|
233
|
+
// console.log(component.entity); // Undefined behavior - component is dead
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Important**: After calling `destroy()` on a component, you must remove all references to it and never access it again. The component is permanently removed from its entity and accessing it will result in undefined behavior.
|
|
237
|
+
|
|
238
|
+
### Traversing Entities and Components
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const root = new Entity({ id: 'root' });
|
|
242
|
+
// ... build tree structure ...
|
|
243
|
+
|
|
244
|
+
// Traverse all children
|
|
245
|
+
for (const child of root.traverseChildren()) {
|
|
246
|
+
console.log(`Child: ${child.id}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Traverse children with a filter
|
|
250
|
+
for (const enabledChild of root.traverseChildren(child => child.enabledSelf)) {
|
|
251
|
+
console.log(`Enabled child: ${enabledChild.id}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Traverse all components in the tree
|
|
255
|
+
for (const component of root.traverseComponents()) {
|
|
256
|
+
console.log(`Component: ${component.constructor.name}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Traverse specific component types with type safety
|
|
260
|
+
for (const transform of root.traverseComponents<TransformComponent>(
|
|
261
|
+
c => c instanceof TransformComponent
|
|
262
|
+
)) {
|
|
263
|
+
console.log(`Transform at entity: ${transform.entity.id}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Find specific entities/components with type safety
|
|
267
|
+
const player = root.getChild(e => e.id === 'player');
|
|
268
|
+
const transform = player?.getComponent<TransformComponent>(
|
|
269
|
+
c => c instanceof TransformComponent
|
|
270
|
+
);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Complete Example: Simple Game Scene
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { Entity, Component } from '@funderforge/ecs';
|
|
277
|
+
|
|
278
|
+
// Define components
|
|
279
|
+
class TransformComponent extends Component {
|
|
280
|
+
x: number = 0;
|
|
281
|
+
y: number = 0;
|
|
282
|
+
rotation: number = 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
class RenderComponent extends Component {
|
|
286
|
+
color: string = '#ffffff';
|
|
287
|
+
|
|
288
|
+
protected onTick(): void {
|
|
289
|
+
const transform = this.entity.getComponent<TransformComponent>(
|
|
290
|
+
c => c instanceof TransformComponent
|
|
291
|
+
);
|
|
292
|
+
if (transform) {
|
|
293
|
+
console.log(`Rendering at (${transform.x}, ${transform.y})`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
class PhysicsComponent extends Component {
|
|
299
|
+
velocityX: number = 0;
|
|
300
|
+
velocityY: number = 0;
|
|
301
|
+
|
|
302
|
+
protected onTick(deltaTime: number): void {
|
|
303
|
+
const transform = this.entity.getComponent<TransformComponent>(
|
|
304
|
+
c => c instanceof TransformComponent
|
|
305
|
+
);
|
|
306
|
+
if (transform) {
|
|
307
|
+
transform.x += this.velocityX * (deltaTime / 1000);
|
|
308
|
+
transform.y += this.velocityY * (deltaTime / 1000);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Create game scene
|
|
314
|
+
const scene = new Entity({ id: 'scene' });
|
|
315
|
+
|
|
316
|
+
// Create player entity
|
|
317
|
+
const player = new Entity({ id: 'player', parent: scene });
|
|
318
|
+
new TransformComponent(player);
|
|
319
|
+
new PhysicsComponent(player);
|
|
320
|
+
new RenderComponent(player);
|
|
321
|
+
|
|
322
|
+
// Create enemy entities
|
|
323
|
+
for (let i = 0; i < 5; i++) {
|
|
324
|
+
const enemy = new Entity({ id: `enemy-${i}`, parent: scene });
|
|
325
|
+
new TransformComponent(enemy);
|
|
326
|
+
new RenderComponent(enemy);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Game loop
|
|
330
|
+
function gameLoop() {
|
|
331
|
+
const deltaTime = 16.67; // ~60fps
|
|
332
|
+
(scene as any)._tick(deltaTime);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Run game loop
|
|
336
|
+
setInterval(gameLoop, 16.67);
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## API Reference
|
|
340
|
+
|
|
341
|
+
### Entity
|
|
342
|
+
|
|
343
|
+
#### Entity Constructor
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
new Entity(preset?: {
|
|
347
|
+
id?: string | number;
|
|
348
|
+
enabledSelf?: boolean;
|
|
349
|
+
parent?: Entity;
|
|
350
|
+
})
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### Entity Methods
|
|
354
|
+
|
|
355
|
+
- `addChild(child: Entity)`: Add a child entity
|
|
356
|
+
- `removeChild(child: Entity)`: Remove a child entity
|
|
357
|
+
- `setParent(parent: Entity | null)`: Set or clear the parent entity
|
|
358
|
+
- `childByIdx<T extends Entity = Entity>(index: number): T | undefined`: Get a child entity by index
|
|
359
|
+
- `getChild<T extends Entity = Entity>(predicate: (child: Entity) => boolean): T | undefined`: Find a child entity
|
|
360
|
+
- `getChildren<T extends Entity = Entity>(filter: (child: Entity) => boolean): T[]`: Get filtered children
|
|
361
|
+
- `traverseChildren<T extends Entity = Entity>(predicate?: (child: Entity) => boolean): Generator<T>`: Traverse all descendants
|
|
362
|
+
- `componentByIdx<T extends Component = Component>(index: number): T | undefined`: Get a component by index
|
|
363
|
+
- `getComponent<T extends Component = Component>(predicate: (component: Component) => boolean): T | undefined`: Find a component
|
|
364
|
+
- `getComponents<T extends Component = Component>(filter: (component: Component) => boolean): T[]`: Get filtered components
|
|
365
|
+
- `traverseComponents<T extends Component = Component>(predicate?: (component: Component) => boolean): Generator<T>`: Traverse all components in tree
|
|
366
|
+
- `hierarchyLevel()`: Get the depth level in the hierarchy
|
|
367
|
+
|
|
368
|
+
#### Entity Properties
|
|
369
|
+
|
|
370
|
+
- `id: string | number`: Unique identifier
|
|
371
|
+
- `enabledSelf: boolean`: Whether this entity is enabled (setter/getter)
|
|
372
|
+
- `enabled: boolean`: Whether this entity is enabled (considering parent state)
|
|
373
|
+
- `parent: Entity | null`: Parent entity reference
|
|
374
|
+
|
|
375
|
+
### Component
|
|
376
|
+
|
|
377
|
+
#### Component Constructor
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
new Component(entity: Entity, preset?: {
|
|
381
|
+
enabledSelf?: boolean;
|
|
382
|
+
precacheTypeLookup?: boolean;
|
|
383
|
+
})
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
#### Component Virtual Methods
|
|
387
|
+
|
|
388
|
+
- `protected onTick(deltaTime: number): void`: Override this method to handle tick events. Called during entity update cycles.
|
|
389
|
+
- `protected onEnabledChanged(newValue: boolean): void`: Override this method to respond to enabled state changes.
|
|
390
|
+
|
|
391
|
+
#### Component Methods
|
|
392
|
+
|
|
393
|
+
- `destroy(): void`: Destroys this component, removing it from its entity. **After calling `destroy()`, the component is considered "dead" and must not be accessed or used in any way. Remove all references to the component and never access its properties or methods again.**
|
|
394
|
+
|
|
395
|
+
#### Component Properties
|
|
396
|
+
|
|
397
|
+
- `entity: Entity`: The entity this component belongs to
|
|
398
|
+
- `enabledSelf: boolean`: Whether this component is enabled (setter/getter)
|
|
399
|
+
- `enabled: boolean`: Whether this component is enabled (considering entity state)
|
|
400
|
+
|
|
401
|
+
## Performance
|
|
402
|
+
|
|
403
|
+
The framework is optimized for performance:
|
|
404
|
+
|
|
405
|
+
- **Cached Property Access**: The `enabled` property is cached and only recomputed when necessary
|
|
406
|
+
- **Efficient Traversal**: Uses stack-based traversal algorithms that minimize allocations
|
|
407
|
+
- **Selective Processing**: Disabled entities and components are skipped during traversal
|
|
408
|
+
- **Method-Based Architecture**: Direct method calls instead of event system overhead
|
|
409
|
+
|
|
410
|
+
## License
|
|
411
|
+
|
|
412
|
+
MIT
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type Entity from "./entity";
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base class for components that can be attached to entities.
|
|
4
|
+
* Components provide functionality to entities and can respond to lifecycle events through virtual methods.
|
|
5
|
+
*
|
|
6
|
+
* Key features:
|
|
7
|
+
* - Belongs to exactly one entity
|
|
8
|
+
* - Method-based architecture with `onTick` and `onEnabledChanged` virtual methods
|
|
9
|
+
* - Enabled/disabled state that respects entity state
|
|
10
|
+
* - Automatic registration with the entity upon construction
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* class TransformComponent extends Component {
|
|
15
|
+
* x = 0;
|
|
16
|
+
* y = 0;
|
|
17
|
+
*
|
|
18
|
+
* protected onTick(deltaTime: number): void {
|
|
19
|
+
* // Update position based on deltaTime
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* const entity = new Entity({ id: 'player' });
|
|
24
|
+
* const transform = new TransformComponent(entity);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export default abstract class Component {
|
|
28
|
+
/** The entity this component belongs to. Set automatically during construction and cannot be changed. */
|
|
29
|
+
readonly entity: Entity;
|
|
30
|
+
private _enabledSelf;
|
|
31
|
+
/**
|
|
32
|
+
* Whether to add this component to the entity's type map for optimized lookups.
|
|
33
|
+
*
|
|
34
|
+
* - `false` (default): Component is NOT added to the type map. Lookups will fall back to iteration, which is slower
|
|
35
|
+
* but avoids the overhead of maintaining the map for components that are rarely looked up by type.
|
|
36
|
+
* - `true`: Component is added to the type map, enabling fast O(1) lookups via `entity.getComponentByType()`.
|
|
37
|
+
*
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
readonly precacheTypeLookup: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new Component instance and attaches it to the specified entity.
|
|
43
|
+
* The component is automatically registered with the entity and cannot be moved to another entity.
|
|
44
|
+
*
|
|
45
|
+
* @param entity - The entity this component belongs to. Required and cannot be changed after construction.
|
|
46
|
+
* @param preset - Optional configuration object for the component.
|
|
47
|
+
* @param preset.enabledSelf - Whether the component is enabled by default. Defaults to `true`.
|
|
48
|
+
* @param preset.precacheTypeLookup - Whether to add this component to the entity's type map for optimized lookups.
|
|
49
|
+
* Defaults to `false` (component is NOT added to map, uses slower iteration-based lookups). Set to `true` to add
|
|
50
|
+
* the component to the map for fast O(1) lookups via `entity.getComponentByType()`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* class MyComponent extends Component {
|
|
55
|
+
* constructor(entity: Entity) {
|
|
56
|
+
* super(entity, { enabledSelf: true });
|
|
57
|
+
* }
|
|
58
|
+
* }
|
|
59
|
+
*
|
|
60
|
+
* const entity = new Entity({ id: 'entity' });
|
|
61
|
+
* const component = new MyComponent(entity);
|
|
62
|
+
*
|
|
63
|
+
* // Component is NOT added to type map by default (slower lookups)
|
|
64
|
+
* const found = entity.getComponentByType(MyComponent);
|
|
65
|
+
*
|
|
66
|
+
* // Add to type map for components frequently looked up by type (fast lookups)
|
|
67
|
+
* const transform = new TransformComponent(entity, { precacheTypeLookup: true });
|
|
68
|
+
* const foundTransform = entity.getComponentByType(TransformComponent); // Fast O(1) lookup
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
constructor(entity: Entity, preset?: {
|
|
72
|
+
enabledSelf?: boolean;
|
|
73
|
+
precacheTypeLookup?: boolean;
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Gets whether this component itself is enabled, independent of its entity's state.
|
|
77
|
+
* @returns `true` if this component is enabled, `false` otherwise.
|
|
78
|
+
*/
|
|
79
|
+
get enabledSelf(): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Sets whether this component itself is enabled. When the effective enabled state changes (considering both this value and the entity's enabled state),
|
|
82
|
+
* the `onEnabledChanged` method will be called.
|
|
83
|
+
* @param value - `true` to enable this component, `false` to disable it.
|
|
84
|
+
*/
|
|
85
|
+
set enabledSelf(value: boolean);
|
|
86
|
+
/**
|
|
87
|
+
* Virtual method called when the component is ticked during the entity update cycle.
|
|
88
|
+
* Override this method in derived classes to implement tick-based logic.
|
|
89
|
+
* @param deltaTime - The time elapsed since the last tick in milliseconds.
|
|
90
|
+
*/
|
|
91
|
+
protected onTick(_deltaTime: number): void;
|
|
92
|
+
/**
|
|
93
|
+
* Virtual method called when the component's effective enabled state changes.
|
|
94
|
+
* Override this method in derived classes to respond to enabled state changes.
|
|
95
|
+
* @param newValue - The new effective enabled state of the component.
|
|
96
|
+
*/
|
|
97
|
+
protected onEnabledChanged(_newValue: boolean): void;
|
|
98
|
+
/**
|
|
99
|
+
* Gets the effective enabled state of this component, considering both its own `enabledSelf` value and its entity's enabled state.
|
|
100
|
+
* @returns `true` only if both this component's `enabledSelf` is `true` and its entity's `enabled` is `true`.
|
|
101
|
+
*/
|
|
102
|
+
get enabled(): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Destroys this component, removing it from its entity and marking it as "dead".
|
|
105
|
+
*
|
|
106
|
+
* After calling `destroy()`, the component is permanently removed from the entity's component list
|
|
107
|
+
* and its entity reference is cleared. The component should be considered "dead" and must not be
|
|
108
|
+
* accessed or used in any way after destruction.
|
|
109
|
+
*
|
|
110
|
+
* **CRITICAL**: After calling `destroy()`, you must:
|
|
111
|
+
* - Remove all references to this component from your code
|
|
112
|
+
* - Never access the component's properties or methods again
|
|
113
|
+
* - Never call any methods on the component
|
|
114
|
+
*
|
|
115
|
+
* Accessing a destroyed component will result in undefined behavior. In non-production builds,
|
|
116
|
+
* the component object is frozen to help detect misuse.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* const component = new MyComponent(entity);
|
|
121
|
+
*
|
|
122
|
+
* // Use the component...
|
|
123
|
+
* component.doSomething();
|
|
124
|
+
*
|
|
125
|
+
* // Destroy the component when no longer needed
|
|
126
|
+
* component.destroy();
|
|
127
|
+
*
|
|
128
|
+
* // IMPORTANT: Remove all references and never access it again
|
|
129
|
+
* // component = null; // or let it go out of scope
|
|
130
|
+
* // DO NOT: component.doSomething(); // ❌ WRONG - component is dead
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
destroy(): void;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,OAAO,CAAC,QAAQ,OAAO,SAAS;IACtC,yGAAyG;IACzG,QAAQ,CAAC,MAAM,EAAG,MAAM,CAAC;IACzB,OAAO,CAAC,YAAY,CAAU;IAE9B;;;;;;;;OAQG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;gBAEF,MAAM,EAAE,MAAM,EACd,MAAM,GAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAO;IAoBrE;;;OAGG;IACH,IAAI,WAAW,IASQ,OAAO,CAP7B;IAED;;;;OAIG;IACH,IAAI,WAAW,CAAC,KAAK,EAAE,OAAO,EAQ7B;IAED;;;;OAIG;IACH,SAAS,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAE1C;;;;OAIG;IACH,SAAS,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI;IAEpD;;;OAGG;IACH,IAAI,OAAO,YAEV;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,OAAO,IAAI,IAAI;CAoBf"}
|