ecspresso 0.10.2 → 0.12.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 +256 -148
- package/dist/asset-manager.d.ts +16 -16
- package/dist/asset-types.d.ts +18 -16
- package/dist/command-buffer.d.ts +30 -20
- package/dist/ecspresso-builder.d.ts +193 -0
- package/dist/ecspresso.d.ts +323 -209
- package/dist/entity-manager.d.ts +76 -30
- package/dist/event-bus.d.ts +6 -1
- package/dist/index.d.ts +6 -13
- package/dist/plugin.d.ts +61 -0
- package/dist/plugins/audio.d.ts +273 -0
- package/dist/{bundles/utils → plugins}/bounds.d.ts +20 -26
- package/dist/plugins/camera.d.ts +88 -0
- package/dist/plugins/collision.d.ts +285 -0
- package/dist/plugins/coroutine.d.ts +126 -0
- package/dist/plugins/diagnostics.d.ts +49 -0
- package/dist/{bundles/utils → plugins}/input.d.ts +22 -29
- package/dist/plugins/particles.d.ts +225 -0
- package/dist/plugins/physics2D.d.ts +163 -0
- package/dist/plugins/renderers/renderer2D.d.ts +262 -0
- package/dist/plugins/spatial-index.d.ts +58 -0
- package/dist/plugins/sprite-animation.d.ts +150 -0
- package/dist/plugins/state-machine.d.ts +244 -0
- package/dist/plugins/timers.d.ts +151 -0
- package/dist/{bundles/utils → plugins}/transform.d.ts +21 -22
- package/dist/plugins/tween.d.ts +162 -0
- package/dist/reactive-query-manager.d.ts +14 -3
- package/dist/resource-manager.d.ts +64 -23
- package/dist/screen-manager.d.ts +21 -15
- package/dist/screen-types.d.ts +15 -11
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +25 -0
- package/dist/src/plugins/audio.js +4 -0
- package/dist/src/plugins/audio.js.map +10 -0
- package/dist/src/plugins/bounds.js +4 -0
- package/dist/src/plugins/bounds.js.map +10 -0
- package/dist/src/plugins/camera.js +4 -0
- package/dist/src/plugins/camera.js.map +10 -0
- package/dist/src/plugins/collision.js +4 -0
- package/dist/src/plugins/collision.js.map +11 -0
- package/dist/src/plugins/coroutine.js +4 -0
- package/dist/src/plugins/coroutine.js.map +10 -0
- package/dist/src/plugins/diagnostics.js +5 -0
- package/dist/src/plugins/diagnostics.js.map +10 -0
- package/dist/src/plugins/input.js +4 -0
- package/dist/src/plugins/input.js.map +10 -0
- package/dist/src/plugins/particles.js +4 -0
- package/dist/src/plugins/particles.js.map +10 -0
- package/dist/src/plugins/physics2D.js +4 -0
- package/dist/src/plugins/physics2D.js.map +11 -0
- package/dist/src/plugins/renderers/renderer2D.js +4 -0
- package/dist/src/plugins/renderers/renderer2D.js.map +10 -0
- package/dist/src/plugins/spatial-index.js +4 -0
- package/dist/src/plugins/spatial-index.js.map +11 -0
- package/dist/src/plugins/sprite-animation.js +4 -0
- package/dist/src/plugins/sprite-animation.js.map +10 -0
- package/dist/src/plugins/state-machine.js +4 -0
- package/dist/src/plugins/state-machine.js.map +10 -0
- package/dist/src/plugins/timers.js +4 -0
- package/dist/src/plugins/timers.js.map +10 -0
- package/dist/src/plugins/transform.js +4 -0
- package/dist/src/plugins/transform.js.map +10 -0
- package/dist/src/plugins/tween.js +4 -0
- package/dist/src/plugins/tween.js.map +11 -0
- package/dist/system-builder.d.ts +75 -112
- package/dist/type-utils.d.ts +247 -7
- package/dist/types.d.ts +58 -39
- 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 +65 -27
- package/dist/bundle.d.ts +0 -123
- package/dist/bundles/renderers/renderer2D.d.ts +0 -220
- 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.d.ts +0 -172
- 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,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Machine Plugin for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Provides ECS-native finite state machines with guard-based transitions,
|
|
5
|
+
* event-driven transitions, and lifecycle hooks (onEnter, onExit, onUpdate).
|
|
6
|
+
*
|
|
7
|
+
* Each entity gets a `stateMachine` component referencing a shared definition.
|
|
8
|
+
* One system processes all state machine entities each tick.
|
|
9
|
+
*/
|
|
10
|
+
import { type Plugin, type BasePluginOptions } from 'ecspresso';
|
|
11
|
+
import type { BaseWorld } from 'ecspresso';
|
|
12
|
+
import type { WorldConfigFrom, EmptyConfig } from '../type-utils';
|
|
13
|
+
/** BaseWorld narrowed to state-machine components for typed access in helpers. */
|
|
14
|
+
type StateMachineWorld = BaseWorld<StateMachineComponentTypes>;
|
|
15
|
+
/**
|
|
16
|
+
* Configuration for a single state in a state machine definition.
|
|
17
|
+
*
|
|
18
|
+
* @template S - Union of state name strings
|
|
19
|
+
* @template W - World interface type for hooks/guards (default: StateMachineWorld)
|
|
20
|
+
*/
|
|
21
|
+
export interface StateConfig<S extends string, W extends BaseWorld<StateMachineComponentTypes> = StateMachineWorld> {
|
|
22
|
+
/** Called when entering this state */
|
|
23
|
+
onEnter?(ctx: {
|
|
24
|
+
ecs: W;
|
|
25
|
+
entityId: number;
|
|
26
|
+
}): void;
|
|
27
|
+
/** Called when exiting this state */
|
|
28
|
+
onExit?(ctx: {
|
|
29
|
+
ecs: W;
|
|
30
|
+
entityId: number;
|
|
31
|
+
}): void;
|
|
32
|
+
/** Called each tick while in this state */
|
|
33
|
+
onUpdate?(ctx: {
|
|
34
|
+
ecs: W;
|
|
35
|
+
entityId: number;
|
|
36
|
+
dt: number;
|
|
37
|
+
}): void;
|
|
38
|
+
/** Guard-based transitions evaluated each tick. First passing guard wins. */
|
|
39
|
+
transitions?: ReadonlyArray<{
|
|
40
|
+
target: S;
|
|
41
|
+
guard(ctx: {
|
|
42
|
+
ecs: W;
|
|
43
|
+
entityId: number;
|
|
44
|
+
}): boolean;
|
|
45
|
+
}>;
|
|
46
|
+
/** Event-based transition map: eventName → target state or guarded transition */
|
|
47
|
+
on?: Record<string, S | {
|
|
48
|
+
target: S;
|
|
49
|
+
guard(ctx: {
|
|
50
|
+
ecs: W;
|
|
51
|
+
entityId: number;
|
|
52
|
+
}): boolean;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Immutable definition of a state machine. Shared across entities.
|
|
57
|
+
*
|
|
58
|
+
* @template S - Union of state name strings
|
|
59
|
+
*/
|
|
60
|
+
export interface StateMachineDefinition<S extends string> {
|
|
61
|
+
readonly id: string;
|
|
62
|
+
readonly initial: S;
|
|
63
|
+
readonly states: {
|
|
64
|
+
readonly [K in S]: StateConfig<S>;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Runtime state machine component stored on each entity.
|
|
69
|
+
*
|
|
70
|
+
* @template S - Union of state name strings (default: string)
|
|
71
|
+
*/
|
|
72
|
+
export interface StateMachine<S extends string = string> {
|
|
73
|
+
readonly definition: StateMachineDefinition<string>;
|
|
74
|
+
current: S;
|
|
75
|
+
previous: S | null;
|
|
76
|
+
stateTime: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Component types provided by the state machine plugin.
|
|
80
|
+
*
|
|
81
|
+
* @template S - Union of state name strings (default: string)
|
|
82
|
+
*/
|
|
83
|
+
export interface StateMachineComponentTypes<S extends string = string> {
|
|
84
|
+
stateMachine: StateMachine<S>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Event published on every state transition.
|
|
88
|
+
*
|
|
89
|
+
* @template S - Union of state name strings (default: string)
|
|
90
|
+
*/
|
|
91
|
+
export interface StateTransitionEvent<S extends string = string> {
|
|
92
|
+
entityId: number;
|
|
93
|
+
from: S;
|
|
94
|
+
to: S;
|
|
95
|
+
definitionId: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Event types provided by the state machine plugin.
|
|
99
|
+
*
|
|
100
|
+
* @template S - Union of state name strings (default: string)
|
|
101
|
+
*/
|
|
102
|
+
export interface StateMachineEventTypes<S extends string = string> {
|
|
103
|
+
stateTransition: StateTransitionEvent<S>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Extract the state name union from a StateMachineDefinition.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const enemyFSM = defineStateMachine('enemy', { initial: 'idle', states: { idle: {}, chase: {} } });
|
|
111
|
+
* type EnemyStates = StatesOf<typeof enemyFSM>; // 'idle' | 'chase'
|
|
112
|
+
* type AllStates = StatesOf<typeof enemyFSM> | StatesOf<typeof playerFSM>;
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export type StatesOf<D> = D extends StateMachineDefinition<infer S> ? S : never;
|
|
116
|
+
/**
|
|
117
|
+
* Configuration options for the state machine plugin.
|
|
118
|
+
*/
|
|
119
|
+
export interface StateMachinePluginOptions<G extends string = 'stateMachine'> extends BasePluginOptions<G> {
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Define a state machine with type-safe state names.
|
|
123
|
+
*
|
|
124
|
+
* @template S - Union of state name strings, inferred from `states` keys
|
|
125
|
+
* @param id - Unique identifier for this definition
|
|
126
|
+
* @param config - Initial state and state configurations
|
|
127
|
+
* @returns A frozen StateMachineDefinition
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* const enemyFSM = defineStateMachine('enemy', {
|
|
132
|
+
* initial: 'idle',
|
|
133
|
+
* states: {
|
|
134
|
+
* idle: {
|
|
135
|
+
* onEnter: ({ ecs, entityId }) => { ... },
|
|
136
|
+
* transitions: [{ target: 'chase', guard: ({ ecs, entityId }) => playerNearby(ecs, entityId) }],
|
|
137
|
+
* },
|
|
138
|
+
* chase: {
|
|
139
|
+
* onUpdate: ({ ecs, entityId, dt }) => { ... },
|
|
140
|
+
* on: { playerLost: 'idle' },
|
|
141
|
+
* },
|
|
142
|
+
* },
|
|
143
|
+
* });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export declare function defineStateMachine<S extends string>(id: string, config: {
|
|
147
|
+
initial: NoInfer<S>;
|
|
148
|
+
states: Record<S, StateConfig<NoInfer<S>>>;
|
|
149
|
+
}): StateMachineDefinition<S>;
|
|
150
|
+
/**
|
|
151
|
+
* Create a stateMachine component from a definition.
|
|
152
|
+
*
|
|
153
|
+
* @param definition - The state machine definition to use
|
|
154
|
+
* @param options - Optional overrides (e.g., initial state)
|
|
155
|
+
* @returns Component object suitable for spreading into spawn()
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* ecs.spawn({
|
|
160
|
+
* ...createStateMachine(enemyFSM),
|
|
161
|
+
* position: { x: 100, y: 200 },
|
|
162
|
+
* });
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export declare function createStateMachine<S extends string>(definition: StateMachineDefinition<S>, options?: {
|
|
166
|
+
initial?: S;
|
|
167
|
+
}): Pick<StateMachineComponentTypes<S>, 'stateMachine'>;
|
|
168
|
+
/**
|
|
169
|
+
* Directly transition an entity's state machine to a target state.
|
|
170
|
+
* Fires onExit, onEnter hooks and publishes stateTransition event.
|
|
171
|
+
*
|
|
172
|
+
* @param ecs - ECS instance (structural typing)
|
|
173
|
+
* @param entityId - Entity to transition
|
|
174
|
+
* @param targetState - State to transition to
|
|
175
|
+
* @returns true if transition succeeded, false if entity has no stateMachine or target state doesn't exist
|
|
176
|
+
*/
|
|
177
|
+
export declare function transitionTo(ecs: StateMachineWorld, entityId: number, targetState: string): boolean;
|
|
178
|
+
/**
|
|
179
|
+
* Send a named event to an entity's state machine.
|
|
180
|
+
* Checks the current state's `on` handlers for a matching event.
|
|
181
|
+
*
|
|
182
|
+
* @param ecs - ECS instance (structural typing)
|
|
183
|
+
* @param entityId - Entity to send event to
|
|
184
|
+
* @param eventName - Event name to match against `on` handlers
|
|
185
|
+
* @returns true if a transition occurred, false otherwise
|
|
186
|
+
*/
|
|
187
|
+
export declare function sendEvent(ecs: StateMachineWorld, entityId: number, eventName: string): boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Get the current state of an entity's state machine.
|
|
190
|
+
*
|
|
191
|
+
* @param ecs - ECS instance (structural typing)
|
|
192
|
+
* @param entityId - Entity to query
|
|
193
|
+
* @returns The current state string, or undefined if entity has no stateMachine
|
|
194
|
+
*/
|
|
195
|
+
export declare function getStateMachineState(ecs: StateMachineWorld, entityId: number): string | undefined;
|
|
196
|
+
/**
|
|
197
|
+
* Typed helpers for the state machine plugin.
|
|
198
|
+
* Creates helpers that validate hook parameters against the world type W.
|
|
199
|
+
* Call after .build() using typeof ecs.
|
|
200
|
+
*/
|
|
201
|
+
export interface StateMachineHelpers<W extends BaseWorld<StateMachineComponentTypes>> {
|
|
202
|
+
defineStateMachine: <S extends string>(id: string, config: {
|
|
203
|
+
initial: NoInfer<S>;
|
|
204
|
+
states: Record<S, StateConfig<NoInfer<S>, W>>;
|
|
205
|
+
}) => StateMachineDefinition<S>;
|
|
206
|
+
}
|
|
207
|
+
export declare function createStateMachineHelpers<W extends BaseWorld<StateMachineComponentTypes> = StateMachineWorld>(_world?: W): StateMachineHelpers<W>;
|
|
208
|
+
/**
|
|
209
|
+
* Create a state machine plugin for ECSpresso.
|
|
210
|
+
*
|
|
211
|
+
* Provides:
|
|
212
|
+
* - Lifecycle hooks (onEnter, onExit, onUpdate) per state
|
|
213
|
+
* - Guard-based automatic transitions evaluated each tick
|
|
214
|
+
* - Event-based transitions via `sendEvent()`
|
|
215
|
+
* - Direct transitions via `transitionTo()`
|
|
216
|
+
* - stateTransition events published on every transition
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* const ecs = ECSpresso.create()
|
|
221
|
+
* .withPlugin(createStateMachinePlugin())
|
|
222
|
+
* .build();
|
|
223
|
+
*
|
|
224
|
+
* const fsm = defineStateMachine('enemy', {
|
|
225
|
+
* initial: 'idle',
|
|
226
|
+
* states: {
|
|
227
|
+
* idle: {
|
|
228
|
+
* transitions: [{ target: 'chase', guard: ({ ecs, entityId }) => playerNearby(ecs, entityId) }],
|
|
229
|
+
* },
|
|
230
|
+
* chase: {
|
|
231
|
+
* onUpdate: ({ ecs, entityId, dt }) => moveTowardPlayer(ecs, entityId, dt),
|
|
232
|
+
* on: { playerLost: 'idle' },
|
|
233
|
+
* },
|
|
234
|
+
* },
|
|
235
|
+
* });
|
|
236
|
+
*
|
|
237
|
+
* ecs.spawn({
|
|
238
|
+
* ...createStateMachine(fsm),
|
|
239
|
+
* position: { x: 0, y: 0 },
|
|
240
|
+
* });
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
export declare function createStateMachinePlugin<S extends string = string, G extends string = 'stateMachine'>(options?: StateMachinePluginOptions<G>): Plugin<WorldConfigFrom<StateMachineComponentTypes<S>, StateMachineEventTypes<S>>, EmptyConfig, 'state-machine-update', G>;
|
|
244
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timer Plugin for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Provides ECS-native timers following the "data, not callbacks" philosophy.
|
|
5
|
+
* Timers are components processed each frame, automatically cleaned up when entities are removed.
|
|
6
|
+
*/
|
|
7
|
+
import { type Plugin, type BasePluginOptions } from 'ecspresso';
|
|
8
|
+
import type { WorldConfigFrom, EmptyConfig } from '../type-utils';
|
|
9
|
+
/**
|
|
10
|
+
* Data structure passed to onComplete callbacks when a timer completes.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* createTimer(1.5, {
|
|
15
|
+
* onComplete: (data) => {
|
|
16
|
+
* console.log(`Timer on entity ${data.entityId} finished after ${data.elapsed}s`);
|
|
17
|
+
* }
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export interface TimerEventData {
|
|
22
|
+
/** The entity ID that the timer belongs to */
|
|
23
|
+
entityId: number;
|
|
24
|
+
/** The timer's configured duration in seconds */
|
|
25
|
+
duration: number;
|
|
26
|
+
/** The actual elapsed time (may exceed duration slightly) */
|
|
27
|
+
elapsed: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Timer component data structure.
|
|
31
|
+
* Use `justFinished` to detect timer completion in your systems.
|
|
32
|
+
*/
|
|
33
|
+
export interface Timer {
|
|
34
|
+
/** Time accumulated so far (seconds) */
|
|
35
|
+
elapsed: number;
|
|
36
|
+
/** Target duration (seconds) */
|
|
37
|
+
duration: number;
|
|
38
|
+
/** Whether timer repeats after completion */
|
|
39
|
+
repeat: boolean;
|
|
40
|
+
/** Whether timer is currently running */
|
|
41
|
+
active: boolean;
|
|
42
|
+
/** True for one frame after timer completes */
|
|
43
|
+
justFinished: boolean;
|
|
44
|
+
/** Optional callback invoked when timer completes */
|
|
45
|
+
onComplete?: (data: TimerEventData) => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Component types provided by the timer plugin.
|
|
49
|
+
* Included automatically via `.withPlugin(createTimerPlugin())`.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const ecs = ECSpresso.create()
|
|
54
|
+
* .withPlugin(createTimerPlugin())
|
|
55
|
+
* .withComponentTypes<{ velocity: { x: number; y: number }; player: true }>()
|
|
56
|
+
* .build();
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export interface TimerComponentTypes {
|
|
60
|
+
timer: Timer;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Configuration options for the timer plugin.
|
|
64
|
+
*/
|
|
65
|
+
export interface TimerPluginOptions<G extends string = 'timers'> extends BasePluginOptions<G> {
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Options for timer creation
|
|
69
|
+
*/
|
|
70
|
+
export interface TimerOptions {
|
|
71
|
+
/** Callback invoked when timer completes */
|
|
72
|
+
onComplete?: (data: TimerEventData) => void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create a one-shot timer that fires once after the specified duration.
|
|
76
|
+
*
|
|
77
|
+
* @param duration Duration in seconds until the timer completes
|
|
78
|
+
* @param options Optional configuration including onComplete callback
|
|
79
|
+
* @returns Component object suitable for spreading into spawn()
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* // Timer without callback
|
|
84
|
+
* ecs.spawn({
|
|
85
|
+
* ...createTimer(2),
|
|
86
|
+
* explosion: true,
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* // Timer with onComplete callback
|
|
90
|
+
* ecs.spawn({
|
|
91
|
+
* ...createTimer(1.5, { onComplete: (data) => console.log('done', data.entityId) }),
|
|
92
|
+
* });
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function createTimer(duration: number, options?: TimerOptions): Pick<TimerComponentTypes, 'timer'>;
|
|
96
|
+
/**
|
|
97
|
+
* Create a repeating timer that fires every `duration` seconds.
|
|
98
|
+
*
|
|
99
|
+
* @param duration Duration in seconds between each timer completion
|
|
100
|
+
* @param options Optional configuration including onComplete callback
|
|
101
|
+
* @returns Component object suitable for spreading into spawn()
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* // Timer without callback
|
|
106
|
+
* ecs.spawn({
|
|
107
|
+
* ...createRepeatingTimer(5),
|
|
108
|
+
* spawner: true,
|
|
109
|
+
* });
|
|
110
|
+
*
|
|
111
|
+
* // Repeating timer with onComplete callback
|
|
112
|
+
* ecs.spawn({
|
|
113
|
+
* ...createRepeatingTimer(3, { onComplete: (data) => console.log('cycle', data.elapsed) }),
|
|
114
|
+
* });
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare function createRepeatingTimer(duration: number, options?: TimerOptions): Pick<TimerComponentTypes, 'timer'>;
|
|
118
|
+
/**
|
|
119
|
+
* Create a timer plugin for ECSpresso.
|
|
120
|
+
*
|
|
121
|
+
* This plugin provides:
|
|
122
|
+
* - Timer update system that processes all timer components each frame
|
|
123
|
+
* - `justFinished` flag pattern for one-frame completion detection
|
|
124
|
+
* - Automatic cleanup when entities are removed
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const ecs = ECSpresso
|
|
129
|
+
* .create<Components, Events, Resources>()
|
|
130
|
+
* .withPlugin(createTimerPlugin())
|
|
131
|
+
* .build();
|
|
132
|
+
*
|
|
133
|
+
* // Spawn entity with timer
|
|
134
|
+
* ecs.spawn({
|
|
135
|
+
* ...createRepeatingTimer(5),
|
|
136
|
+
* spawner: true,
|
|
137
|
+
* });
|
|
138
|
+
*
|
|
139
|
+
* // React to timer completion in a system
|
|
140
|
+
* ecs.addSystem('spawn-on-timer')
|
|
141
|
+
* .addQuery('spawners', { with: ['timer', 'spawner'] })
|
|
142
|
+
* .setProcess((queries, _dt, ecs) => {
|
|
143
|
+
* for (const { components } of queries.spawners) {
|
|
144
|
+
* if (components.timer.justFinished) {
|
|
145
|
+
* ecs.spawn({ enemy: true });
|
|
146
|
+
* }
|
|
147
|
+
* }
|
|
148
|
+
* });
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export declare function createTimerPlugin<G extends string = 'timers'>(options?: TimerPluginOptions<G>): Plugin<WorldConfigFrom<TimerComponentTypes>, EmptyConfig, 'timer-update', G>;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Transform
|
|
2
|
+
* Transform Plugin for ECSpresso
|
|
3
3
|
*
|
|
4
4
|
* Provides hierarchical transform propagation following Bevy's Transform/GlobalTransform pattern.
|
|
5
5
|
* LocalTransform is modified by user code; WorldTransform is computed automatically.
|
|
6
6
|
*
|
|
7
7
|
* @see https://docs.rs/bevy/latest/bevy/transform/components/struct.GlobalTransform.html
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import type {
|
|
9
|
+
import { type Plugin, type BasePluginOptions } from 'ecspresso';
|
|
10
|
+
import type { WorldConfigFrom, EmptyConfig } from '../type-utils';
|
|
11
11
|
/**
|
|
12
12
|
* Local transform relative to parent (or world if no parent).
|
|
13
13
|
* This is the transform you modify directly.
|
|
@@ -31,15 +31,15 @@ export interface WorldTransform {
|
|
|
31
31
|
scaleY: number;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Component types provided by the transform
|
|
35
|
-
*
|
|
34
|
+
* Component types provided by the transform plugin.
|
|
35
|
+
* Included automatically via `.withPlugin(createTransformPlugin())`.
|
|
36
36
|
*
|
|
37
37
|
* @example
|
|
38
38
|
* ```typescript
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* velocity: { x: number; y: number }
|
|
42
|
-
*
|
|
39
|
+
* const ecs = ECSpresso.create()
|
|
40
|
+
* .withPlugin(createTransformPlugin())
|
|
41
|
+
* .withComponentTypes<{ sprite: Sprite; velocity: { x: number; y: number } }>()
|
|
42
|
+
* .build();
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
45
|
export interface TransformComponentTypes {
|
|
@@ -47,15 +47,14 @@ export interface TransformComponentTypes {
|
|
|
47
47
|
worldTransform: WorldTransform;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* WorldConfig representing the transform plugin's provided components.
|
|
51
|
+
* Used as the `Requires` type parameter by plugins that depend on transform.
|
|
52
|
+
*/
|
|
53
|
+
export type TransformWorldConfig = WorldConfigFrom<TransformComponentTypes>;
|
|
54
|
+
/**
|
|
55
|
+
* Configuration options for the transform plugin.
|
|
51
56
|
*/
|
|
52
|
-
export interface
|
|
53
|
-
/** System group name (default: 'transform') */
|
|
54
|
-
systemGroup?: string;
|
|
55
|
-
/** Priority for transform propagation (default: 500, runs after physics/movement) */
|
|
56
|
-
priority?: number;
|
|
57
|
-
/** Execution phase (default: 'postUpdate') */
|
|
58
|
-
phase?: SystemPhase;
|
|
57
|
+
export interface TransformPluginOptions<G extends string = 'transform'> extends BasePluginOptions<G> {
|
|
59
58
|
}
|
|
60
59
|
/**
|
|
61
60
|
* Default local transform values.
|
|
@@ -126,9 +125,9 @@ export interface TransformOptions {
|
|
|
126
125
|
*/
|
|
127
126
|
export declare function createTransform(x: number, y: number, options?: TransformOptions): TransformComponentTypes;
|
|
128
127
|
/**
|
|
129
|
-
* Create a transform
|
|
128
|
+
* Create a transform plugin for ECSpresso.
|
|
130
129
|
*
|
|
131
|
-
* This
|
|
130
|
+
* This plugin provides:
|
|
132
131
|
* - Transform propagation system that computes world transforms from local transforms
|
|
133
132
|
* - Parent-first traversal ensures parents are processed before children
|
|
134
133
|
* - Supports full transform hierarchy (position, rotation, scale)
|
|
@@ -137,8 +136,8 @@ export declare function createTransform(x: number, y: number, options?: Transfor
|
|
|
137
136
|
* ```typescript
|
|
138
137
|
* const ecs = ECSpresso
|
|
139
138
|
* .create<Components, Events, Resources>()
|
|
140
|
-
* .
|
|
141
|
-
* .
|
|
139
|
+
* .withPlugin(createTransformPlugin())
|
|
140
|
+
* .withPlugin(createPhysics2DPlugin())
|
|
142
141
|
* .build();
|
|
143
142
|
*
|
|
144
143
|
* // Spawn entity with transform
|
|
@@ -148,4 +147,4 @@ export declare function createTransform(x: number, y: number, options?: Transfor
|
|
|
148
147
|
* });
|
|
149
148
|
* ```
|
|
150
149
|
*/
|
|
151
|
-
export declare function
|
|
150
|
+
export declare function createTransformPlugin<G extends string = 'transform'>(options?: TransformPluginOptions<G>): Plugin<WorldConfigFrom<TransformComponentTypes>, EmptyConfig, 'transform-propagation', G>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tween Plugin for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Declarative property animation within the ECS. Tween any numeric component
|
|
5
|
+
* field over time with standard easing functions, sequences, and completion events.
|
|
6
|
+
*/
|
|
7
|
+
import { type Plugin, type BasePluginOptions } from 'ecspresso';
|
|
8
|
+
import type { ComponentsOfWorld, AnyECSpresso } from 'ecspresso';
|
|
9
|
+
import type { WorldConfigFrom, EmptyConfig } from '../type-utils';
|
|
10
|
+
import { type EasingFn } from '../utils/easing';
|
|
11
|
+
/**
|
|
12
|
+
* Data structure published when a tween completes.
|
|
13
|
+
* Use this type when defining tween completion events in your EventTypes interface.
|
|
14
|
+
*/
|
|
15
|
+
export interface TweenEventData {
|
|
16
|
+
/** The entity ID the tween belongs to */
|
|
17
|
+
entityId: number;
|
|
18
|
+
/** Number of steps in the tween */
|
|
19
|
+
stepCount: number;
|
|
20
|
+
}
|
|
21
|
+
export interface TweenTarget {
|
|
22
|
+
/** Component name on the entity */
|
|
23
|
+
component: string;
|
|
24
|
+
/** Pre-split field path (e.g., ['position', 'x']) */
|
|
25
|
+
path: readonly string[];
|
|
26
|
+
/** Starting value. null = resolve from current value on first tick */
|
|
27
|
+
from: number | null;
|
|
28
|
+
/** Target value */
|
|
29
|
+
to: number;
|
|
30
|
+
}
|
|
31
|
+
export interface TweenStep {
|
|
32
|
+
targets: TweenTarget[];
|
|
33
|
+
duration: number;
|
|
34
|
+
easing: EasingFn;
|
|
35
|
+
}
|
|
36
|
+
export interface Tween {
|
|
37
|
+
steps: TweenStep[];
|
|
38
|
+
currentStep: number;
|
|
39
|
+
elapsed: number;
|
|
40
|
+
loop: LoopMode;
|
|
41
|
+
totalLoops: number;
|
|
42
|
+
completedLoops: number;
|
|
43
|
+
direction: 1 | -1;
|
|
44
|
+
state: 'pending' | 'active' | 'complete';
|
|
45
|
+
onComplete?: (data: TweenEventData) => void;
|
|
46
|
+
justFinished: boolean;
|
|
47
|
+
}
|
|
48
|
+
export type LoopMode = 'once' | 'loop' | 'yoyo';
|
|
49
|
+
/**
|
|
50
|
+
* Component types provided by the tween plugin.
|
|
51
|
+
*/
|
|
52
|
+
export interface TweenComponentTypes {
|
|
53
|
+
tween: Tween;
|
|
54
|
+
}
|
|
55
|
+
export interface TweenPluginOptions<G extends string = 'tweens'> extends BasePluginOptions<G> {
|
|
56
|
+
}
|
|
57
|
+
export interface TweenOptions {
|
|
58
|
+
/** Explicit starting value (default: captures current value on first tick) */
|
|
59
|
+
from?: number;
|
|
60
|
+
/** Easing function (default: linear) */
|
|
61
|
+
easing?: EasingFn;
|
|
62
|
+
/** Loop mode (default: 'once') */
|
|
63
|
+
loop?: LoopMode;
|
|
64
|
+
/** Number of loops. -1 = infinite (default: 1) */
|
|
65
|
+
loops?: number;
|
|
66
|
+
/** Callback invoked when tween completes */
|
|
67
|
+
onComplete?: (data: TweenEventData) => void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a single-target tween component.
|
|
71
|
+
*
|
|
72
|
+
* @param component Component name on the entity
|
|
73
|
+
* @param field Field path (dot-separated for nested, e.g. 'position.x')
|
|
74
|
+
* @param to Target value
|
|
75
|
+
* @param duration Duration in seconds
|
|
76
|
+
* @param options Optional configuration
|
|
77
|
+
* @returns Component object suitable for spreading into spawn()
|
|
78
|
+
*/
|
|
79
|
+
export declare function createTween(component: string, field: string, to: number, duration: number, options?: TweenOptions): Pick<TweenComponentTypes, 'tween'>;
|
|
80
|
+
export interface TweenSequenceStepInput {
|
|
81
|
+
targets: ReadonlyArray<{
|
|
82
|
+
component: string;
|
|
83
|
+
field: string;
|
|
84
|
+
to: number;
|
|
85
|
+
from?: number;
|
|
86
|
+
}>;
|
|
87
|
+
duration: number;
|
|
88
|
+
easing?: EasingFn;
|
|
89
|
+
}
|
|
90
|
+
export interface TweenSequenceOptions {
|
|
91
|
+
/** Loop mode (default: 'once') */
|
|
92
|
+
loop?: LoopMode;
|
|
93
|
+
/** Number of loops. -1 = infinite (default: 1) */
|
|
94
|
+
loops?: number;
|
|
95
|
+
/** Callback invoked when tween completes */
|
|
96
|
+
onComplete?: (data: TweenEventData) => void;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create a multi-step tween sequence. Each step can have parallel targets.
|
|
100
|
+
*
|
|
101
|
+
* @param steps Array of step definitions
|
|
102
|
+
* @param options Optional configuration
|
|
103
|
+
* @returns Component object suitable for spreading into spawn()
|
|
104
|
+
*/
|
|
105
|
+
export declare function createTweenSequence(steps: ReadonlyArray<TweenSequenceStepInput>, options?: TweenSequenceOptions): Pick<TweenComponentTypes, 'tween'>;
|
|
106
|
+
/**
|
|
107
|
+
* Recursively produce a union of dot-separated paths that resolve to `number`
|
|
108
|
+
* within type T. Depth-limited to 4 levels to prevent TS recursion errors.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* NumericPaths<{ x: number; y: number }> // 'x' | 'y'
|
|
112
|
+
* NumericPaths<{ position: { x: number }; rotation: number }> // 'position.x' | 'rotation'
|
|
113
|
+
*/
|
|
114
|
+
export type NumericPaths<T, Depth extends readonly unknown[] = []> = Depth['length'] extends 4 ? never : T extends readonly unknown[] ? never : T extends Record<string, unknown> ? {
|
|
115
|
+
[K in keyof T & string]: NonNullable<T[K]> extends number ? K : NonNullable<T[K]> extends readonly unknown[] ? never : NonNullable<T[K]> extends Record<string, unknown> ? `${K}.${NumericPaths<NonNullable<T[K]>, [...Depth, unknown]>}` : never;
|
|
116
|
+
}[keyof T & string] : never;
|
|
117
|
+
/**
|
|
118
|
+
* Discriminated union over component names: each variant constrains `field`
|
|
119
|
+
* to the numeric paths of that component. TS narrows inline object literals
|
|
120
|
+
* by `component` discriminant — zero runtime overhead.
|
|
121
|
+
*/
|
|
122
|
+
export type TypedTweenTargetInput<C extends Record<string, any>> = {
|
|
123
|
+
[K in keyof C & string]: {
|
|
124
|
+
component: K;
|
|
125
|
+
field: NumericPaths<C[K]>;
|
|
126
|
+
to: number;
|
|
127
|
+
from?: number;
|
|
128
|
+
};
|
|
129
|
+
}[keyof C & string];
|
|
130
|
+
export interface TypedTweenSequenceStepInput<C extends Record<string, any>> {
|
|
131
|
+
targets: ReadonlyArray<TypedTweenTargetInput<C>>;
|
|
132
|
+
duration: number;
|
|
133
|
+
easing?: EasingFn;
|
|
134
|
+
}
|
|
135
|
+
export interface TweenHelpers<W extends AnyECSpresso> {
|
|
136
|
+
createTween: <K extends keyof ComponentsOfWorld<W> & string>(component: K, field: NumericPaths<ComponentsOfWorld<W>[K]>, to: number, duration: number, options?: {
|
|
137
|
+
from?: number;
|
|
138
|
+
easing?: EasingFn;
|
|
139
|
+
loop?: LoopMode;
|
|
140
|
+
loops?: number;
|
|
141
|
+
onComplete?: (data: TweenEventData) => void;
|
|
142
|
+
}) => Pick<TweenComponentTypes, 'tween'>;
|
|
143
|
+
createTweenSequence: (steps: ReadonlyArray<TypedTweenSequenceStepInput<ComponentsOfWorld<W>>>, options?: {
|
|
144
|
+
loop?: LoopMode;
|
|
145
|
+
loops?: number;
|
|
146
|
+
onComplete?: (data: TweenEventData) => void;
|
|
147
|
+
}) => Pick<TweenComponentTypes, 'tween'>;
|
|
148
|
+
}
|
|
149
|
+
export declare function createTweenHelpers<W extends AnyECSpresso>(_world?: W): TweenHelpers<W>;
|
|
150
|
+
/**
|
|
151
|
+
* Create a tween plugin for ECSpresso.
|
|
152
|
+
*
|
|
153
|
+
* This plugin provides:
|
|
154
|
+
* - Tween system that processes all tween components each frame
|
|
155
|
+
* - Support for single-field, multi-target, and multi-step sequences
|
|
156
|
+
* - 31 standard easing functions
|
|
157
|
+
* - Loop modes: once, loop, yoyo
|
|
158
|
+
* - `justFinished` flag for one-frame completion detection
|
|
159
|
+
* - `onComplete` callback on completion
|
|
160
|
+
* - Change detection via markChanged
|
|
161
|
+
*/
|
|
162
|
+
export declare function createTweenPlugin<G extends string = 'tweens'>(options?: TweenPluginOptions<G>): Plugin<WorldConfigFrom<TweenComponentTypes>, EmptyConfig, 'tween-update', G>;
|
|
@@ -20,7 +20,7 @@ export interface ReactiveQueryDefinition<ComponentTypes extends Record<string, a
|
|
|
20
20
|
/**
|
|
21
21
|
* Manages reactive queries that trigger callbacks when entities enter/exit query matches
|
|
22
22
|
*/
|
|
23
|
-
export default class ReactiveQueryManager<ComponentTypes extends Record<string, any
|
|
23
|
+
export default class ReactiveQueryManager<ComponentTypes extends Record<string, any>, QueryNames extends string = string> {
|
|
24
24
|
private queries;
|
|
25
25
|
private entityManager;
|
|
26
26
|
/** Whether any registered query uses parentHas */
|
|
@@ -35,17 +35,22 @@ export default class ReactiveQueryManager<ComponentTypes extends Record<string,
|
|
|
35
35
|
* @param name Unique name for the query
|
|
36
36
|
* @param definition Query definition with callbacks
|
|
37
37
|
*/
|
|
38
|
-
addQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never, OptionalComponents extends keyof ComponentTypes = never>(name:
|
|
38
|
+
addQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never, OptionalComponents extends keyof ComponentTypes = never>(name: QueryNames, definition: ReactiveQueryDefinition<ComponentTypes, WithComponents, WithoutComponents, OptionalComponents>): void;
|
|
39
39
|
/**
|
|
40
40
|
* Remove a reactive query
|
|
41
41
|
* @param name Name of the query to remove
|
|
42
42
|
* @returns true if the query existed and was removed
|
|
43
43
|
*/
|
|
44
|
-
removeQuery(name:
|
|
44
|
+
removeQuery(name: QueryNames): boolean;
|
|
45
45
|
/**
|
|
46
46
|
* Check if an entity matches a query definition
|
|
47
47
|
*/
|
|
48
48
|
private entityMatchesQuery;
|
|
49
|
+
/**
|
|
50
|
+
* Apply enter/exit transitions for a single query against an entity.
|
|
51
|
+
* Fires onEnter when entity starts matching, onExit when it stops.
|
|
52
|
+
*/
|
|
53
|
+
private _applyQueryTransition;
|
|
49
54
|
/**
|
|
50
55
|
* Called when a component is added to an entity
|
|
51
56
|
* Checks all queries for potential enter/exit events
|
|
@@ -66,6 +71,12 @@ export default class ReactiveQueryManager<ComponentTypes extends Record<string,
|
|
|
66
71
|
* Fires enter/exit callbacks as appropriate based on current state vs tracked state
|
|
67
72
|
*/
|
|
68
73
|
recheckEntity(entity: Entity<ComponentTypes>): void;
|
|
74
|
+
/**
|
|
75
|
+
* Recheck an entity and its children against all queries.
|
|
76
|
+
* Used after component mutations to handle both the entity's own queries
|
|
77
|
+
* and parentHas queries on its children.
|
|
78
|
+
*/
|
|
79
|
+
recheckEntityAndChildren(entity: Entity<ComponentTypes>): void;
|
|
69
80
|
/**
|
|
70
81
|
* Recheck all children of a parent entity against parentHas queries.
|
|
71
82
|
* Called when a component is added/removed from a parent entity.
|