ecspresso 0.6.0 → 0.7.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/README.md +187 -0
- package/dist/bundles/renderers/pixi.d.ts +14 -27
- package/dist/bundles/renderers/pixi.js +2 -2
- package/dist/bundles/renderers/pixi.js.map +5 -4
- package/dist/bundles/utils/bounds.d.ts +186 -0
- package/dist/bundles/utils/collision.d.ts +201 -0
- package/dist/bundles/utils/movement.d.ts +83 -0
- package/dist/bundles/utils/timers.d.ts +67 -11
- package/dist/bundles/utils/timers.js +2 -2
- package/dist/bundles/utils/timers.js.map +3 -3
- package/dist/bundles/utils/transform.d.ts +148 -0
- package/dist/command-buffer.d.ts +90 -0
- package/dist/ecspresso.d.ts +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +6 -5
- package/package.json +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds Bundle for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Provides screen bounds enforcement for entities with transforms.
|
|
5
|
+
* Reads worldTransform for position checking; modifies localTransform for corrections.
|
|
6
|
+
* Supports destroy, clamp, and wrap behaviors.
|
|
7
|
+
*/
|
|
8
|
+
import Bundle from '../../bundle';
|
|
9
|
+
import type { TransformComponentTypes } from './transform';
|
|
10
|
+
/**
|
|
11
|
+
* Component that marks an entity for destruction when outside bounds.
|
|
12
|
+
*/
|
|
13
|
+
export interface DestroyOutOfBounds {
|
|
14
|
+
/** Extra padding beyond bounds before destruction (default: 0) */
|
|
15
|
+
padding?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Component that clamps an entity's position to stay within bounds.
|
|
19
|
+
*/
|
|
20
|
+
export interface ClampToBounds {
|
|
21
|
+
/** Margin to shrink the valid area (default: 0) */
|
|
22
|
+
margin?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Component that wraps an entity's position to the opposite edge.
|
|
26
|
+
*/
|
|
27
|
+
export interface WrapAtBounds {
|
|
28
|
+
/** Padding beyond bounds before wrapping (default: 0) */
|
|
29
|
+
padding?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Component types provided by the bounds bundle.
|
|
33
|
+
* Extend your component types with this interface.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* interface GameComponents extends TransformComponentTypes, BoundsComponentTypes {
|
|
38
|
+
* sprite: Sprite;
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export interface BoundsComponentTypes {
|
|
43
|
+
destroyOutOfBounds: DestroyOutOfBounds;
|
|
44
|
+
clampToBounds: ClampToBounds;
|
|
45
|
+
wrapAtBounds: WrapAtBounds;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Bounds rectangle definition.
|
|
49
|
+
*/
|
|
50
|
+
export interface BoundsRect {
|
|
51
|
+
/** Left edge x coordinate (default: 0) */
|
|
52
|
+
x?: number;
|
|
53
|
+
/** Top edge y coordinate (default: 0) */
|
|
54
|
+
y?: number;
|
|
55
|
+
/** Width of the bounds area */
|
|
56
|
+
width: number;
|
|
57
|
+
/** Height of the bounds area */
|
|
58
|
+
height: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resource types provided by the bounds bundle.
|
|
62
|
+
*/
|
|
63
|
+
export interface BoundsResourceTypes {
|
|
64
|
+
bounds: BoundsRect;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Event fired when an entity exits bounds.
|
|
68
|
+
*/
|
|
69
|
+
export interface EntityOutOfBoundsEvent {
|
|
70
|
+
/** The entity that exited bounds */
|
|
71
|
+
entityId: number;
|
|
72
|
+
/** The edge the entity exited through */
|
|
73
|
+
exitEdge: 'top' | 'bottom' | 'left' | 'right';
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Event types provided by the bounds bundle.
|
|
77
|
+
*/
|
|
78
|
+
export interface BoundsEventTypes {
|
|
79
|
+
entityOutOfBounds: EntityOutOfBoundsEvent;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Configuration options for the bounds bundle.
|
|
83
|
+
*/
|
|
84
|
+
export interface BoundsBundleOptions {
|
|
85
|
+
/** System group name (default: 'physics') */
|
|
86
|
+
systemGroup?: string;
|
|
87
|
+
/** Priority for bounds systems (default: 50) */
|
|
88
|
+
priority?: number;
|
|
89
|
+
/** Resource key for bounds rectangle (default: 'bounds') */
|
|
90
|
+
boundsResourceKey?: string;
|
|
91
|
+
/** Whether to auto-remove entities when out of bounds (default: true) */
|
|
92
|
+
autoRemove?: boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create a bounds rectangle resource.
|
|
96
|
+
*
|
|
97
|
+
* @param width The width of the bounds area
|
|
98
|
+
* @param height The height of the bounds area
|
|
99
|
+
* @param x The left edge x coordinate (default: 0)
|
|
100
|
+
* @param y The top edge y coordinate (default: 0)
|
|
101
|
+
* @returns Bounds rectangle suitable for use as a resource
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* ECSpresso.create()
|
|
106
|
+
* .withResource('bounds', createBounds(800, 600))
|
|
107
|
+
* .build();
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function createBounds(width: number, height: number, x?: number, y?: number): BoundsRect;
|
|
111
|
+
/**
|
|
112
|
+
* Create a destroyOutOfBounds component.
|
|
113
|
+
*
|
|
114
|
+
* @param padding Extra padding beyond bounds before destruction
|
|
115
|
+
* @returns Component object suitable for spreading into spawn()
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* ecs.spawn({
|
|
120
|
+
* ...createTransform(100, 200),
|
|
121
|
+
* ...createDestroyOutOfBounds(20),
|
|
122
|
+
* });
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export declare function createDestroyOutOfBounds(padding?: number): Pick<BoundsComponentTypes, 'destroyOutOfBounds'>;
|
|
126
|
+
/**
|
|
127
|
+
* Create a clampToBounds component.
|
|
128
|
+
*
|
|
129
|
+
* @param margin Margin to shrink the valid area
|
|
130
|
+
* @returns Component object suitable for spreading into spawn()
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* ecs.spawn({
|
|
135
|
+
* ...createTransform(100, 200),
|
|
136
|
+
* ...createClampToBounds(30),
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare function createClampToBounds(margin?: number): Pick<BoundsComponentTypes, 'clampToBounds'>;
|
|
141
|
+
/**
|
|
142
|
+
* Create a wrapAtBounds component.
|
|
143
|
+
*
|
|
144
|
+
* @param padding Padding beyond bounds before wrapping
|
|
145
|
+
* @returns Component object suitable for spreading into spawn()
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* ecs.spawn({
|
|
150
|
+
* ...createTransform(100, 200),
|
|
151
|
+
* ...createWrapAtBounds(10),
|
|
152
|
+
* });
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export declare function createWrapAtBounds(padding?: number): Pick<BoundsComponentTypes, 'wrapAtBounds'>;
|
|
156
|
+
type CombinedComponentTypes = BoundsComponentTypes & TransformComponentTypes;
|
|
157
|
+
/**
|
|
158
|
+
* Create a bounds bundle for ECSpresso.
|
|
159
|
+
*
|
|
160
|
+
* This bundle provides:
|
|
161
|
+
* - Destroy out of bounds system - removes entities that exit bounds
|
|
162
|
+
* - Clamp to bounds system - constrains entities within bounds
|
|
163
|
+
* - Wrap at bounds system - wraps entities to opposite edge
|
|
164
|
+
*
|
|
165
|
+
* Uses worldTransform for position checking (world-space) and modifies
|
|
166
|
+
* localTransform for corrections. Works best with entities that don't
|
|
167
|
+
* have parent transforms (orphan entities).
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* const ecs = ECSpresso
|
|
172
|
+
* .create<Components, Events, Resources>()
|
|
173
|
+
* .withResource('bounds', createBounds(800, 600))
|
|
174
|
+
* .withBundle(createTransformBundle())
|
|
175
|
+
* .withBundle(createBoundsBundle())
|
|
176
|
+
* .build();
|
|
177
|
+
*
|
|
178
|
+
* // Entity that gets destroyed when leaving screen
|
|
179
|
+
* ecs.spawn({
|
|
180
|
+
* ...createTransform(100, 200),
|
|
181
|
+
* ...createDestroyOutOfBounds(),
|
|
182
|
+
* });
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
export declare function createBoundsBundle<ResourceTypes extends BoundsResourceTypes = BoundsResourceTypes>(options?: BoundsBundleOptions): Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes>;
|
|
186
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collision Bundle for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Provides layer-based collision detection with events.
|
|
5
|
+
* Uses worldTransform for position (world-space collision).
|
|
6
|
+
* Supports AABB and circle colliders.
|
|
7
|
+
*/
|
|
8
|
+
import Bundle from '../../bundle';
|
|
9
|
+
import type { TransformComponentTypes } from './transform';
|
|
10
|
+
/**
|
|
11
|
+
* Axis-Aligned Bounding Box collider.
|
|
12
|
+
*/
|
|
13
|
+
export interface AABBCollider {
|
|
14
|
+
/** Width of the bounding box */
|
|
15
|
+
width: number;
|
|
16
|
+
/** Height of the bounding box */
|
|
17
|
+
height: number;
|
|
18
|
+
/** X offset from entity position (default: 0) */
|
|
19
|
+
offsetX?: number;
|
|
20
|
+
/** Y offset from entity position (default: 0) */
|
|
21
|
+
offsetY?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Circle collider.
|
|
25
|
+
*/
|
|
26
|
+
export interface CircleCollider {
|
|
27
|
+
/** Radius of the circle */
|
|
28
|
+
radius: number;
|
|
29
|
+
/** X offset from entity position (default: 0) */
|
|
30
|
+
offsetX?: number;
|
|
31
|
+
/** Y offset from entity position (default: 0) */
|
|
32
|
+
offsetY?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Collision layer configuration.
|
|
36
|
+
*/
|
|
37
|
+
export interface CollisionLayer {
|
|
38
|
+
/** The layer this entity belongs to */
|
|
39
|
+
layer: string;
|
|
40
|
+
/** Layers this entity can collide with */
|
|
41
|
+
collidesWith: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Component types provided by the collision bundle.
|
|
45
|
+
* Extend your component types with this interface.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* interface GameComponents extends TransformComponentTypes, CollisionComponentTypes {
|
|
50
|
+
* sprite: Sprite;
|
|
51
|
+
* enemy: boolean;
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export interface CollisionComponentTypes {
|
|
56
|
+
aabbCollider: AABBCollider;
|
|
57
|
+
circleCollider: CircleCollider;
|
|
58
|
+
collisionLayer: CollisionLayer;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Event fired when two entities collide.
|
|
62
|
+
*/
|
|
63
|
+
export interface CollisionEvent {
|
|
64
|
+
/** First entity in the collision */
|
|
65
|
+
entityA: number;
|
|
66
|
+
/** Second entity in the collision */
|
|
67
|
+
entityB: number;
|
|
68
|
+
/** Layer of the first entity */
|
|
69
|
+
layerA: string;
|
|
70
|
+
/** Layer of the second entity */
|
|
71
|
+
layerB: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Event types provided by the collision bundle.
|
|
75
|
+
*/
|
|
76
|
+
export interface CollisionEventTypes {
|
|
77
|
+
collision: CollisionEvent;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Configuration options for the collision bundle.
|
|
81
|
+
*/
|
|
82
|
+
export interface CollisionBundleOptions {
|
|
83
|
+
/** System group name (default: 'physics') */
|
|
84
|
+
systemGroup?: string;
|
|
85
|
+
/** Priority for collision system (default: 0) */
|
|
86
|
+
priority?: number;
|
|
87
|
+
/** Name of the collision event (default: 'collision') */
|
|
88
|
+
collisionEventName?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create an AABB collider component.
|
|
92
|
+
*
|
|
93
|
+
* @param width Width of the bounding box
|
|
94
|
+
* @param height Height of the bounding box
|
|
95
|
+
* @param offsetX X offset from entity position
|
|
96
|
+
* @param offsetY Y offset from entity position
|
|
97
|
+
* @returns Component object suitable for spreading into spawn()
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* ecs.spawn({
|
|
102
|
+
* ...createTransform(100, 200),
|
|
103
|
+
* ...createAABBCollider(50, 30),
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export declare function createAABBCollider(width: number, height: number, offsetX?: number, offsetY?: number): Pick<CollisionComponentTypes, 'aabbCollider'>;
|
|
108
|
+
/**
|
|
109
|
+
* Create a circle collider component.
|
|
110
|
+
*
|
|
111
|
+
* @param radius Radius of the circle
|
|
112
|
+
* @param offsetX X offset from entity position
|
|
113
|
+
* @param offsetY Y offset from entity position
|
|
114
|
+
* @returns Component object suitable for spreading into spawn()
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* ecs.spawn({
|
|
119
|
+
* ...createTransform(100, 200),
|
|
120
|
+
* ...createCircleCollider(25),
|
|
121
|
+
* });
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export declare function createCircleCollider(radius: number, offsetX?: number, offsetY?: number): Pick<CollisionComponentTypes, 'circleCollider'>;
|
|
125
|
+
/**
|
|
126
|
+
* Create a collision layer component.
|
|
127
|
+
*
|
|
128
|
+
* @param layer The layer this entity belongs to
|
|
129
|
+
* @param collidesWith Layers this entity can collide with
|
|
130
|
+
* @returns Component object suitable for spreading into spawn()
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* ecs.spawn({
|
|
135
|
+
* ...createTransform(100, 200),
|
|
136
|
+
* ...createAABBCollider(50, 30),
|
|
137
|
+
* ...createCollisionLayer('player', ['enemy', 'obstacle']),
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export declare function createCollisionLayer(layer: string, collidesWith: readonly string[]): Pick<CollisionComponentTypes, 'collisionLayer'>;
|
|
142
|
+
/**
|
|
143
|
+
* Layer factory result from defineCollisionLayers.
|
|
144
|
+
*/
|
|
145
|
+
export type LayerFactories<T extends Record<string, readonly string[]>> = {
|
|
146
|
+
[K in keyof T]: () => Pick<CollisionComponentTypes, 'collisionLayer'>;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Define collision layer relationships and get factory functions.
|
|
150
|
+
*
|
|
151
|
+
* @param rules Object mapping layer names to arrays of layers they collide with
|
|
152
|
+
* @returns Object with factory functions for each layer
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* const layers = defineCollisionLayers({
|
|
157
|
+
* player: ['enemy', 'enemyProjectile'],
|
|
158
|
+
* playerProjectile: ['enemy'],
|
|
159
|
+
* enemy: ['playerProjectile'],
|
|
160
|
+
* enemyProjectile: ['player'],
|
|
161
|
+
* });
|
|
162
|
+
*
|
|
163
|
+
* // Usage
|
|
164
|
+
* ecs.spawn({
|
|
165
|
+
* ...createTransform(100, 200),
|
|
166
|
+
* ...createAABBCollider(50, 30),
|
|
167
|
+
* ...layers.player(),
|
|
168
|
+
* });
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export declare function defineCollisionLayers<T extends Record<string, readonly string[]>>(rules: T): LayerFactories<T>;
|
|
172
|
+
type CombinedComponentTypes = CollisionComponentTypes & TransformComponentTypes;
|
|
173
|
+
/**
|
|
174
|
+
* Create a collision bundle for ECSpresso.
|
|
175
|
+
*
|
|
176
|
+
* This bundle provides:
|
|
177
|
+
* - O(n²) collision detection between entities with colliders
|
|
178
|
+
* - AABB-AABB, circle-circle, and AABB-circle collision
|
|
179
|
+
* - Layer-based filtering for collision pairs
|
|
180
|
+
* - Deduplication of A-B / B-A collisions
|
|
181
|
+
*
|
|
182
|
+
* Uses worldTransform for position (world-space collision detection).
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* const ecs = ECSpresso
|
|
187
|
+
* .create<Components, Events, Resources>()
|
|
188
|
+
* .withBundle(createTransformBundle())
|
|
189
|
+
* .withBundle(createCollisionBundle())
|
|
190
|
+
* .build();
|
|
191
|
+
*
|
|
192
|
+
* // Entity with collision
|
|
193
|
+
* ecs.spawn({
|
|
194
|
+
* ...createTransform(100, 200),
|
|
195
|
+
* ...createAABBCollider(50, 30),
|
|
196
|
+
* ...createCollisionLayer('player', ['enemy']),
|
|
197
|
+
* });
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
export declare function createCollisionBundle(options?: CollisionBundleOptions): Bundle<CombinedComponentTypes, CollisionEventTypes, {}>;
|
|
201
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Movement Bundle for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Provides velocity → localTransform integration for entities.
|
|
5
|
+
* Works with the transform bundle's localTransform/worldTransform system.
|
|
6
|
+
*/
|
|
7
|
+
import Bundle from '../../bundle';
|
|
8
|
+
import type { TransformComponentTypes } from './transform';
|
|
9
|
+
/**
|
|
10
|
+
* Velocity component data structure.
|
|
11
|
+
*/
|
|
12
|
+
export interface Velocity {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Component types provided by the movement bundle.
|
|
18
|
+
* Extend your component types with this interface.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* interface GameComponents extends TransformComponentTypes, MovementComponentTypes {
|
|
23
|
+
* sprite: Sprite;
|
|
24
|
+
* player: boolean;
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export interface MovementComponentTypes extends TransformComponentTypes {
|
|
29
|
+
velocity: Velocity;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Configuration options for the movement bundle.
|
|
33
|
+
*/
|
|
34
|
+
export interface MovementBundleOptions {
|
|
35
|
+
/** System group name (default: 'physics') */
|
|
36
|
+
systemGroup?: string;
|
|
37
|
+
/** Priority for movement update system (default: 1000, runs early before transform propagation) */
|
|
38
|
+
priority?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a velocity component.
|
|
42
|
+
*
|
|
43
|
+
* @param x The x velocity
|
|
44
|
+
* @param y The y velocity
|
|
45
|
+
* @returns Component object suitable for spreading into spawn()
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* ecs.spawn({
|
|
50
|
+
* ...createTransform(100, 200),
|
|
51
|
+
* ...createVelocity(50, -25),
|
|
52
|
+
* projectile: true,
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function createVelocity(x: number, y: number): Pick<MovementComponentTypes, 'velocity'>;
|
|
57
|
+
/**
|
|
58
|
+
* Create a movement bundle for ECSpresso.
|
|
59
|
+
*
|
|
60
|
+
* This bundle provides:
|
|
61
|
+
* - Movement update system that integrates velocity into localTransform
|
|
62
|
+
* - Processes all entities with both localTransform and velocity components
|
|
63
|
+
*
|
|
64
|
+
* Note: This bundle modifies localTransform. The transform bundle's propagation
|
|
65
|
+
* system will then compute worldTransform for use by other systems.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const ecs = ECSpresso
|
|
70
|
+
* .create<Components, Events, Resources>()
|
|
71
|
+
* .withBundle(createTransformBundle())
|
|
72
|
+
* .withBundle(createMovementBundle())
|
|
73
|
+
* .build();
|
|
74
|
+
*
|
|
75
|
+
* // Spawn entity with movement
|
|
76
|
+
* ecs.spawn({
|
|
77
|
+
* ...createTransform(100, 200),
|
|
78
|
+
* ...createVelocity(50, -25),
|
|
79
|
+
* sprite,
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function createMovementBundle(options?: MovementBundleOptions): Bundle<MovementComponentTypes, {}, {}>;
|
|
@@ -5,11 +5,40 @@
|
|
|
5
5
|
* Timers are components processed each frame, automatically cleaned up when entities are removed.
|
|
6
6
|
*/
|
|
7
7
|
import Bundle from '../../bundle';
|
|
8
|
+
/**
|
|
9
|
+
* Data structure published when a timer completes.
|
|
10
|
+
* Use this type when defining timer completion events in your EventTypes interface.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* interface Events {
|
|
15
|
+
* hideMessage: TimerEventData;
|
|
16
|
+
* spawnWave: TimerEventData;
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export interface TimerEventData {
|
|
21
|
+
/** The entity ID that the timer belongs to */
|
|
22
|
+
entityId: number;
|
|
23
|
+
/** The timer's configured duration in seconds */
|
|
24
|
+
duration: number;
|
|
25
|
+
/** The actual elapsed time (may exceed duration slightly) */
|
|
26
|
+
elapsed: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Extracts event names from EventTypes that have TimerEventData as their payload.
|
|
30
|
+
* This ensures only compatible events can be used with timer.onComplete.
|
|
31
|
+
*/
|
|
32
|
+
export type TimerEventName<EventTypes extends Record<string, any>> = {
|
|
33
|
+
[K in keyof EventTypes]: EventTypes[K] extends TimerEventData ? K : never;
|
|
34
|
+
}[keyof EventTypes];
|
|
8
35
|
/**
|
|
9
36
|
* Timer component data structure.
|
|
10
37
|
* Use `justFinished` to detect timer completion in your systems.
|
|
38
|
+
*
|
|
39
|
+
* @template EventTypes The event types from your ECS
|
|
11
40
|
*/
|
|
12
|
-
export interface Timer {
|
|
41
|
+
export interface Timer<EventTypes extends Record<string, any>> {
|
|
13
42
|
/** Time accumulated so far (seconds) */
|
|
14
43
|
elapsed: number;
|
|
15
44
|
/** Target duration (seconds) */
|
|
@@ -20,21 +49,25 @@ export interface Timer {
|
|
|
20
49
|
active: boolean;
|
|
21
50
|
/** True for one frame after timer completes */
|
|
22
51
|
justFinished: boolean;
|
|
52
|
+
/** Optional event name to publish when timer completes. Must be an event with TimerEventData payload. */
|
|
53
|
+
onComplete?: TimerEventName<EventTypes>;
|
|
23
54
|
}
|
|
24
55
|
/**
|
|
25
56
|
* Component types provided by the timer bundle.
|
|
26
57
|
* Extend your component types with this interface.
|
|
27
58
|
*
|
|
59
|
+
* @template EventTypes The event types from your ECS
|
|
60
|
+
*
|
|
28
61
|
* @example
|
|
29
62
|
* ```typescript
|
|
30
|
-
* interface GameComponents extends TimerComponentTypes {
|
|
63
|
+
* interface GameComponents extends TimerComponentTypes<GameEvents> {
|
|
31
64
|
* velocity: { x: number; y: number };
|
|
32
65
|
* player: true;
|
|
33
66
|
* }
|
|
34
67
|
* ```
|
|
35
68
|
*/
|
|
36
|
-
export interface TimerComponentTypes {
|
|
37
|
-
timer: Timer
|
|
69
|
+
export interface TimerComponentTypes<EventTypes extends Record<string, any>> {
|
|
70
|
+
timer: Timer<EventTypes>;
|
|
38
71
|
}
|
|
39
72
|
/**
|
|
40
73
|
* Configuration options for the timer bundle.
|
|
@@ -45,38 +78,61 @@ export interface TimerBundleOptions {
|
|
|
45
78
|
/** Priority for timer update system (default: 0) */
|
|
46
79
|
priority?: number;
|
|
47
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Options for timer creation
|
|
83
|
+
*
|
|
84
|
+
* @template EventTypes The event types from your ECS
|
|
85
|
+
*/
|
|
86
|
+
export interface TimerOptions<EventTypes extends Record<string, any>> {
|
|
87
|
+
/** Event name to publish when timer completes. Must be an event with TimerEventData payload. */
|
|
88
|
+
onComplete?: TimerEventName<EventTypes>;
|
|
89
|
+
}
|
|
48
90
|
/**
|
|
49
91
|
* Create a one-shot timer that fires once after the specified duration.
|
|
50
92
|
*
|
|
93
|
+
* @template EventTypes The event types from your ECS (must be explicitly provided)
|
|
51
94
|
* @param duration Duration in seconds until the timer completes
|
|
95
|
+
* @param options Optional configuration including event name
|
|
52
96
|
* @returns Component object suitable for spreading into spawn()
|
|
53
97
|
*
|
|
54
98
|
* @example
|
|
55
99
|
* ```typescript
|
|
56
|
-
* // Timer
|
|
100
|
+
* // Timer without event
|
|
57
101
|
* ecs.spawn({
|
|
58
|
-
* ...createTimer(2),
|
|
102
|
+
* ...createTimer<GameEvents>(2),
|
|
59
103
|
* explosion: true,
|
|
60
104
|
* });
|
|
105
|
+
*
|
|
106
|
+
* // Timer that publishes an event on completion
|
|
107
|
+
* ecs.spawn({
|
|
108
|
+
* ...createTimer<GameEvents>(1.5, { onComplete: 'hideMessage' }),
|
|
109
|
+
* });
|
|
61
110
|
* ```
|
|
62
111
|
*/
|
|
63
|
-
export declare function createTimer(duration: number): Pick<TimerComponentTypes
|
|
112
|
+
export declare function createTimer<EventTypes extends Record<string, any>>(duration: number, options?: TimerOptions<EventTypes>): Pick<TimerComponentTypes<EventTypes>, 'timer'>;
|
|
64
113
|
/**
|
|
65
114
|
* Create a repeating timer that fires every `duration` seconds.
|
|
66
115
|
*
|
|
116
|
+
* @template EventTypes The event types from your ECS (must be explicitly provided)
|
|
67
117
|
* @param duration Duration in seconds between each timer completion
|
|
118
|
+
* @param options Optional configuration including event name
|
|
68
119
|
* @returns Component object suitable for spreading into spawn()
|
|
69
120
|
*
|
|
70
121
|
* @example
|
|
71
122
|
* ```typescript
|
|
72
|
-
* // Timer
|
|
123
|
+
* // Timer without event
|
|
73
124
|
* ecs.spawn({
|
|
74
|
-
* ...createRepeatingTimer(5),
|
|
125
|
+
* ...createRepeatingTimer<GameEvents>(5),
|
|
75
126
|
* spawner: true,
|
|
76
127
|
* });
|
|
128
|
+
*
|
|
129
|
+
* // Repeating timer that publishes an event each cycle
|
|
130
|
+
* ecs.spawn({
|
|
131
|
+
* ...createRepeatingTimer<GameEvents>(3, { onComplete: 'spawnWave' }),
|
|
132
|
+
* });
|
|
77
133
|
* ```
|
|
78
134
|
*/
|
|
79
|
-
export declare function createRepeatingTimer(duration: number): Pick<TimerComponentTypes
|
|
135
|
+
export declare function createRepeatingTimer<EventTypes extends Record<string, any>>(duration: number, options?: TimerOptions<EventTypes>): Pick<TimerComponentTypes<EventTypes>, 'timer'>;
|
|
80
136
|
/**
|
|
81
137
|
* Create a timer bundle for ECSpresso.
|
|
82
138
|
*
|
|
@@ -110,4 +166,4 @@ export declare function createRepeatingTimer(duration: number): Pick<TimerCompon
|
|
|
110
166
|
* });
|
|
111
167
|
* ```
|
|
112
168
|
*/
|
|
113
|
-
export declare function createTimerBundle(options?: TimerBundleOptions): Bundle<TimerComponentTypes
|
|
169
|
+
export declare function createTimerBundle<EventTypes extends Record<string, any>>(options?: TimerBundleOptions): Bundle<TimerComponentTypes<EventTypes>, EventTypes, {}>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var $=Object.create;var{getPrototypeOf:G,defineProperty:Z,getOwnPropertyNames:q}=Object;var z=Object.prototype.hasOwnProperty;var P=(j,x,F)=>{F=j!=null?$(G(j)):{};let K=x||!j||!j.__esModule?Z(F,"default",{value:j,enumerable:!0}):F;for(let H of q(j))if(!z.call(K,H))Z(K,H,{get:()=>j[H],enumerable:!0});return K};var W=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(x,F)=>(typeof require<"u"?require:x)[F]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});class Y{_label;_ecspresso;_bundle;queries={};processFunction;detachFunction;initializeFunction;eventHandlers;_priority=0;_isRegistered=!1;_groups=[];_inScreens;_excludeScreens;_requiredAssets;constructor(j,x=null,F=null){this._label=j;this._ecspresso=x;this._bundle=F}get label(){return this._label}get bundle(){return this._bundle}get ecspresso(){return this._ecspresso}_autoRegister(){if(this._isRegistered||!this._ecspresso)return;let j=this._buildSystemObject();X(j,this._ecspresso),this._isRegistered=!0}_buildSystemObject(){return this._createSystemObject()}_createSystemObject(){let j={label:this._label,entityQueries:this.queries,priority:this._priority};if(this.processFunction)j.process=this.processFunction;if(this.detachFunction)j.onDetach=this.detachFunction;if(this.initializeFunction)j.onInitialize=this.initializeFunction;if(this.eventHandlers)j.eventHandlers=this.eventHandlers;if(this._groups.length>0)j.groups=[...this._groups];if(this._inScreens)j.inScreens=this._inScreens;if(this._excludeScreens)j.excludeScreens=this._excludeScreens;if(this._requiredAssets)j.requiredAssets=this._requiredAssets;return j}setPriority(j){return this._priority=j,this}inGroup(j){if(!this._groups.includes(j))this._groups.push(j);return this}inScreens(j){return this._inScreens=[...j],this}excludeScreens(j){return this._excludeScreens=[...j],this}requiresAssets(j){return this._requiredAssets=[...j],this}addQuery(j,x){let F=this;return F.queries={...this.queries,[j]:x},F}setProcess(j){return this.processFunction=j,this}registerAndContinue(){if(!this._ecspresso)throw Error(`Cannot register system '${this._label}': SystemBuilder is not attached to an ECSpresso instance. Use Bundle.addSystem() or ECSpresso.addSystem() instead.`);return this._autoRegister(),this._ecspresso}and(){if(this._ecspresso)return this._autoRegister(),this._ecspresso;if(this._bundle)return this._bundle;throw Error(`Cannot use and() on system '${this._label}': not attached to ECSpresso or Bundle.`)}setOnDetach(j){return this.detachFunction=j,this}setOnInitialize(j){return this.initializeFunction=j,this}setEventHandlers(j){return this.eventHandlers=j,this}build(j){let x=this._createSystemObject();if(this._ecspresso)X(x,this._ecspresso);if(j)X(x,j);return this}}function X(j,x){x._registerSystem(j)}function D(j,x){return new Y(j,x)}function _(j,x){return new Y(j,null,x)}function C(){return`bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,9)}`}class U{_systems=[];_resources=new Map;_assets=new Map;_assetGroups=new Map;_screens=new Map;_id;constructor(j){this._id=j||C()}get id(){return this._id}set id(j){this._id=j}addSystem(j){if(typeof j==="string"){let x=_(j,this);return this._systems.push(x),x}else return this._systems.push(j),j}addResource(j,x){return this._resources.set(j,x),this}addAsset(j,x,F){return this._assets.set(j,{loader:x,eager:F?.eager??!0,group:F?.group}),this}addAssetGroup(j,x){let F=new Map;for(let[K,H]of Object.entries(x))F.set(K,H),this._assets.set(K,{loader:H,eager:!1,group:j});return this._assetGroups.set(j,F),this}addScreen(j,x){return this._screens.set(j,x),this}getAssets(){return new Map(this._assets)}getScreens(){return new Map(this._screens)}_setResource(j,x){this._resources.set(j,x)}_setAsset(j,x){this._assets.set(j,x)}_setScreen(j,x){this._screens.set(j,x)}getSystems(){return this._systems.map((j)=>j.build())}registerSystemsWithEcspresso(j){for(let x of this._systems)x.build(j)}getResources(){return new Map(this._resources)}getResource(j){return this._resources.get(j)}getSystemBuilders(){return[...this._systems]}hasResource(j){return this._resources.has(j)}}function O(j,...x){if(x.length===0)return new U(j);let F=new U(j);for(let K of x){for(let H of K.getSystemBuilders())F.addSystem(H);for(let[H,M]of K.getResources().entries())F._setResource(H,M);for(let[H,M]of K.getAssets().entries())F._setAsset(H,M);for(let[H,M]of K.getScreens().entries())F._setScreen(H,M)}return F}function T(j,x){return{timer:{elapsed:0,duration:j,repeat:!1,active:!0,justFinished:!1,onComplete:x?.onComplete}}}function S(j,x){return{timer:{elapsed:0,duration:j,repeat:!0,active:!0,justFinished:!1,onComplete:x?.onComplete}}}function k(j){let{systemGroup:x="timers",priority:F=0}=j??{},K=new U("timers");K.addSystem("timer-update").setPriority(F).inGroup(x).addQuery("timers",{with:["timer"]}).setProcess((M,V,Q)=>{for(let R of M.timers){let{timer:J}=R.components;if(J.justFinished=!1,!J.active)continue;if(J.elapsed+=V,J.elapsed<J.duration)continue;if(J.repeat)while(J.elapsed>=J.duration)J.justFinished=!0,H(Q,R.id,J),J.elapsed-=J.duration;else J.justFinished=!0,H(Q,R.id,J),J.active=!1,Q.commands.removeEntity(R.id)}}).and();function H(M,V,Q){if(!Q.onComplete)return;let R={entityId:V,duration:Q.duration,elapsed:Q.elapsed};M.eventBus.publish(Q.onComplete,R)}return K}export{k as createTimerBundle,T as createTimer,S as createRepeatingTimer};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=8A5D1E808447563C64756E2164756E21
|
|
4
4
|
//# sourceMappingURL=timers.js.map
|