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
package/dist/entity-manager.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export default class EntityManager<ComponentTypes> {
|
|
|
15
15
|
* Hierarchy manager for parent-child relationships
|
|
16
16
|
*/
|
|
17
17
|
private hierarchyManager;
|
|
18
|
+
/**
|
|
19
|
+
* Per-type component dispose callbacks.
|
|
20
|
+
* Called when a component is removed (explicit removal, entity destruction, or replacement).
|
|
21
|
+
*/
|
|
22
|
+
private disposeCallbacks;
|
|
18
23
|
/**
|
|
19
24
|
* Per-entity per-component change sequence tracking.
|
|
20
25
|
* Maps entityId -> (componentName -> sequence number when last changed)
|
|
@@ -25,24 +30,59 @@ export default class EntityManager<ComponentTypes> {
|
|
|
25
30
|
* Each markChanged call increments this and stamps the new value.
|
|
26
31
|
*/
|
|
27
32
|
private _changeSeq;
|
|
33
|
+
private _afterComponentAddedHooks;
|
|
34
|
+
private _afterEntityMutatedHooks;
|
|
35
|
+
private _afterComponentRemovedHooks;
|
|
36
|
+
private _beforeEntityRemovedHooks;
|
|
37
|
+
private _afterParentChangedHooks;
|
|
38
|
+
private _batchingDepth;
|
|
39
|
+
private _batchedEntityIds;
|
|
40
|
+
/** Component keys being added in the current addComponents batch, if any.
|
|
41
|
+
* Used by required component resolution to skip auto-adding explicitly provided components. */
|
|
42
|
+
_pendingBatchKeys: ReadonlySet<keyof ComponentTypes> | null;
|
|
43
|
+
get entityCount(): number;
|
|
28
44
|
createEntity(): Entity<ComponentTypes>;
|
|
29
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Register a dispose callback for a component type.
|
|
47
|
+
* Called when a component is removed (explicit removal, entity destruction, or replacement).
|
|
48
|
+
* Later registrations replace earlier ones for the same component type.
|
|
49
|
+
* @param componentName The component type to register disposal for
|
|
50
|
+
* @param callback Function receiving the component value being disposed and the entity ID
|
|
51
|
+
*/
|
|
52
|
+
registerDispose<ComponentName extends keyof ComponentTypes>(componentName: ComponentName, callback: (ctx: {
|
|
53
|
+
value: ComponentTypes[ComponentName];
|
|
54
|
+
entityId: number;
|
|
55
|
+
}) => void): void;
|
|
56
|
+
/**
|
|
57
|
+
* Get all registered dispose callbacks.
|
|
58
|
+
* @internal Used by ECSpresso for plugin installation
|
|
59
|
+
*/
|
|
60
|
+
getDisposeCallbacks(): Map<keyof ComponentTypes, (ctx: {
|
|
61
|
+
value: unknown;
|
|
62
|
+
entityId: number;
|
|
63
|
+
}) => void>;
|
|
64
|
+
/**
|
|
65
|
+
* Invoke the dispose callback for a component, if registered.
|
|
66
|
+
* Errors are caught and logged to prevent blocking removal.
|
|
67
|
+
*/
|
|
68
|
+
private invokeDispose;
|
|
69
|
+
addComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName, data: ComponentTypes[ComponentName]): this;
|
|
30
70
|
/**
|
|
31
71
|
* Add multiple components to an entity at once
|
|
32
|
-
* @param
|
|
72
|
+
* @param entityId Entity ID to add components to
|
|
33
73
|
* @param components Object with component names as keys and component data as values
|
|
34
74
|
*/
|
|
35
75
|
addComponents<T extends {
|
|
36
76
|
[K in keyof ComponentTypes]?: ComponentTypes[K];
|
|
37
|
-
}>(
|
|
38
|
-
removeComponent<ComponentName extends keyof ComponentTypes>(
|
|
39
|
-
getComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName): ComponentTypes[ComponentName] |
|
|
77
|
+
}>(entityId: number, components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): this;
|
|
78
|
+
removeComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName): this;
|
|
79
|
+
getComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName): ComponentTypes[ComponentName] | undefined;
|
|
40
80
|
getEntitiesWithQuery<WithComponents extends keyof ComponentTypes = never, WithoutComponents extends keyof ComponentTypes = never>(required?: ReadonlyArray<WithComponents>, excluded?: ReadonlyArray<WithoutComponents>, changed?: ReadonlyArray<keyof ComponentTypes>, changeThreshold?: number, parentHas?: ReadonlyArray<keyof ComponentTypes>): Array<FilteredEntity<ComponentTypes, WithComponents extends never ? never : WithComponents, WithoutComponents extends never ? never : WithoutComponents>>;
|
|
41
81
|
/**
|
|
42
82
|
* Check if an entity's direct parent has all specified components
|
|
43
83
|
*/
|
|
44
84
|
private parentHasComponents;
|
|
45
|
-
removeEntity(
|
|
85
|
+
removeEntity(entityId: number, options?: RemoveEntityOptions): boolean;
|
|
46
86
|
/**
|
|
47
87
|
* Internal method to remove a single entity without cascade logic
|
|
48
88
|
*/
|
|
@@ -54,14 +94,25 @@ export default class EntityManager<ComponentTypes> {
|
|
|
54
94
|
* @param handler Function receiving the new component value and the entity
|
|
55
95
|
* @returns Unsubscribe function to remove the callback
|
|
56
96
|
*/
|
|
57
|
-
onComponentAdded<ComponentName extends keyof ComponentTypes>(componentName: ComponentName, handler: (
|
|
97
|
+
onComponentAdded<ComponentName extends keyof ComponentTypes>(componentName: ComponentName, handler: (ctx: {
|
|
98
|
+
value: ComponentTypes[ComponentName];
|
|
99
|
+
entity: Entity<ComponentTypes>;
|
|
100
|
+
}) => void): () => void;
|
|
58
101
|
/**
|
|
59
102
|
* Register a callback when a specific component is removed from any entity
|
|
60
103
|
* @param componentName The component key
|
|
61
104
|
* @param handler Function receiving the old component value and the entity
|
|
62
105
|
* @returns Unsubscribe function to remove the callback
|
|
63
106
|
*/
|
|
64
|
-
onComponentRemoved<ComponentName extends keyof ComponentTypes>(componentName: ComponentName, handler: (
|
|
107
|
+
onComponentRemoved<ComponentName extends keyof ComponentTypes>(componentName: ComponentName, handler: (ctx: {
|
|
108
|
+
value: ComponentTypes[ComponentName];
|
|
109
|
+
entity: Entity<ComponentTypes>;
|
|
110
|
+
}) => void): () => void;
|
|
111
|
+
onAfterComponentAdded(hook: (entityId: number, componentName: keyof ComponentTypes) => void): () => void;
|
|
112
|
+
onAfterEntityMutated(hook: (entityId: number) => void): () => void;
|
|
113
|
+
onAfterComponentRemoved(hook: (entityId: number, componentName: keyof ComponentTypes) => void): () => void;
|
|
114
|
+
onBeforeEntityRemoved(hook: (entityId: number) => void): () => void;
|
|
115
|
+
onAfterParentChanged(hook: (childId: number) => void): () => void;
|
|
65
116
|
/**
|
|
66
117
|
* The current monotonic change sequence value.
|
|
67
118
|
* Each markChanged call increments this before stamping.
|
|
@@ -80,11 +131,6 @@ export default class EntityManager<ComponentTypes> {
|
|
|
80
131
|
* @returns The sequence number when last changed, or -1 if never changed
|
|
81
132
|
*/
|
|
82
133
|
getChangeSeq<K extends keyof ComponentTypes>(entityId: number, componentName: K): number;
|
|
83
|
-
/**
|
|
84
|
-
* Clear all change sequences for an entity
|
|
85
|
-
* @param entityId The entity ID
|
|
86
|
-
*/
|
|
87
|
-
clearChangeSeqs(entityId: number): void;
|
|
88
134
|
/**
|
|
89
135
|
* Create an entity as a child of another entity with initial components
|
|
90
136
|
* @param parentId The parent entity ID
|
|
@@ -93,80 +139,80 @@ export default class EntityManager<ComponentTypes> {
|
|
|
93
139
|
*/
|
|
94
140
|
spawnChild<T extends {
|
|
95
141
|
[K in keyof ComponentTypes]?: ComponentTypes[K];
|
|
96
|
-
}>(parentId: number, components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes
|
|
142
|
+
}>(parentId: number, components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes>;
|
|
97
143
|
/**
|
|
98
144
|
* Set the parent of an entity
|
|
99
|
-
* @param childId The entity to set as a child
|
|
100
|
-
* @param parentId The entity to set as the parent
|
|
145
|
+
* @param childId The entity ID to set as a child
|
|
146
|
+
* @param parentId The entity ID to set as the parent
|
|
101
147
|
*/
|
|
102
148
|
setParent(childId: number, parentId: number): this;
|
|
103
149
|
/**
|
|
104
150
|
* Remove the parent relationship for an entity (orphan it)
|
|
105
|
-
* @param childId The entity to orphan
|
|
151
|
+
* @param childId The entity ID to orphan
|
|
106
152
|
* @returns true if a parent was removed, false if entity had no parent
|
|
107
153
|
*/
|
|
108
154
|
removeParent(childId: number): boolean;
|
|
109
155
|
/**
|
|
110
156
|
* Get the parent of an entity
|
|
111
|
-
* @param entityId The entity to get the parent of
|
|
157
|
+
* @param entityId The entity ID to get the parent of
|
|
112
158
|
* @returns The parent entity ID, or null if no parent
|
|
113
159
|
*/
|
|
114
160
|
getParent(entityId: number): number | null;
|
|
115
161
|
/**
|
|
116
162
|
* Get all children of an entity in insertion order
|
|
117
|
-
* @param parentId The parent entity
|
|
163
|
+
* @param parentId The parent entity ID
|
|
118
164
|
* @returns Readonly array of child entity IDs
|
|
119
165
|
*/
|
|
120
166
|
getChildren(parentId: number): readonly number[];
|
|
121
167
|
/**
|
|
122
168
|
* Get a child at a specific index
|
|
123
|
-
* @param parentId The parent entity
|
|
169
|
+
* @param parentId The parent entity ID
|
|
124
170
|
* @param index The index of the child
|
|
125
171
|
* @returns The child entity ID, or null if index is out of bounds
|
|
126
172
|
*/
|
|
127
173
|
getChildAt(parentId: number, index: number): number | null;
|
|
128
174
|
/**
|
|
129
175
|
* Get the index of a child within its parent's children list
|
|
130
|
-
* @param parentId The parent entity
|
|
131
|
-
* @param childId The child entity to find
|
|
176
|
+
* @param parentId The parent entity ID
|
|
177
|
+
* @param childId The child entity ID to find
|
|
132
178
|
* @returns The index of the child, or -1 if not found
|
|
133
179
|
*/
|
|
134
180
|
getChildIndex(parentId: number, childId: number): number;
|
|
135
181
|
/**
|
|
136
182
|
* Get all ancestors of an entity in order [parent, grandparent, ...]
|
|
137
|
-
* @param entityId The entity to get ancestors of
|
|
183
|
+
* @param entityId The entity ID to get ancestors of
|
|
138
184
|
* @returns Readonly array of ancestor entity IDs
|
|
139
185
|
*/
|
|
140
186
|
getAncestors(entityId: number): readonly number[];
|
|
141
187
|
/**
|
|
142
188
|
* Get all descendants of an entity in depth-first order
|
|
143
|
-
* @param entityId The entity to get descendants of
|
|
189
|
+
* @param entityId The entity ID to get descendants of
|
|
144
190
|
* @returns Readonly array of descendant entity IDs
|
|
145
191
|
*/
|
|
146
192
|
getDescendants(entityId: number): readonly number[];
|
|
147
193
|
/**
|
|
148
194
|
* Get the root ancestor of an entity (topmost parent), or self if no parent
|
|
149
|
-
* @param entityId The entity to get the root of
|
|
195
|
+
* @param entityId The entity ID to get the root of
|
|
150
196
|
* @returns The root entity ID
|
|
151
197
|
*/
|
|
152
198
|
getRoot(entityId: number): number;
|
|
153
199
|
/**
|
|
154
200
|
* Get siblings of an entity (other children of the same parent)
|
|
155
|
-
* @param entityId The entity to get siblings of
|
|
201
|
+
* @param entityId The entity ID to get siblings of
|
|
156
202
|
* @returns Readonly array of sibling entity IDs
|
|
157
203
|
*/
|
|
158
204
|
getSiblings(entityId: number): readonly number[];
|
|
159
205
|
/**
|
|
160
206
|
* Check if an entity is a descendant of another entity
|
|
161
|
-
* @param entityId The potential descendant
|
|
162
|
-
* @param ancestorId The potential ancestor
|
|
207
|
+
* @param entityId The potential descendant ID
|
|
208
|
+
* @param ancestorId The potential ancestor ID
|
|
163
209
|
* @returns true if entityId is a descendant of ancestorId
|
|
164
210
|
*/
|
|
165
211
|
isDescendantOf(entityId: number, ancestorId: number): boolean;
|
|
166
212
|
/**
|
|
167
213
|
* Check if an entity is an ancestor of another entity
|
|
168
|
-
* @param entityId The potential ancestor
|
|
169
|
-
* @param descendantId The potential descendant
|
|
214
|
+
* @param entityId The potential ancestor ID
|
|
215
|
+
* @param descendantId The potential descendant ID
|
|
170
216
|
* @returns true if entityId is an ancestor of descendantId
|
|
171
217
|
*/
|
|
172
218
|
isAncestorOf(entityId: number, descendantId: number): boolean;
|
package/dist/event-bus.d.ts
CHANGED
|
@@ -17,7 +17,12 @@ export default class EventBus<EventTypes> {
|
|
|
17
17
|
* Internal method to add an event handler
|
|
18
18
|
*/
|
|
19
19
|
private addHandler;
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Publish an event. Data is required unless EventTypes[E] extends void | undefined.
|
|
22
|
+
* Zero-allocation hot path: uses index-based iteration with a snapshot length
|
|
23
|
+
* so handlers added mid-publish are not called in the same publish cycle.
|
|
24
|
+
*/
|
|
25
|
+
publish<E extends keyof EventTypes>(...[eventType, data]: EventTypes[E] extends void | undefined ? [eventType: E, data?: EventTypes[E]] : [eventType: E, data: EventTypes[E]]): void;
|
|
21
26
|
clear(): void;
|
|
22
27
|
clearEvent<E extends keyof EventTypes>(eventType: E): void;
|
|
23
28
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
import ECSpresso from './ecspresso';
|
|
2
|
-
import { SystemBuilder } from './system-builder';
|
|
3
|
-
import
|
|
2
|
+
import { SystemBuilder, type ProcessContext } from './system-builder';
|
|
3
|
+
import { type Plugin, type BasePluginOptions, definePlugin } from './plugin';
|
|
4
4
|
export * from './types';
|
|
5
5
|
export * from './asset-types';
|
|
6
6
|
export * from './screen-types';
|
|
7
|
+
export * from './utils/math';
|
|
7
8
|
export type { ReactiveQueryDefinition } from './reactive-query-manager';
|
|
8
|
-
export { default as EntityManager } from './entity-manager';
|
|
9
|
-
export { default as EventBus } from './event-bus';
|
|
10
|
-
export { default as CommandBuffer } from './command-buffer';
|
|
11
|
-
export { default as HierarchyManager } from './hierarchy-manager';
|
|
12
|
-
/**
|
|
13
|
-
* @internal ResourceManager is exported for testing purposes only.
|
|
14
|
-
* Use ECSpresso resource methods instead: getResource(), addResource(), removeResource(), updateResource(), hasResource()
|
|
15
|
-
*/
|
|
16
|
-
export { default as ResourceManager } from './resource-manager';
|
|
17
9
|
export { default as AssetManager, createAssetConfigurator } from './asset-manager';
|
|
18
10
|
export { default as ScreenManager, createScreenConfigurator } from './screen-manager';
|
|
19
|
-
export { SystemBuilder };
|
|
20
|
-
export {
|
|
11
|
+
export { SystemBuilder, type ProcessContext };
|
|
12
|
+
export { type Plugin, type BasePluginOptions, definePlugin };
|
|
13
|
+
export { directValue, type ResourceDirectValue } from './resource-manager';
|
|
21
14
|
export default ECSpresso;
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type ECSpresso from './ecspresso';
|
|
2
|
+
import type { SystemPhase } from './types';
|
|
3
|
+
import type { WorldConfig, EmptyConfig, MergeConfigs, AnyECSpresso, ConfigOf } from './type-utils';
|
|
4
|
+
/**
|
|
5
|
+
* Plugin interface for ECSpresso. A plugin is a plain object with an `install`
|
|
6
|
+
* function that configures a world directly, plus phantom properties for
|
|
7
|
+
* compile-time type extraction.
|
|
8
|
+
*
|
|
9
|
+
* @typeParam Cfg - The WorldConfig this plugin provides (components, events, resources, etc.)
|
|
10
|
+
* @typeParam Requires - The WorldConfig this plugin requires from other plugins
|
|
11
|
+
*/
|
|
12
|
+
export interface Plugin<Cfg extends WorldConfig = EmptyConfig, Requires extends WorldConfig = EmptyConfig, Labels extends string = never, Groups extends string = never, AssetGroupNames extends string = never, ReactiveQueryNames extends string = never> {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly install: (world: ECSpresso<MergeConfigs<Cfg, Requires>>) => void;
|
|
15
|
+
readonly _cfg?: Cfg;
|
|
16
|
+
readonly _requires?: Requires;
|
|
17
|
+
readonly _labels?: Labels;
|
|
18
|
+
readonly _groups?: Groups;
|
|
19
|
+
readonly _assetGroupNames?: AssetGroupNames;
|
|
20
|
+
readonly _reactiveQueryNames?: ReactiveQueryNames;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Common configuration options shared by most plugins.
|
|
24
|
+
* Plugin-specific options interfaces extend this with additional fields.
|
|
25
|
+
*/
|
|
26
|
+
export interface BasePluginOptions<G extends string = string> {
|
|
27
|
+
/** System group name for all systems registered by this plugin */
|
|
28
|
+
systemGroup?: G;
|
|
29
|
+
/** Priority for the plugin's primary system (default varies per plugin) */
|
|
30
|
+
priority?: number;
|
|
31
|
+
/** Execution phase for the plugin's primary system */
|
|
32
|
+
phase?: SystemPhase;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Factory function to create a type-safe Plugin with phantom type parameters.
|
|
36
|
+
* The type assertion adds phantom types without runtime cost.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* // Option 1: Explicit config type param
|
|
41
|
+
* const myPlugin = definePlugin<WorldConfigFrom<MyComponents, MyEvents, MyResources>>({
|
|
42
|
+
* id: 'my-plugin',
|
|
43
|
+
* install(world) { ... },
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // Option 2: Single world type param (extracts config automatically)
|
|
47
|
+
* type MyWorld = typeof ecs;
|
|
48
|
+
* const myPlugin = definePlugin<MyWorld>({
|
|
49
|
+
* id: 'my-plugin',
|
|
50
|
+
* install(world) { ... },
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function definePlugin<W extends AnyECSpresso, Requires extends WorldConfig = EmptyConfig, Labels extends string = never, Groups extends string = never, AssetGroupNames extends string = never, ReactiveQueryNames extends string = never>(config: {
|
|
55
|
+
id: string;
|
|
56
|
+
install: (world: W) => void;
|
|
57
|
+
}): Plugin<ConfigOf<W>, Requires, Labels, Groups, AssetGroupNames, ReactiveQueryNames>;
|
|
58
|
+
export declare function definePlugin<Cfg extends WorldConfig = EmptyConfig, Requires extends WorldConfig = EmptyConfig, Labels extends string = never, Groups extends string = never, AssetGroupNames extends string = never, ReactiveQueryNames extends string = never>(config: {
|
|
59
|
+
id: string;
|
|
60
|
+
install: (world: ECSpresso<MergeConfigs<Cfg, Requires>>) => void;
|
|
61
|
+
}): Plugin<Cfg, Requires, Labels, Groups, AssetGroupNames, ReactiveQueryNames>;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio Plugin for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Web Audio API integration via Howler.js for sound effects and music playback.
|
|
5
|
+
* User-defined channels with type-safe volume control, hybrid resource + component API,
|
|
6
|
+
* and asset manager integration.
|
|
7
|
+
*/
|
|
8
|
+
import { type Plugin, type BasePluginOptions } from 'ecspresso';
|
|
9
|
+
import type { AssetsOfWorld, AnyECSpresso, ChannelOfWorld } from 'ecspresso';
|
|
10
|
+
import type { WorldConfigFrom, EmptyConfig } from '../type-utils';
|
|
11
|
+
import type { Howl } from 'howler';
|
|
12
|
+
/**
|
|
13
|
+
* Configuration for a single audio channel.
|
|
14
|
+
*/
|
|
15
|
+
export interface AudioChannelConfig {
|
|
16
|
+
readonly volume: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Define audio channels with type-safe names and initial volumes.
|
|
20
|
+
* Mirrors `defineCollisionLayers` pattern.
|
|
21
|
+
*
|
|
22
|
+
* @param channels Object mapping channel names to their configuration
|
|
23
|
+
* @returns Frozen channel configuration with inferred channel name union
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const channels = defineAudioChannels({
|
|
28
|
+
* sfx: { volume: 1 },
|
|
29
|
+
* music: { volume: 0.7 },
|
|
30
|
+
* ui: { volume: 0.8 },
|
|
31
|
+
* });
|
|
32
|
+
* type Ch = ChannelsOf<typeof channels>; // 'sfx' | 'music' | 'ui'
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function defineAudioChannels<const T extends Record<string, AudioChannelConfig>>(channels: T): Readonly<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Extract channel name union from a `defineAudioChannels` result.
|
|
38
|
+
*/
|
|
39
|
+
export type ChannelsOf<T> = T extends Record<infer K extends string, AudioChannelConfig> ? K : never;
|
|
40
|
+
/**
|
|
41
|
+
* Audio source component attached to entities for positional/entity-bound audio.
|
|
42
|
+
*/
|
|
43
|
+
export interface AudioSource<Ch extends string = string> {
|
|
44
|
+
/** Asset key for the sound */
|
|
45
|
+
readonly sound: string;
|
|
46
|
+
/** Channel this sound plays on */
|
|
47
|
+
readonly channel: Ch;
|
|
48
|
+
/** Individual volume (0-1) */
|
|
49
|
+
volume: number;
|
|
50
|
+
/** Whether sound loops */
|
|
51
|
+
loop: boolean;
|
|
52
|
+
/** Remove entity when sound ends (like timer autoRemove) */
|
|
53
|
+
autoRemove: boolean;
|
|
54
|
+
/** Whether sound is currently playing (system-managed) */
|
|
55
|
+
playing: boolean;
|
|
56
|
+
/** Howler sound ID (system-managed, -1 = not started) */
|
|
57
|
+
_soundId: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Component types provided by the audio plugin.
|
|
61
|
+
*/
|
|
62
|
+
export interface AudioComponentTypes<Ch extends string = string> {
|
|
63
|
+
audioSource: AudioSource<Ch>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Event to trigger fire-and-forget sound playback from any system.
|
|
67
|
+
*/
|
|
68
|
+
export interface PlaySoundEvent<Ch extends string = string> {
|
|
69
|
+
/** Asset key for the sound */
|
|
70
|
+
sound: string;
|
|
71
|
+
/** Channel to play on */
|
|
72
|
+
channel?: Ch;
|
|
73
|
+
/** Individual volume (0-1) */
|
|
74
|
+
volume?: number;
|
|
75
|
+
/** Whether sound loops */
|
|
76
|
+
loop?: boolean;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Event to stop music on a channel.
|
|
80
|
+
*/
|
|
81
|
+
export interface StopMusicEvent<Ch extends string = string> {
|
|
82
|
+
/** Channel to stop music on. If omitted, stops all music. */
|
|
83
|
+
channel?: Ch;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Event published when a sound finishes playing.
|
|
87
|
+
*/
|
|
88
|
+
export interface SoundEndedEvent {
|
|
89
|
+
/** Entity ID if sound was entity-attached, -1 for fire-and-forget */
|
|
90
|
+
entityId: number;
|
|
91
|
+
/** Howler sound ID */
|
|
92
|
+
soundId: number;
|
|
93
|
+
/** Asset key of the sound */
|
|
94
|
+
sound: string;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Event types provided by the audio plugin.
|
|
98
|
+
*/
|
|
99
|
+
export interface AudioEventTypes<Ch extends string = string> {
|
|
100
|
+
playSound: PlaySoundEvent<Ch>;
|
|
101
|
+
stopMusic: StopMusicEvent<Ch>;
|
|
102
|
+
soundEnded: SoundEndedEvent;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Play options for fire-and-forget sound effects.
|
|
106
|
+
*/
|
|
107
|
+
export interface PlayOptions<Ch extends string = string> {
|
|
108
|
+
/** Channel to play on (uses first defined channel if omitted) */
|
|
109
|
+
channel?: Ch;
|
|
110
|
+
/** Individual volume (0-1, default: 1) */
|
|
111
|
+
volume?: number;
|
|
112
|
+
/** Whether to loop (default: false) */
|
|
113
|
+
loop?: boolean;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Music playback options.
|
|
117
|
+
*/
|
|
118
|
+
export interface MusicOptions<Ch extends string = string> {
|
|
119
|
+
/** Channel to play music on (uses first defined channel if omitted) */
|
|
120
|
+
channel?: Ch;
|
|
121
|
+
/** Volume (0-1, default: 1) */
|
|
122
|
+
volume?: number;
|
|
123
|
+
/** Whether to loop (default: true) */
|
|
124
|
+
loop?: boolean;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Audio state resource providing fire-and-forget SFX and music control.
|
|
128
|
+
* Effective volume = individual * channel * master.
|
|
129
|
+
*/
|
|
130
|
+
export interface AudioState<Ch extends string = string> {
|
|
131
|
+
/** Play a fire-and-forget sound effect. Returns the Howler sound ID. */
|
|
132
|
+
play(sound: string, options?: PlayOptions<Ch>): number;
|
|
133
|
+
/** Stop a specific sound by its Howler sound ID. */
|
|
134
|
+
stop(soundId: number): void;
|
|
135
|
+
/** Play music on a channel. Stops any existing music on that channel first. */
|
|
136
|
+
playMusic(sound: string, options?: MusicOptions<Ch>): void;
|
|
137
|
+
/** Stop music on a channel. If omitted, stops all music. */
|
|
138
|
+
stopMusic(channel?: Ch): void;
|
|
139
|
+
/** Pause music on a channel. If omitted, pauses all music. */
|
|
140
|
+
pauseMusic(channel?: Ch): void;
|
|
141
|
+
/** Resume music on a channel. If omitted, resumes all music. */
|
|
142
|
+
resumeMusic(channel?: Ch): void;
|
|
143
|
+
/** Set volume for a channel (0-1). */
|
|
144
|
+
setChannelVolume(channel: Ch, volume: number): void;
|
|
145
|
+
/** Get current volume for a channel. */
|
|
146
|
+
getChannelVolume(channel: Ch): number;
|
|
147
|
+
/** Set master volume (0-1). */
|
|
148
|
+
setMasterVolume(volume: number): void;
|
|
149
|
+
/** Get current master volume. */
|
|
150
|
+
getMasterVolume(): number;
|
|
151
|
+
/** Mute all audio. */
|
|
152
|
+
mute(): void;
|
|
153
|
+
/** Unmute all audio. */
|
|
154
|
+
unmute(): void;
|
|
155
|
+
/** Toggle mute state. */
|
|
156
|
+
toggleMute(): void;
|
|
157
|
+
/** Check if audio is muted. */
|
|
158
|
+
isMuted(): boolean;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Resource types provided by the audio plugin.
|
|
162
|
+
*/
|
|
163
|
+
export interface AudioResourceTypes<Ch extends string = string> {
|
|
164
|
+
audioState: AudioState<Ch>;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Configuration options for the audio plugin.
|
|
168
|
+
*/
|
|
169
|
+
export interface AudioPluginOptions<Ch extends string, G extends string = 'audio'> extends BasePluginOptions<G> {
|
|
170
|
+
/** Channel definitions from defineAudioChannels */
|
|
171
|
+
channels: Readonly<Record<Ch, AudioChannelConfig>>;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Create an audioSource component for entity-attached audio.
|
|
175
|
+
*
|
|
176
|
+
* @param sound Asset key for the sound
|
|
177
|
+
* @param channel Channel to play on
|
|
178
|
+
* @param options Optional configuration
|
|
179
|
+
* @returns Component object suitable for spreading into spawn()
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* ecs.spawn({
|
|
184
|
+
* ...createAudioSource('explosion', 'sfx'),
|
|
185
|
+
* ...createTransform(100, 200),
|
|
186
|
+
* });
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export declare function createAudioSource<Ch extends string>(sound: string, channel: Ch, options?: {
|
|
190
|
+
volume?: number;
|
|
191
|
+
loop?: boolean;
|
|
192
|
+
autoRemove?: boolean;
|
|
193
|
+
}): Pick<AudioComponentTypes<Ch>, 'audioSource'>;
|
|
194
|
+
/**
|
|
195
|
+
* Create a loader function for use with the asset manager.
|
|
196
|
+
* Returns a factory function that loads a Howl when called.
|
|
197
|
+
*
|
|
198
|
+
* @param src URL(s) for the sound file
|
|
199
|
+
* @param options Optional Howl configuration
|
|
200
|
+
* @returns Factory function compatible with asset manager's loader parameter
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* const ecs = ECSpresso.create()
|
|
205
|
+
* .withAssets(a => a
|
|
206
|
+
* .add('explosion', loadSound('/sounds/explosion.mp3'))
|
|
207
|
+
* .add('bgm', loadSound(['/sounds/bgm.webm', '/sounds/bgm.mp3']))
|
|
208
|
+
* )
|
|
209
|
+
* .build();
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
export declare function loadSound(src: string | string[], options?: {
|
|
213
|
+
html5?: boolean;
|
|
214
|
+
preload?: boolean;
|
|
215
|
+
}): () => Promise<Howl>;
|
|
216
|
+
/**
|
|
217
|
+
* Create an audio plugin for ECSpresso.
|
|
218
|
+
*
|
|
219
|
+
* Provides:
|
|
220
|
+
* - `audioState` resource for fire-and-forget SFX and music
|
|
221
|
+
* - `audioSource` component for entity-attached sounds
|
|
222
|
+
* - Volume hierarchy: individual * channel * master
|
|
223
|
+
* - `playSound` / `stopMusic` event handlers
|
|
224
|
+
* - `soundEnded` event on completion
|
|
225
|
+
* - Automatic cleanup on entity removal (dispose callback)
|
|
226
|
+
*
|
|
227
|
+
* Sounds must be preloaded through the asset pipeline (`loadSound` helper).
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* const channels = defineAudioChannels({
|
|
232
|
+
* sfx: { volume: 1 },
|
|
233
|
+
* music: { volume: 0.7 },
|
|
234
|
+
* });
|
|
235
|
+
*
|
|
236
|
+
* const ecs = ECSpresso.create()
|
|
237
|
+
* .withAssets(a => a.add('explosion', loadSound('/sfx/boom.mp3')))
|
|
238
|
+
* .withPlugin(createAudioPlugin({ channels }))
|
|
239
|
+
* .build();
|
|
240
|
+
*
|
|
241
|
+
* await ecs.initialize();
|
|
242
|
+
* const audio = ecs.getResource('audioState');
|
|
243
|
+
* audio.play('explosion', { channel: 'sfx' });
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
export declare function createAudioPlugin<Ch extends string, G extends string = 'audio'>(options: AudioPluginOptions<Ch, G>): Plugin<WorldConfigFrom<AudioComponentTypes<Ch>, AudioEventTypes<Ch>, AudioResourceTypes<Ch>>, EmptyConfig, 'audio-sync', G, never, 'audio-sources'>;
|
|
247
|
+
/**
|
|
248
|
+
* Typed helpers for the audio plugin.
|
|
249
|
+
* Creates helpers that validate sound keys and channel names against the world type W.
|
|
250
|
+
* Call after .build() using typeof ecs.
|
|
251
|
+
*
|
|
252
|
+
* @template W - Concrete ECS world type (e.g. `typeof ecs`)
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* const ecs = ECSpresso.create()
|
|
257
|
+
* .withPlugin(createAudioPlugin({ channels }))
|
|
258
|
+
* .withAssets(a => a.add('boom', loadSound('/sfx/boom.mp3')))
|
|
259
|
+
* .build();
|
|
260
|
+
*
|
|
261
|
+
* const { createAudioSource } = createAudioHelpers<typeof ecs>();
|
|
262
|
+
* // Type-safe: 'boom' must be a registered asset, 'sfx' a valid channel
|
|
263
|
+
* createAudioSource('boom', 'sfx');
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export interface AudioHelpers<W extends AnyECSpresso> {
|
|
267
|
+
createAudioSource: (sound: keyof AssetsOfWorld<W> & string, channel: ChannelOfWorld<W>, options?: {
|
|
268
|
+
volume?: number;
|
|
269
|
+
loop?: boolean;
|
|
270
|
+
autoRemove?: boolean;
|
|
271
|
+
}) => Pick<AudioComponentTypes<ChannelOfWorld<W>>, 'audioSource'>;
|
|
272
|
+
}
|
|
273
|
+
export declare function createAudioHelpers<W extends AnyECSpresso>(_world?: W): AudioHelpers<W>;
|