ecspresso 0.10.2 → 0.11.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 +73 -17
- package/dist/asset-manager.d.ts +15 -15
- package/dist/asset-types.d.ts +16 -14
- package/dist/bundle.d.ts +66 -16
- package/dist/bundles/audio.d.ts +293 -0
- package/dist/bundles/{utils/bounds.d.ts → bounds.d.ts} +9 -7
- package/dist/bundles/camera.d.ts +89 -0
- package/dist/bundles/collision.d.ts +289 -0
- package/dist/bundles/diagnostics.d.ts +48 -0
- package/dist/bundles/{utils/input.d.ts → input.d.ts} +16 -17
- package/dist/bundles/physics2D.d.ts +159 -0
- package/dist/bundles/renderers/renderer2D.d.ts +65 -24
- package/dist/bundles/spatial-index.d.ts +57 -0
- package/dist/bundles/state-machine.d.ts +298 -0
- package/dist/bundles/{utils/timers.d.ts → timers.d.ts} +9 -8
- package/dist/bundles/{utils/transform.d.ts → transform.d.ts} +10 -10
- package/dist/bundles/tween.d.ts +197 -0
- package/dist/command-buffer.d.ts +20 -20
- package/dist/ecspresso-builder.d.ts +165 -0
- package/dist/ecspresso.d.ts +157 -178
- package/dist/entity-manager.d.ts +76 -40
- package/dist/event-bus.d.ts +6 -1
- package/dist/index.d.ts +1 -9
- package/dist/reactive-query-manager.d.ts +14 -3
- package/dist/resource-manager.d.ts +35 -19
- package/dist/screen-manager.d.ts +4 -4
- package/dist/screen-types.d.ts +12 -11
- package/dist/src/bundles/audio.js +4 -0
- package/dist/src/bundles/audio.js.map +10 -0
- package/dist/src/bundles/bounds.js +4 -0
- package/dist/src/bundles/bounds.js.map +10 -0
- package/dist/src/bundles/camera.js +4 -0
- package/dist/src/bundles/camera.js.map +10 -0
- package/dist/src/bundles/collision.js +4 -0
- package/dist/src/bundles/collision.js.map +11 -0
- package/dist/src/bundles/diagnostics.js +5 -0
- package/dist/src/bundles/diagnostics.js.map +10 -0
- package/dist/src/bundles/input.js +4 -0
- package/dist/src/bundles/input.js.map +10 -0
- package/dist/src/bundles/physics2D.js +4 -0
- package/dist/src/bundles/physics2D.js.map +11 -0
- package/dist/src/bundles/renderers/renderer2D.js +4 -0
- package/dist/src/bundles/renderers/renderer2D.js.map +10 -0
- package/dist/src/bundles/spatial-index.js +4 -0
- package/dist/src/bundles/spatial-index.js.map +11 -0
- package/dist/src/bundles/state-machine.js +4 -0
- package/dist/src/bundles/state-machine.js.map +10 -0
- package/dist/src/bundles/timers.js +4 -0
- package/dist/src/bundles/timers.js.map +10 -0
- package/dist/src/bundles/transform.js +4 -0
- package/dist/src/bundles/transform.js.map +10 -0
- package/dist/src/bundles/tween.js +4 -0
- package/dist/src/bundles/tween.js.map +11 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +25 -0
- package/dist/system-builder.d.ts +36 -42
- package/dist/type-utils.d.ts +52 -3
- package/dist/types.d.ts +10 -19
- package/dist/utils/check-required-cycle.d.ts +12 -0
- package/dist/utils/easing.d.ts +71 -0
- package/dist/utils/math.d.ts +67 -0
- package/dist/utils/narrowphase.d.ts +63 -0
- package/dist/utils/spatial-hash.d.ts +53 -0
- package/package.json +50 -20
- package/dist/bundles/renderers/renderer2D.js +0 -4
- package/dist/bundles/renderers/renderer2D.js.map +0 -10
- package/dist/bundles/utils/bounds.js +0 -4
- package/dist/bundles/utils/bounds.js.map +0 -10
- package/dist/bundles/utils/collision.d.ts +0 -204
- package/dist/bundles/utils/collision.js +0 -4
- package/dist/bundles/utils/collision.js.map +0 -10
- package/dist/bundles/utils/input.js +0 -4
- package/dist/bundles/utils/input.js.map +0 -10
- package/dist/bundles/utils/movement.d.ts +0 -86
- package/dist/bundles/utils/movement.js +0 -4
- package/dist/bundles/utils/movement.js.map +0 -10
- package/dist/bundles/utils/timers.js +0 -4
- package/dist/bundles/utils/timers.js.map +0 -10
- package/dist/bundles/utils/transform.js +0 -4
- package/dist/bundles/utils/transform.js.map +0 -10
- package/dist/index.js +0 -4
- package/dist/index.js.map +0 -22
|
@@ -0,0 +1,289 @@
|
|
|
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 'ecspresso';
|
|
9
|
+
import type { SystemPhase } from 'ecspresso';
|
|
10
|
+
import type { TransformComponentTypes } from './transform';
|
|
11
|
+
/**
|
|
12
|
+
* Axis-Aligned Bounding Box collider.
|
|
13
|
+
*/
|
|
14
|
+
export interface AABBCollider {
|
|
15
|
+
/** Width of the bounding box */
|
|
16
|
+
width: number;
|
|
17
|
+
/** Height of the bounding box */
|
|
18
|
+
height: number;
|
|
19
|
+
/** X offset from entity position (default: 0) */
|
|
20
|
+
offsetX?: number;
|
|
21
|
+
/** Y offset from entity position (default: 0) */
|
|
22
|
+
offsetY?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Circle collider.
|
|
26
|
+
*/
|
|
27
|
+
export interface CircleCollider {
|
|
28
|
+
/** Radius of the circle */
|
|
29
|
+
radius: number;
|
|
30
|
+
/** X offset from entity position (default: 0) */
|
|
31
|
+
offsetX?: number;
|
|
32
|
+
/** Y offset from entity position (default: 0) */
|
|
33
|
+
offsetY?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Collision layer configuration.
|
|
37
|
+
*/
|
|
38
|
+
export interface CollisionLayer<L extends string = never> {
|
|
39
|
+
/** The layer this entity belongs to */
|
|
40
|
+
layer: L;
|
|
41
|
+
/** Layers this entity can collide with */
|
|
42
|
+
collidesWith: readonly L[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Component types provided by the collision bundle.
|
|
46
|
+
* Included automatically via `.withBundle(createCollisionBundle())`.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const ecs = ECSpresso.create()
|
|
51
|
+
* .withBundle(createCollisionBundle())
|
|
52
|
+
* .withComponentTypes<{ sprite: Sprite; enemy: boolean }>()
|
|
53
|
+
* .build();
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export interface CollisionComponentTypes<L extends string = never> {
|
|
57
|
+
aabbCollider: AABBCollider;
|
|
58
|
+
circleCollider: CircleCollider;
|
|
59
|
+
collisionLayer: CollisionLayer<L>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Event fired when two entities collide.
|
|
63
|
+
*/
|
|
64
|
+
export interface CollisionEvent<L extends string = never> {
|
|
65
|
+
/** First entity in the collision */
|
|
66
|
+
entityA: number;
|
|
67
|
+
/** Second entity in the collision */
|
|
68
|
+
entityB: number;
|
|
69
|
+
/** Layer of the first entity */
|
|
70
|
+
layerA: L;
|
|
71
|
+
/** Layer of the second entity */
|
|
72
|
+
layerB: L;
|
|
73
|
+
/** Contact normal pointing from entityA toward entityB */
|
|
74
|
+
normal: {
|
|
75
|
+
x: number;
|
|
76
|
+
y: number;
|
|
77
|
+
};
|
|
78
|
+
/** Penetration depth (positive = overlapping) */
|
|
79
|
+
depth: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Event types provided by the collision bundle.
|
|
83
|
+
*/
|
|
84
|
+
export interface CollisionEventTypes<L extends string = never> {
|
|
85
|
+
collision: CollisionEvent<L>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Configuration options for the collision bundle.
|
|
89
|
+
*/
|
|
90
|
+
export interface CollisionBundleOptions<G extends string = 'physics'> {
|
|
91
|
+
/** System group name (default: 'physics') */
|
|
92
|
+
systemGroup?: G;
|
|
93
|
+
/** Priority for collision system (default: 0) */
|
|
94
|
+
priority?: number;
|
|
95
|
+
/** Name of the collision event (default: 'collision') */
|
|
96
|
+
collisionEventName?: string;
|
|
97
|
+
/** Execution phase (default: 'postUpdate') */
|
|
98
|
+
phase?: SystemPhase;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create an AABB collider component.
|
|
102
|
+
*
|
|
103
|
+
* @param width Width of the bounding box
|
|
104
|
+
* @param height Height of the bounding box
|
|
105
|
+
* @param offsetX X offset from entity position
|
|
106
|
+
* @param offsetY Y offset from entity position
|
|
107
|
+
* @returns Component object suitable for spreading into spawn()
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* ecs.spawn({
|
|
112
|
+
* ...createTransform(100, 200),
|
|
113
|
+
* ...createAABBCollider(50, 30),
|
|
114
|
+
* });
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare function createAABBCollider(width: number, height: number, offsetX?: number, offsetY?: number): {
|
|
118
|
+
aabbCollider: AABBCollider;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Create a circle collider component.
|
|
122
|
+
*
|
|
123
|
+
* @param radius Radius of the circle
|
|
124
|
+
* @param offsetX X offset from entity position
|
|
125
|
+
* @param offsetY Y offset from entity position
|
|
126
|
+
* @returns Component object suitable for spreading into spawn()
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* ecs.spawn({
|
|
131
|
+
* ...createTransform(100, 200),
|
|
132
|
+
* ...createCircleCollider(25),
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export declare function createCircleCollider(radius: number, offsetX?: number, offsetY?: number): {
|
|
137
|
+
circleCollider: CircleCollider;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Create a collision layer component.
|
|
141
|
+
*
|
|
142
|
+
* @param layer The layer this entity belongs to
|
|
143
|
+
* @param collidesWith Layers this entity can collide with
|
|
144
|
+
* @returns Component object suitable for spreading into spawn()
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* ecs.spawn({
|
|
149
|
+
* ...createTransform(100, 200),
|
|
150
|
+
* ...createAABBCollider(50, 30),
|
|
151
|
+
* ...createCollisionLayer('player', ['enemy', 'obstacle']),
|
|
152
|
+
* });
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export declare function createCollisionLayer<L extends string>(layer: L, collidesWith: readonly L[]): Pick<CollisionComponentTypes<L>, 'collisionLayer'>;
|
|
156
|
+
/**
|
|
157
|
+
* Layer factory result from defineCollisionLayers.
|
|
158
|
+
*/
|
|
159
|
+
export type LayerFactories<T extends Record<string, readonly string[]>> = {
|
|
160
|
+
[K in keyof T]: () => Pick<CollisionComponentTypes<Extract<keyof T, string>>, 'collisionLayer'>;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Extract layer names from a `defineCollisionLayers` result for use with
|
|
164
|
+
* `createCollisionPairHandler`'s `L` type parameter.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });
|
|
169
|
+
* type Layer = LayersOf<typeof layers>;
|
|
170
|
+
* const handler = createCollisionPairHandler<ECS, Layer>({
|
|
171
|
+
* 'player:enemy': (playerId, enemyId, ecs) => { ... },
|
|
172
|
+
* });
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
export type LayersOf<T> = Extract<keyof T, string>;
|
|
176
|
+
/**
|
|
177
|
+
* Define collision layer relationships and get factory functions.
|
|
178
|
+
*
|
|
179
|
+
* @param rules Object mapping layer names to arrays of layers they collide with
|
|
180
|
+
* @returns Object with factory functions for each layer
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* const layers = defineCollisionLayers({
|
|
185
|
+
* player: ['enemy', 'enemyProjectile'],
|
|
186
|
+
* playerProjectile: ['enemy'],
|
|
187
|
+
* enemy: ['playerProjectile'],
|
|
188
|
+
* enemyProjectile: ['player'],
|
|
189
|
+
* });
|
|
190
|
+
*
|
|
191
|
+
* // Usage
|
|
192
|
+
* ecs.spawn({
|
|
193
|
+
* ...createTransform(100, 200),
|
|
194
|
+
* ...createAABBCollider(50, 30),
|
|
195
|
+
* ...layers.player(),
|
|
196
|
+
* });
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
/**
|
|
200
|
+
* Validates that all `collidesWith` values reference actual layer keys.
|
|
201
|
+
* Catches typos at compile time.
|
|
202
|
+
*/
|
|
203
|
+
type ValidateCollidesWith<T> = {
|
|
204
|
+
[K in keyof T]: T[K] extends readonly (infer V)[] ? [V] extends [Extract<keyof T, string>] ? T[K] : readonly Extract<keyof T, string>[] : never;
|
|
205
|
+
};
|
|
206
|
+
export declare function defineCollisionLayers<const T extends Record<string, readonly string[]>>(rules: T & ValidateCollidesWith<T>): LayerFactories<T>;
|
|
207
|
+
/**
|
|
208
|
+
* Callback for a collision pair handler.
|
|
209
|
+
*
|
|
210
|
+
* @param firstEntityId Entity belonging to the first layer in the pair key
|
|
211
|
+
* @param secondEntityId Entity belonging to the second layer in the pair key
|
|
212
|
+
* @param ecs The ECS world instance (passed through from the subscriber)
|
|
213
|
+
*/
|
|
214
|
+
export type CollisionPairCallback<W = unknown> = (firstEntityId: number, secondEntityId: number, ecs: W) => void;
|
|
215
|
+
/**
|
|
216
|
+
* Create a collision pair handler that routes collision events to
|
|
217
|
+
* layer-pair-specific callbacks.
|
|
218
|
+
*
|
|
219
|
+
* Registering `"a:b"` automatically handles both `(layerA=a, layerB=b)` and
|
|
220
|
+
* `(layerA=b, layerB=a)`. Entity arguments are swapped to match the declared
|
|
221
|
+
* key order. If both `"a:b"` and `"b:a"` are explicitly registered, each gets
|
|
222
|
+
* its own handler with no implicit reverse.
|
|
223
|
+
*
|
|
224
|
+
* @typeParam W - The ECS world type (e.g. `ECSpresso<C, E, R>`). Defaults to `unknown`.
|
|
225
|
+
* @typeParam L - Union of valid layer names. Defaults to `string`.
|
|
226
|
+
* Provide specific layer names for compile-time key validation:
|
|
227
|
+
* `createCollisionPairHandler<ECS, keyof typeof layers>({...})`
|
|
228
|
+
*
|
|
229
|
+
* @param pairs Object mapping `"layerA:layerB"` keys to callbacks
|
|
230
|
+
* @returns A dispatch function to call with collision event data and ECS instance
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```typescript
|
|
234
|
+
* // Basic usage:
|
|
235
|
+
* const handler = createCollisionPairHandler<ECS>({
|
|
236
|
+
* 'playerProjectile:enemy': (projectileId, enemyId, ecs) => {
|
|
237
|
+
* ecs.commands.removeEntity(projectileId);
|
|
238
|
+
* },
|
|
239
|
+
* });
|
|
240
|
+
*
|
|
241
|
+
* // With layer name validation:
|
|
242
|
+
* const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });
|
|
243
|
+
* type Layer = LayersOf<typeof layers>;
|
|
244
|
+
* const handler = createCollisionPairHandler<ECS, Layer>({
|
|
245
|
+
* 'player:enemy': (playerId, enemyId, ecs) => { ... },
|
|
246
|
+
* });
|
|
247
|
+
*
|
|
248
|
+
* ecs.eventBus.subscribe('collision', (data) => handler(data, ecs));
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export declare function createCollisionPairHandler<W = unknown, L extends string = string>(pairs: {
|
|
252
|
+
[K in `${L}:${L}`]?: CollisionPairCallback<W>;
|
|
253
|
+
}): (event: CollisionEvent<L>, ecs: W) => void;
|
|
254
|
+
type CombinedComponentTypes<L extends string> = CollisionComponentTypes<L> & TransformComponentTypes;
|
|
255
|
+
/**
|
|
256
|
+
* Create a collision bundle for ECSpresso.
|
|
257
|
+
*
|
|
258
|
+
* This bundle provides:
|
|
259
|
+
* - Collision detection between entities with colliders
|
|
260
|
+
* - AABB-AABB, circle-circle, and AABB-circle collision
|
|
261
|
+
* - Layer-based filtering for collision pairs
|
|
262
|
+
* - Deduplication of A-B / B-A collisions
|
|
263
|
+
* - Automatic broadphase acceleration when spatialIndex resource is present
|
|
264
|
+
*
|
|
265
|
+
* Uses worldTransform for position (world-space collision detection).
|
|
266
|
+
* The `layers` parameter is required for type inference — at runtime the
|
|
267
|
+
* bundle does not consume it.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });
|
|
272
|
+
* const ecs = ECSpresso
|
|
273
|
+
* .create()
|
|
274
|
+
* .withBundle(createTransformBundle())
|
|
275
|
+
* .withBundle(createCollisionBundle({ layers }))
|
|
276
|
+
* .build();
|
|
277
|
+
*
|
|
278
|
+
* // Entity with collision
|
|
279
|
+
* ecs.spawn({
|
|
280
|
+
* ...createTransform(100, 200),
|
|
281
|
+
* ...createAABBCollider(50, 30),
|
|
282
|
+
* ...layers.player(),
|
|
283
|
+
* });
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
export declare function createCollisionBundle<L extends string, G extends string = 'physics'>(options: CollisionBundleOptions<G> & {
|
|
287
|
+
layers: LayerFactories<Record<L, readonly string[]>>;
|
|
288
|
+
}): Bundle<CombinedComponentTypes<L>, CollisionEventTypes<L>, {}, {}, {}, 'collision-detection', G>;
|
|
289
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostics Bundle for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Runtime diagnostics: FPS, entity count, per-system timing, per-phase timing,
|
|
5
|
+
* and an optional DOM overlay for visual debugging.
|
|
6
|
+
*/
|
|
7
|
+
import { Bundle } from 'ecspresso';
|
|
8
|
+
import type { SystemPhase } from 'ecspresso';
|
|
9
|
+
export interface DiagnosticsData {
|
|
10
|
+
fps: number;
|
|
11
|
+
entityCount: number;
|
|
12
|
+
systemTimings: ReadonlyMap<string, number>;
|
|
13
|
+
phaseTimings: Readonly<Record<SystemPhase, number>>;
|
|
14
|
+
averageFrameTime: number;
|
|
15
|
+
}
|
|
16
|
+
export interface DiagnosticsResourceTypes {
|
|
17
|
+
diagnostics: DiagnosticsData;
|
|
18
|
+
}
|
|
19
|
+
export interface DiagnosticsBundleOptions<G extends string = 'diagnostics'> {
|
|
20
|
+
/** System group name (default: 'diagnostics') */
|
|
21
|
+
systemGroup?: G;
|
|
22
|
+
/** Enable timing collection on initialize (default: true) */
|
|
23
|
+
enableTimingOnInit?: boolean;
|
|
24
|
+
/** Number of frames to sample for FPS average (default: 60) */
|
|
25
|
+
fpsSampleCount?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface DiagnosticsOverlayOptions {
|
|
28
|
+
/** Corner position (default: 'top-left') */
|
|
29
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
30
|
+
/** Milliseconds between DOM updates (default: 200) */
|
|
31
|
+
updateInterval?: number;
|
|
32
|
+
/** Show per-system timings (default: true) */
|
|
33
|
+
showSystemTimings?: boolean;
|
|
34
|
+
/** Maximum systems to show in overlay (default: 10) */
|
|
35
|
+
maxSystemsShown?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare function createDiagnosticsBundle<G extends string = 'diagnostics'>(options?: DiagnosticsBundleOptions<G>): Bundle<{}, {}, DiagnosticsResourceTypes, {}, {}, 'diagnostics-collect', G>;
|
|
38
|
+
/**
|
|
39
|
+
* Create a DOM overlay that displays diagnostics data.
|
|
40
|
+
* Returns a cleanup function that removes the element and clears the interval.
|
|
41
|
+
*
|
|
42
|
+
* @param ecs An ECSpresso instance with the diagnostics resource
|
|
43
|
+
* @param options Overlay configuration
|
|
44
|
+
* @returns Cleanup function
|
|
45
|
+
*/
|
|
46
|
+
export declare function createDiagnosticsOverlay<R extends DiagnosticsResourceTypes>(ecs: {
|
|
47
|
+
getResource<K extends keyof R>(key: K): R[K];
|
|
48
|
+
}, options?: DiagnosticsOverlayOptions): () => void;
|
|
@@ -48,35 +48,35 @@ export interface PointerState {
|
|
|
48
48
|
justPressed(button: number): boolean;
|
|
49
49
|
justReleased(button: number): boolean;
|
|
50
50
|
}
|
|
51
|
-
export interface ActionState {
|
|
52
|
-
isActive(action:
|
|
53
|
-
justActivated(action:
|
|
54
|
-
justDeactivated(action:
|
|
51
|
+
export interface ActionState<A extends string = string> {
|
|
52
|
+
isActive(action: A): boolean;
|
|
53
|
+
justActivated(action: A): boolean;
|
|
54
|
+
justDeactivated(action: A): boolean;
|
|
55
55
|
}
|
|
56
|
-
export interface InputState {
|
|
56
|
+
export interface InputState<A extends string = string> {
|
|
57
57
|
readonly keyboard: KeyboardState;
|
|
58
58
|
readonly pointer: PointerState;
|
|
59
|
-
readonly actions: ActionState
|
|
60
|
-
setActionMap(actions: ActionMap): void;
|
|
61
|
-
getActionMap(): Readonly<ActionMap
|
|
59
|
+
readonly actions: ActionState<A>;
|
|
60
|
+
setActionMap(actions: ActionMap<A>): void;
|
|
61
|
+
getActionMap(): Readonly<ActionMap<A>>;
|
|
62
62
|
}
|
|
63
63
|
export interface ActionBinding {
|
|
64
64
|
keys?: KeyCode[];
|
|
65
65
|
buttons?: number[];
|
|
66
66
|
}
|
|
67
|
-
export type ActionMap = Record<
|
|
68
|
-
export interface InputResourceTypes {
|
|
69
|
-
inputState: InputState
|
|
67
|
+
export type ActionMap<A extends string = string> = Record<A, ActionBinding>;
|
|
68
|
+
export interface InputResourceTypes<A extends string = string> {
|
|
69
|
+
inputState: InputState<A>;
|
|
70
70
|
}
|
|
71
|
-
export interface InputBundleOptions {
|
|
71
|
+
export interface InputBundleOptions<A extends string = string, G extends string = 'input'> {
|
|
72
72
|
/** System group name (default: 'input') */
|
|
73
|
-
systemGroup?:
|
|
73
|
+
systemGroup?: G;
|
|
74
74
|
/** Priority for input system (default: 100) */
|
|
75
75
|
priority?: number;
|
|
76
76
|
/** Execution phase (default: 'preUpdate') */
|
|
77
77
|
phase?: SystemPhase;
|
|
78
78
|
/** Initial action mappings */
|
|
79
|
-
actions?: ActionMap
|
|
79
|
+
actions?: ActionMap<A>;
|
|
80
80
|
/** EventTarget to attach listeners to (default: globalThis). Pass a custom target for testability. */
|
|
81
81
|
target?: EventTarget;
|
|
82
82
|
}
|
|
@@ -98,8 +98,7 @@ export declare function createActionBinding(binding: ActionBinding): ActionBindi
|
|
|
98
98
|
*
|
|
99
99
|
* @example
|
|
100
100
|
* ```typescript
|
|
101
|
-
* const ecs = ECSpresso
|
|
102
|
-
* .create<Components, Events, Resources>()
|
|
101
|
+
* const ecs = ECSpresso.create()
|
|
103
102
|
* .withBundle(createInputBundle({
|
|
104
103
|
* actions: {
|
|
105
104
|
* jump: { keys: [' ', 'ArrowUp'] },
|
|
@@ -114,5 +113,5 @@ export declare function createActionBinding(binding: ActionBinding): ActionBindi
|
|
|
114
113
|
* if (input.keyboard.isDown('ArrowRight')) { ... }
|
|
115
114
|
* ```
|
|
116
115
|
*/
|
|
117
|
-
export declare function createInputBundle(options?: InputBundleOptions): Bundle<{}, {}, InputResourceTypes>;
|
|
116
|
+
export declare function createInputBundle<A extends string = string, G extends string = 'input'>(options?: InputBundleOptions<A, G>): Bundle<{}, {}, InputResourceTypes<A>, {}, {}, 'input-state', G>;
|
|
118
117
|
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physics 2D Bundle for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Provides ECS-native arcade physics: gravity, forces, drag, semi-implicit Euler
|
|
5
|
+
* integration, and impulse-based collision response with friction.
|
|
6
|
+
*
|
|
7
|
+
* Reuses collider types from the collision bundle for shape definitions.
|
|
8
|
+
* Has its own collision detection in fixedUpdate for physics response;
|
|
9
|
+
* the existing collision bundle can still run in postUpdate for game logic events.
|
|
10
|
+
*/
|
|
11
|
+
import { Bundle } from 'ecspresso';
|
|
12
|
+
import type { SystemPhase } from 'ecspresso';
|
|
13
|
+
import type { TransformComponentTypes } from './transform';
|
|
14
|
+
import type { CollisionComponentTypes, LayerFactories } from './collision';
|
|
15
|
+
import type { Vector2D } from 'ecspresso';
|
|
16
|
+
/**
|
|
17
|
+
* Rigid body types for physics simulation.
|
|
18
|
+
* - 'dynamic': Fully simulated (gravity, forces, collisions)
|
|
19
|
+
* - 'kinematic': Moves via velocity only (ignores gravity/forces, immovable in collisions)
|
|
20
|
+
* - 'static': Immovable (ignores gravity, forces, and velocity)
|
|
21
|
+
*/
|
|
22
|
+
export type BodyType = 'dynamic' | 'kinematic' | 'static';
|
|
23
|
+
/**
|
|
24
|
+
* Rigid body component controlling physics behavior.
|
|
25
|
+
*/
|
|
26
|
+
export interface RigidBody {
|
|
27
|
+
type: BodyType;
|
|
28
|
+
/** Mass in arbitrary units. Affects force→acceleration. Infinity = immovable. */
|
|
29
|
+
mass: number;
|
|
30
|
+
/** Linear velocity damping coefficient (units/sec, 0 = none) */
|
|
31
|
+
drag: number;
|
|
32
|
+
/** Bounciness 0–1 (0 = no bounce, 1 = perfectly elastic) */
|
|
33
|
+
restitution: number;
|
|
34
|
+
/** Surface friction coefficient 0–1 */
|
|
35
|
+
friction: number;
|
|
36
|
+
/** Per-entity gravity multiplier (0 = no gravity) */
|
|
37
|
+
gravityScale: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Component types provided by the physics bundle.
|
|
41
|
+
*/
|
|
42
|
+
export interface Physics2DComponentTypes<L extends string = never> extends TransformComponentTypes, CollisionComponentTypes<L> {
|
|
43
|
+
rigidBody: RigidBody;
|
|
44
|
+
velocity: Vector2D;
|
|
45
|
+
force: Vector2D;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Physics configuration resource.
|
|
49
|
+
*/
|
|
50
|
+
export interface Physics2DConfig {
|
|
51
|
+
gravity: Vector2D;
|
|
52
|
+
}
|
|
53
|
+
export interface Physics2DResourceTypes {
|
|
54
|
+
physicsConfig: Physics2DConfig;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Event emitted for each physics collision pair.
|
|
58
|
+
*/
|
|
59
|
+
export interface Physics2DCollisionEvent {
|
|
60
|
+
entityA: number;
|
|
61
|
+
entityB: number;
|
|
62
|
+
/** Unit normal pointing from A toward B */
|
|
63
|
+
normal: Vector2D;
|
|
64
|
+
/** Penetration depth (positive) */
|
|
65
|
+
depth: number;
|
|
66
|
+
}
|
|
67
|
+
export interface Physics2DEventTypes {
|
|
68
|
+
physicsCollision: Physics2DCollisionEvent;
|
|
69
|
+
}
|
|
70
|
+
export interface Physics2DBundleOptions<G extends string = 'physics2D', CG extends string = never> {
|
|
71
|
+
/** World gravity vector (default: {x: 0, y: 0}) */
|
|
72
|
+
gravity?: Vector2D;
|
|
73
|
+
/** System group name (default: 'physics2D') */
|
|
74
|
+
systemGroup?: G;
|
|
75
|
+
/** Additional group for the collision system only (default: none).
|
|
76
|
+
* When set, the collision system belongs to both `systemGroup` and this group,
|
|
77
|
+
* allowing independent enable/disable of collision detection. */
|
|
78
|
+
collisionSystemGroup?: CG;
|
|
79
|
+
/** Priority for integration system (default: 1000) */
|
|
80
|
+
integrationPriority?: number;
|
|
81
|
+
/** Priority for collision system (default: 900) */
|
|
82
|
+
collisionPriority?: number;
|
|
83
|
+
/** Execution phase (default: 'fixedUpdate') */
|
|
84
|
+
phase?: SystemPhase;
|
|
85
|
+
}
|
|
86
|
+
export interface RigidBodyOptions {
|
|
87
|
+
mass?: number;
|
|
88
|
+
drag?: number;
|
|
89
|
+
restitution?: number;
|
|
90
|
+
friction?: number;
|
|
91
|
+
gravityScale?: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create a rigid body + force component pair.
|
|
95
|
+
* Static bodies automatically get mass=Infinity.
|
|
96
|
+
*/
|
|
97
|
+
export declare function createRigidBody(type: BodyType, options?: RigidBodyOptions): {
|
|
98
|
+
rigidBody: RigidBody;
|
|
99
|
+
force: Vector2D;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Create a force component with initial values.
|
|
103
|
+
*/
|
|
104
|
+
export declare function createForce(x: number, y: number): {
|
|
105
|
+
force: Vector2D;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Accumulate a force onto an entity's force component.
|
|
109
|
+
*/
|
|
110
|
+
export declare function applyForce(ecs: {
|
|
111
|
+
entityManager: {
|
|
112
|
+
getComponent(id: number, name: 'force'): Vector2D | undefined;
|
|
113
|
+
};
|
|
114
|
+
}, entityId: number, fx: number, fy: number): void;
|
|
115
|
+
/**
|
|
116
|
+
* Apply an instantaneous impulse: velocity += impulse / mass.
|
|
117
|
+
*/
|
|
118
|
+
export declare function applyImpulse(ecs: {
|
|
119
|
+
entityManager: {
|
|
120
|
+
getComponent(id: number, name: 'velocity'): Vector2D | undefined;
|
|
121
|
+
getComponent(id: number, name: 'rigidBody'): RigidBody | undefined;
|
|
122
|
+
};
|
|
123
|
+
}, entityId: number, ix: number, iy: number): void;
|
|
124
|
+
/**
|
|
125
|
+
* Directly set an entity's velocity.
|
|
126
|
+
*/
|
|
127
|
+
export declare function setVelocity(ecs: {
|
|
128
|
+
entityManager: {
|
|
129
|
+
getComponent(id: number, name: 'velocity'): Vector2D | undefined;
|
|
130
|
+
};
|
|
131
|
+
}, entityId: number, vx: number, vy: number): void;
|
|
132
|
+
/**
|
|
133
|
+
* Create a 2D physics bundle for ECSpresso.
|
|
134
|
+
*
|
|
135
|
+
* Provides:
|
|
136
|
+
* - Semi-implicit Euler integration (gravity, forces, drag → velocity → position)
|
|
137
|
+
* - Impulse-based collision response with restitution and friction
|
|
138
|
+
* - physicsCollision events with contact normal and depth
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* const ecs = ECSpresso.create()
|
|
143
|
+
* .withBundle(createTransformBundle())
|
|
144
|
+
* .withBundle(createPhysics2DBundle({ gravity: { x: 0, y: 980 } }))
|
|
145
|
+
* .withFixedTimestep(1/60)
|
|
146
|
+
* .build();
|
|
147
|
+
*
|
|
148
|
+
* ecs.spawn({
|
|
149
|
+
* ...createTransform(100, 200),
|
|
150
|
+
* ...createRigidBody('dynamic', { mass: 1, restitution: 0.5 }),
|
|
151
|
+
* velocity: { x: 0, y: 0 },
|
|
152
|
+
* ...createAABBCollider(32, 32),
|
|
153
|
+
* ...createCollisionLayer('player', ['ground']),
|
|
154
|
+
* });
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
export declare function createPhysics2DBundle<L extends string = never, G extends string = 'physics2D', CG extends string = never>(options?: Physics2DBundleOptions<G, CG> & {
|
|
158
|
+
layers?: LayerFactories<Record<L, readonly string[]>>;
|
|
159
|
+
}): Bundle<Physics2DComponentTypes<L>, Physics2DEventTypes, Physics2DResourceTypes, {}, {}, 'physics2D-integration' | 'physics2D-collision', G | CG>;
|