ecspresso 0.11.0 → 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 +200 -148
- package/dist/asset-manager.d.ts +1 -1
- package/dist/asset-types.d.ts +2 -2
- package/dist/command-buffer.d.ts +34 -24
- package/dist/ecspresso-builder.d.ts +100 -72
- package/dist/ecspresso.d.ts +257 -122
- package/dist/entity-manager.d.ts +57 -47
- package/dist/index.d.ts +5 -4
- package/dist/plugin.d.ts +61 -0
- package/dist/{bundles → plugins}/audio.d.ts +27 -47
- package/dist/{bundles → plugins}/bounds.d.ts +17 -25
- package/dist/{bundles → plugins}/camera.d.ts +8 -9
- package/dist/{bundles → plugins}/collision.d.ts +22 -26
- package/dist/plugins/coroutine.d.ts +126 -0
- package/dist/{bundles → plugins}/diagnostics.d.ts +5 -4
- package/dist/{bundles → plugins}/input.d.ts +9 -15
- package/dist/plugins/particles.d.ts +225 -0
- package/dist/{bundles → plugins}/physics2D.d.ts +27 -23
- package/dist/{bundles → plugins}/renderers/renderer2D.d.ts +40 -39
- package/dist/{bundles → plugins}/spatial-index.d.ts +11 -10
- package/dist/plugins/sprite-animation.d.ts +150 -0
- package/dist/{bundles → plugins}/state-machine.d.ts +50 -104
- package/dist/plugins/timers.d.ts +151 -0
- package/dist/{bundles → plugins}/transform.d.ts +18 -19
- package/dist/{bundles → plugins}/tween.d.ts +36 -71
- package/dist/resource-manager.d.ts +32 -7
- package/dist/screen-manager.d.ts +17 -11
- package/dist/screen-types.d.ts +5 -2
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +17 -17
- 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 +66 -97
- package/dist/type-utils.d.ts +218 -27
- package/dist/types.d.ts +52 -24
- package/dist/utils/check-required-cycle.d.ts +1 -1
- package/dist/utils/narrowphase.d.ts +7 -7
- package/package.json +53 -45
- package/dist/bundle.d.ts +0 -173
- package/dist/bundles/timers.d.ts +0 -173
- package/dist/src/bundles/audio.js +0 -4
- package/dist/src/bundles/audio.js.map +0 -10
- package/dist/src/bundles/bounds.js +0 -4
- package/dist/src/bundles/bounds.js.map +0 -10
- package/dist/src/bundles/camera.js +0 -4
- package/dist/src/bundles/camera.js.map +0 -10
- package/dist/src/bundles/collision.js +0 -4
- package/dist/src/bundles/collision.js.map +0 -11
- package/dist/src/bundles/diagnostics.js +0 -5
- package/dist/src/bundles/diagnostics.js.map +0 -10
- package/dist/src/bundles/input.js +0 -4
- package/dist/src/bundles/input.js.map +0 -10
- package/dist/src/bundles/physics2D.js +0 -4
- package/dist/src/bundles/physics2D.js.map +0 -11
- package/dist/src/bundles/renderers/renderer2D.js +0 -4
- package/dist/src/bundles/renderers/renderer2D.js.map +0 -10
- package/dist/src/bundles/spatial-index.js +0 -4
- package/dist/src/bundles/spatial-index.js.map +0 -11
- package/dist/src/bundles/state-machine.js +0 -4
- package/dist/src/bundles/state-machine.js.map +0 -10
- package/dist/src/bundles/timers.js +0 -4
- package/dist/src/bundles/timers.js.map +0 -10
- package/dist/src/bundles/transform.js +0 -4
- package/dist/src/bundles/transform.js.map +0 -10
- package/dist/src/bundles/tween.js +0 -4
- package/dist/src/bundles/tween.js.map +0 -11
package/dist/bundle.d.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { SystemBuilderWithBundle } from './system-builder';
|
|
2
|
-
import type ECSpresso from './ecspresso';
|
|
3
|
-
import type { ResourceFactoryWithDeps } from './resource-manager';
|
|
4
|
-
import type { AssetDefinition } from './asset-types';
|
|
5
|
-
import type { ScreenDefinition } from './screen-types';
|
|
6
|
-
import type { BundlesAreCompatible } from './type-utils';
|
|
7
|
-
import type { QueryDefinition } from './types';
|
|
8
|
-
/**
|
|
9
|
-
* Bundle class that encapsulates a set of components, resources, events, and systems
|
|
10
|
-
* that can be merged into a ECSpresso instance
|
|
11
|
-
*/
|
|
12
|
-
export default class Bundle<ComponentTypes extends Record<string, any> = {}, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}, AssetTypes extends Record<string, unknown> = {}, ScreenStates extends Record<string, ScreenDefinition<any, any>> = {}, Labels extends string = never, Groups extends string = never, AssetGroupNames extends string = never, ReactiveQueryNames extends string = never> {
|
|
13
|
-
private _systems;
|
|
14
|
-
private _resources;
|
|
15
|
-
private _assets;
|
|
16
|
-
private _assetGroups;
|
|
17
|
-
private _screens;
|
|
18
|
-
private _disposeCallbacks;
|
|
19
|
-
private _requiredComponents;
|
|
20
|
-
private _id;
|
|
21
|
-
constructor(id?: string);
|
|
22
|
-
/**
|
|
23
|
-
* Get the unique ID of this bundle
|
|
24
|
-
*/
|
|
25
|
-
get id(): string;
|
|
26
|
-
/**
|
|
27
|
-
* Set the ID of this bundle
|
|
28
|
-
* @internal Used by combineBundles
|
|
29
|
-
*/
|
|
30
|
-
set id(value: string);
|
|
31
|
-
/**
|
|
32
|
-
* Add a system to this bundle, by label (creating a new builder) or by reusing an existing one
|
|
33
|
-
*/
|
|
34
|
-
addSystem<Q extends Record<string, QueryDefinition<ComponentTypes>>, BL extends string, BG extends string, L extends string, SG extends string>(builder: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, Q, BL, BG, L, SG, any, any>): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, Q, Labels, Groups, L, SG, AssetGroupNames, ReactiveQueryNames>;
|
|
35
|
-
addSystem<L extends string>(label: L): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, {}, Labels, Groups, L, never, AssetGroupNames, ReactiveQueryNames>;
|
|
36
|
-
/**
|
|
37
|
-
* Add a resource to this bundle
|
|
38
|
-
* @param label The resource key
|
|
39
|
-
* @param resource The resource value, a factory function, or a factory with dependencies
|
|
40
|
-
*/
|
|
41
|
-
addResource<K extends keyof ResourceTypes>(label: K, resource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>) => ResourceTypes[K] | Promise<ResourceTypes[K]>) | ResourceFactoryWithDeps<ResourceTypes[K], ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>, keyof ResourceTypes & string>): this;
|
|
42
|
-
/**
|
|
43
|
-
* Add an asset to this bundle
|
|
44
|
-
* @param key The asset key
|
|
45
|
-
* @param loader Function that loads and returns the asset
|
|
46
|
-
* @param options Optional asset configuration
|
|
47
|
-
*/
|
|
48
|
-
addAsset<K extends string, T>(key: K, loader: () => Promise<T>, options?: {
|
|
49
|
-
eager?: boolean;
|
|
50
|
-
group?: string;
|
|
51
|
-
}): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & Record<K, T>, ScreenStates, Labels, Groups, AssetGroupNames, ReactiveQueryNames>;
|
|
52
|
-
/**
|
|
53
|
-
* Add a group of assets to this bundle
|
|
54
|
-
* @param groupName The group name
|
|
55
|
-
* @param assets Object mapping asset keys to loader functions
|
|
56
|
-
*/
|
|
57
|
-
addAssetGroup<GN extends string, T extends Record<string, () => Promise<unknown>>>(groupName: GN, assets: T): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & {
|
|
58
|
-
[K in keyof T]: Awaited<ReturnType<T[K]>>;
|
|
59
|
-
}, ScreenStates, Labels, Groups, AssetGroupNames | GN, ReactiveQueryNames>;
|
|
60
|
-
/**
|
|
61
|
-
* Add a screen to this bundle
|
|
62
|
-
* @param name The screen name
|
|
63
|
-
* @param definition The screen definition
|
|
64
|
-
*/
|
|
65
|
-
addScreen<K extends string, Config extends Record<string, unknown>, State extends Record<string, unknown>>(name: K, definition: ScreenDefinition<Config, State, ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, Record<string, ScreenDefinition>>>): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates & Record<K, ScreenDefinition<Config, State>>, Labels, Groups, AssetGroupNames, ReactiveQueryNames>;
|
|
66
|
-
/**
|
|
67
|
-
* Get all asset definitions in this bundle
|
|
68
|
-
*/
|
|
69
|
-
getAssets(): Map<string, AssetDefinition<unknown>>;
|
|
70
|
-
/**
|
|
71
|
-
* Get all screen definitions in this bundle
|
|
72
|
-
*/
|
|
73
|
-
getScreens(): Map<string, ScreenDefinition<any, any>>;
|
|
74
|
-
/**
|
|
75
|
-
* Register a dispose callback for a component type in this bundle.
|
|
76
|
-
* Called when a component is removed (explicit removal, entity destruction, or replacement).
|
|
77
|
-
* @param componentName The component type to register disposal for
|
|
78
|
-
* @param callback Function receiving the component value being disposed
|
|
79
|
-
* @returns This bundle for method chaining
|
|
80
|
-
*/
|
|
81
|
-
registerDispose<K extends keyof ComponentTypes>(componentName: K, callback: (value: ComponentTypes[K]) => void): this;
|
|
82
|
-
/**
|
|
83
|
-
* Get all registered dispose callbacks in this bundle
|
|
84
|
-
*/
|
|
85
|
-
getDisposeCallbacks(): Map<string, (value: unknown) => void>;
|
|
86
|
-
/**
|
|
87
|
-
* Register a required component relationship.
|
|
88
|
-
* When an entity gains `trigger`, the `required` component is auto-added
|
|
89
|
-
* (using `factory` for the default value) if not already present.
|
|
90
|
-
* @param trigger The component whose presence triggers auto-addition
|
|
91
|
-
* @param required The component to auto-add
|
|
92
|
-
* @param factory Function that creates the default value for the required component
|
|
93
|
-
* @returns This bundle for method chaining
|
|
94
|
-
*/
|
|
95
|
-
registerRequired<Trigger extends keyof ComponentTypes, Required extends keyof ComponentTypes>(trigger: Trigger, required: Required, factory: (triggerValue: ComponentTypes[Trigger]) => ComponentTypes[Required]): this;
|
|
96
|
-
/**
|
|
97
|
-
* Declare reactive query names that this bundle will register at runtime.
|
|
98
|
-
* This is a pure type-level operation with no runtime cost.
|
|
99
|
-
*/
|
|
100
|
-
withReactiveQueryNames<N extends string>(): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, Labels, Groups, AssetGroupNames, ReactiveQueryNames | N>;
|
|
101
|
-
/**
|
|
102
|
-
* Get all registered required component mappings in this bundle
|
|
103
|
-
*/
|
|
104
|
-
getRequiredComponents(): Map<string, Array<{
|
|
105
|
-
component: string;
|
|
106
|
-
factory: (triggerValue: any) => unknown;
|
|
107
|
-
}>>;
|
|
108
|
-
/**
|
|
109
|
-
* Check for circular dependencies in the required components graph
|
|
110
|
-
* @throws Error if adding the new edge would create a cycle
|
|
111
|
-
*/
|
|
112
|
-
private _checkRequiredCycle;
|
|
113
|
-
/**
|
|
114
|
-
* Internal method to set a dispose callback
|
|
115
|
-
* @internal Used by mergeBundles
|
|
116
|
-
*/
|
|
117
|
-
_setDisposeCallback(name: string, callback: (value: unknown) => void): void;
|
|
118
|
-
/**
|
|
119
|
-
* Internal method to set a resource
|
|
120
|
-
* @internal Used by mergeBundles
|
|
121
|
-
*/
|
|
122
|
-
_setResource(key: string, value: unknown): void;
|
|
123
|
-
/**
|
|
124
|
-
* Internal method to set an asset definition
|
|
125
|
-
* @internal Used by mergeBundles
|
|
126
|
-
*/
|
|
127
|
-
_setAsset(key: string, definition: AssetDefinition<unknown>): void;
|
|
128
|
-
/**
|
|
129
|
-
* Internal method to set a screen definition
|
|
130
|
-
* @internal Used by mergeBundles
|
|
131
|
-
*/
|
|
132
|
-
_setScreen(name: string, definition: ScreenDefinition<any, any>): void;
|
|
133
|
-
/**
|
|
134
|
-
* Internal method to add a required component entry
|
|
135
|
-
* @internal Used by mergeBundles
|
|
136
|
-
*/
|
|
137
|
-
_addRequired(trigger: string, component: string, factory: (triggerValue: any) => unknown): void;
|
|
138
|
-
/**
|
|
139
|
-
* Get all systems defined in this bundle
|
|
140
|
-
* Returns built System objects instead of SystemBuilders
|
|
141
|
-
*/
|
|
142
|
-
getSystems(): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, any, any, any, any, any, any, any>[];
|
|
143
|
-
/**
|
|
144
|
-
* Register all systems in this bundle with an ECSpresso instance
|
|
145
|
-
* @internal Used by ECSpresso when adding a bundle
|
|
146
|
-
*/
|
|
147
|
-
registerSystemsWithEcspresso(ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void;
|
|
148
|
-
/**
|
|
149
|
-
* Get all resources defined in this bundle
|
|
150
|
-
*/
|
|
151
|
-
getResources(): Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]>;
|
|
152
|
-
/**
|
|
153
|
-
* Get a specific resource by key
|
|
154
|
-
* @param key The resource key
|
|
155
|
-
* @returns The resource value or undefined if not found
|
|
156
|
-
*/
|
|
157
|
-
getResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K];
|
|
158
|
-
/**
|
|
159
|
-
* Get all system builders in this bundle
|
|
160
|
-
*/
|
|
161
|
-
getSystemBuilders(): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, any, any, any, any, any, any, any>[];
|
|
162
|
-
/**
|
|
163
|
-
* Check if this bundle has a specific resource
|
|
164
|
-
* @param key The resource key to check
|
|
165
|
-
* @returns True if the resource exists
|
|
166
|
-
*/
|
|
167
|
-
hasResource<K extends keyof ResourceTypes>(key: K): boolean;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Function that merges multiple bundles into a single bundle
|
|
171
|
-
*/
|
|
172
|
-
export declare function mergeBundles<C1 extends Record<string, any>, E1 extends Record<string, any>, R1 extends Record<string, any>, A1 extends Record<string, unknown>, S1 extends Record<string, ScreenDefinition<any, any>>, L1 extends string, G1 extends string, AG1 extends string, RQ1 extends string, C2 extends Record<string, any>, E2 extends Record<string, any>, R2 extends Record<string, any>, A2 extends Record<string, unknown>, S2 extends Record<string, ScreenDefinition<any, any>>, L2 extends string, G2 extends string, AG2 extends string, RQ2 extends string>(id: string, bundle1: Bundle<C1, E1, R1, A1, S1, L1, G1, AG1, RQ1>, bundle2: BundlesAreCompatible<C1, C2, E1, E2, R1, R2, A1, A2, S1, S2> extends true ? Bundle<C2, E2, R2, A2, S2, L2, G2, AG2, RQ2> : never): Bundle<C1 & C2, E1 & E2, R1 & R2, A1 & A2, S1 & S2, L1 | L2, G1 | G2, AG1 | AG2, RQ1 | RQ2>;
|
|
173
|
-
export declare function mergeBundles<ComponentTypes extends Record<string, any>, EventTypes extends Record<string, any>, ResourceTypes extends Record<string, any>, AssetTypes extends Record<string, unknown>, ScreenStates extends Record<string, ScreenDefinition<any, any>>, Labels extends string, Groups extends string, AssetGroupNamesParam extends string, ReactiveQueryNamesParam extends string>(id: string, ...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, Labels, Groups, AssetGroupNamesParam, ReactiveQueryNamesParam>>): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, Labels, Groups, AssetGroupNamesParam, ReactiveQueryNamesParam>;
|
package/dist/bundles/timers.d.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Timer Bundle 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 { Bundle } from 'ecspresso';
|
|
8
|
-
import type { SystemPhase } from 'ecspresso';
|
|
9
|
-
/**
|
|
10
|
-
* Data structure published when a timer completes.
|
|
11
|
-
* Use this type when defining timer completion events in your EventTypes interface.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```typescript
|
|
15
|
-
* interface Events {
|
|
16
|
-
* hideMessage: TimerEventData;
|
|
17
|
-
* spawnWave: TimerEventData;
|
|
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
|
-
* Extracts event names from EventTypes that have TimerEventData as their payload.
|
|
31
|
-
* This ensures only compatible events can be used with timer.onComplete.
|
|
32
|
-
*/
|
|
33
|
-
export type TimerEventName<EventTypes extends Record<string, any>> = {
|
|
34
|
-
[K in keyof EventTypes]: EventTypes[K] extends TimerEventData ? K : never;
|
|
35
|
-
}[keyof EventTypes];
|
|
36
|
-
/**
|
|
37
|
-
* Timer component data structure.
|
|
38
|
-
* Use `justFinished` to detect timer completion in your systems.
|
|
39
|
-
*
|
|
40
|
-
* @template EventTypes The event types from your ECS
|
|
41
|
-
*/
|
|
42
|
-
export interface Timer<EventTypes extends Record<string, any>> {
|
|
43
|
-
/** Time accumulated so far (seconds) */
|
|
44
|
-
elapsed: number;
|
|
45
|
-
/** Target duration (seconds) */
|
|
46
|
-
duration: number;
|
|
47
|
-
/** Whether timer repeats after completion */
|
|
48
|
-
repeat: boolean;
|
|
49
|
-
/** Whether timer is currently running */
|
|
50
|
-
active: boolean;
|
|
51
|
-
/** True for one frame after timer completes */
|
|
52
|
-
justFinished: boolean;
|
|
53
|
-
/** Optional event name to publish when timer completes. Must be an event with TimerEventData payload. */
|
|
54
|
-
onComplete?: TimerEventName<EventTypes>;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Component types provided by the timer bundle.
|
|
58
|
-
* Included automatically via `.withBundle(createTimerBundle<Events>())`.
|
|
59
|
-
* The EventTypes generic constrains which events can be used with `onComplete`.
|
|
60
|
-
*
|
|
61
|
-
* @template EventTypes The event types from your ECS
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* ```typescript
|
|
65
|
-
* const ecs = ECSpresso.create()
|
|
66
|
-
* .withBundle(createTimerBundle<{ respawn: TimerEventData }>())
|
|
67
|
-
* .withComponentTypes<{ velocity: { x: number; y: number }; player: true }>()
|
|
68
|
-
* .build();
|
|
69
|
-
* ```
|
|
70
|
-
*/
|
|
71
|
-
export interface TimerComponentTypes<EventTypes extends Record<string, any>> {
|
|
72
|
-
timer: Timer<EventTypes>;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Configuration options for the timer bundle.
|
|
76
|
-
*/
|
|
77
|
-
export interface TimerBundleOptions<G extends string = 'timers'> {
|
|
78
|
-
/** System group name (default: 'timers') */
|
|
79
|
-
systemGroup?: G;
|
|
80
|
-
/** Priority for timer update system (default: 0) */
|
|
81
|
-
priority?: number;
|
|
82
|
-
/** Execution phase (default: 'preUpdate') */
|
|
83
|
-
phase?: SystemPhase;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Options for timer creation
|
|
87
|
-
*
|
|
88
|
-
* @template EventTypes The event types from your ECS
|
|
89
|
-
*/
|
|
90
|
-
export interface TimerOptions<EventTypes extends Record<string, any>> {
|
|
91
|
-
/** Event name to publish when timer completes. Must be an event with TimerEventData payload. */
|
|
92
|
-
onComplete?: TimerEventName<EventTypes>;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Create a one-shot timer that fires once after the specified duration.
|
|
96
|
-
*
|
|
97
|
-
* @template EventTypes The event types from your ECS (must be explicitly provided)
|
|
98
|
-
* @param duration Duration in seconds until the timer completes
|
|
99
|
-
* @param options Optional configuration including event name
|
|
100
|
-
* @returns Component object suitable for spreading into spawn()
|
|
101
|
-
*
|
|
102
|
-
* @example
|
|
103
|
-
* ```typescript
|
|
104
|
-
* // Timer without event
|
|
105
|
-
* ecs.spawn({
|
|
106
|
-
* ...createTimer<GameEvents>(2),
|
|
107
|
-
* explosion: true,
|
|
108
|
-
* });
|
|
109
|
-
*
|
|
110
|
-
* // Timer that publishes an event on completion
|
|
111
|
-
* ecs.spawn({
|
|
112
|
-
* ...createTimer<GameEvents>(1.5, { onComplete: 'hideMessage' }),
|
|
113
|
-
* });
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
|
-
export declare function createTimer<EventTypes extends Record<string, any>>(duration: number, options?: TimerOptions<EventTypes>): Pick<TimerComponentTypes<EventTypes>, 'timer'>;
|
|
117
|
-
/**
|
|
118
|
-
* Create a repeating timer that fires every `duration` seconds.
|
|
119
|
-
*
|
|
120
|
-
* @template EventTypes The event types from your ECS (must be explicitly provided)
|
|
121
|
-
* @param duration Duration in seconds between each timer completion
|
|
122
|
-
* @param options Optional configuration including event name
|
|
123
|
-
* @returns Component object suitable for spreading into spawn()
|
|
124
|
-
*
|
|
125
|
-
* @example
|
|
126
|
-
* ```typescript
|
|
127
|
-
* // Timer without event
|
|
128
|
-
* ecs.spawn({
|
|
129
|
-
* ...createRepeatingTimer<GameEvents>(5),
|
|
130
|
-
* spawner: true,
|
|
131
|
-
* });
|
|
132
|
-
*
|
|
133
|
-
* // Repeating timer that publishes an event each cycle
|
|
134
|
-
* ecs.spawn({
|
|
135
|
-
* ...createRepeatingTimer<GameEvents>(3, { onComplete: 'spawnWave' }),
|
|
136
|
-
* });
|
|
137
|
-
* ```
|
|
138
|
-
*/
|
|
139
|
-
export declare function createRepeatingTimer<EventTypes extends Record<string, any>>(duration: number, options?: TimerOptions<EventTypes>): Pick<TimerComponentTypes<EventTypes>, 'timer'>;
|
|
140
|
-
/**
|
|
141
|
-
* Create a timer bundle for ECSpresso.
|
|
142
|
-
*
|
|
143
|
-
* This bundle provides:
|
|
144
|
-
* - Timer update system that processes all timer components each frame
|
|
145
|
-
* - `justFinished` flag pattern for one-frame completion detection
|
|
146
|
-
* - Automatic cleanup when entities are removed
|
|
147
|
-
*
|
|
148
|
-
* @example
|
|
149
|
-
* ```typescript
|
|
150
|
-
* const ecs = ECSpresso
|
|
151
|
-
* .create<Components, Events, Resources>()
|
|
152
|
-
* .withBundle(createTimerBundle())
|
|
153
|
-
* .build();
|
|
154
|
-
*
|
|
155
|
-
* // Spawn entity with timer
|
|
156
|
-
* ecs.spawn({
|
|
157
|
-
* ...createRepeatingTimer(5),
|
|
158
|
-
* spawner: true,
|
|
159
|
-
* });
|
|
160
|
-
*
|
|
161
|
-
* // React to timer completion in a system
|
|
162
|
-
* ecs.addSystem('spawn-on-timer')
|
|
163
|
-
* .addQuery('spawners', { with: ['timer', 'spawner'] })
|
|
164
|
-
* .setProcess((queries, _dt, ecs) => {
|
|
165
|
-
* for (const { components } of queries.spawners) {
|
|
166
|
-
* if (components.timer.justFinished) {
|
|
167
|
-
* ecs.spawn({ enemy: true });
|
|
168
|
-
* }
|
|
169
|
-
* }
|
|
170
|
-
* });
|
|
171
|
-
* ```
|
|
172
|
-
*/
|
|
173
|
-
export declare function createTimerBundle<EventTypes extends Record<string, any>, G extends string = 'timers'>(options?: TimerBundleOptions<G>): Bundle<TimerComponentTypes<EventTypes>, EventTypes, {}, {}, {}, 'timer-update', G>;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
var{defineProperty:k,getOwnPropertyNames:V,getOwnPropertyDescriptor:I}=Object,S=Object.prototype.hasOwnProperty;var B=new WeakMap,r=(F)=>{var L=B.get(F),U;if(L)return L;if(L=k({},"__esModule",{value:!0}),F&&typeof F==="object"||typeof F==="function")V(F).map((Z)=>!S.call(L,Z)&&k(L,Z,{get:()=>F[Z],enumerable:!(U=I(F,Z))||U.enumerable}));return B.set(F,L),L};var h=(F,L)=>{for(var U in L)k(F,U,{get:L[U],enumerable:!0,configurable:!0,set:(Z)=>L[U]=()=>Z})};var i=(F,L)=>()=>(F&&(L=F(F=0)),L);var w=((F)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(F,{get:(L,U)=>(typeof require<"u"?require:L)[U]}):F)(function(F){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+F+'" is not supported')});import{Bundle as f}from"ecspresso";function u(F){return Object.freeze(F)}function C(F,L,U){return{audioSource:{sound:F,channel:L,volume:U?.volume??1,loop:U?.loop??!1,autoRemove:U?.autoRemove??!1,playing:!1,_soundId:-1}}}function v(F,L){return()=>import("howler").then(({Howl:U})=>new Promise((Z,N)=>{let _,P=!1;if(_=new U({src:Array.isArray(F)?F:[F],html5:L?.html5??!1,preload:L?.preload??!0,onload:()=>{P=!0,Z(_)},onloaderror:(X,K)=>N(K instanceof Error?K:Error(String(K)))}),!P&&_.state?.()==="loaded")Z(_)}))}function y(F){let{channels:L,systemGroup:U="audio",priority:Z=0,phase:N="update"}=F,_=new Map,P=new Map,X=new Map,K=1,G=!1,R=[];for(let[q,z]of Object.entries(L))_.set(q,z.volume),R.push(q);let x=R[0];function H(q,z){if(G)return 0;let J=_.get(z)??1;return q*J*K}function b(q){for(let J of P.values()){if(J.channel!==q)continue;J.howl.volume(H(J.individualVolume,q),J.soundId)}let z=X.get(q);if(z)z.howl.volume(H(z.individualVolume,q),z.soundId)}function T(){for(let q of R)b(q)}function O(q){let z=P.get(q);if(!z)return;z.howl.stop(q),P.delete(q)}let W=null,D=null,g={play(q,z){if(!D)return-1;let J=z?.channel??x,M=z?.volume??1,$=z?.loop??!1,Q=D(q);Q.volume(H(M,J)),Q.loop($);let Y=Q.play(),E={howl:Q,soundId:Y,channel:J,individualVolume:M,assetKey:q,entityId:-1};return P.set(Y,E),Q.once("end",()=>{P.delete(Y),W?.publish("soundEnded",{entityId:-1,soundId:Y,sound:q})},Y),Y},stop(q){O(q)},playMusic(q,z){if(!D)return;let J=z?.channel??x,M=z?.volume??1,$=z?.loop??!0,Q=X.get(J);if(Q)Q.howl.stop(Q.soundId),P.delete(Q.soundId);let Y=D(q);Y.volume(H(M,J)),Y.loop($);let E=Y.play(),A={howl:Y,soundId:E,channel:J,individualVolume:M,assetKey:q};X.set(J,A),P.set(E,{...A,entityId:-1}),Y.once("end",()=>{if(P.delete(E),X.get(J)?.soundId===E)X.delete(J)},E)},stopMusic(q){if(q!==void 0){let z=X.get(q);if(z)z.howl.stop(z.soundId),P.delete(z.soundId),X.delete(q)}else for(let[z,J]of X)J.howl.stop(J.soundId),P.delete(J.soundId),X.delete(z)},pauseMusic(q){if(q!==void 0){let z=X.get(q);if(z)z.howl.pause(z.soundId)}else for(let z of X.values())z.howl.pause(z.soundId)},resumeMusic(q){if(q!==void 0){let z=X.get(q);if(z)z.howl.play(z.soundId)}else for(let z of X.values())z.howl.play(z.soundId)},setChannelVolume(q,z){_.set(q,z),b(q)},getChannelVolume(q){return _.get(q)??1},setMasterVolume(q){K=q,T()},getMasterVolume(){return K},mute(){G=!0,T()},unmute(){G=!1,T()},toggleMute(){G=!G,T()},isMuted(){return G}},j=new f("audio");return j.addResource("audioState",g),j.registerDispose("audioSource",(q)=>{if(q._soundId!==-1)O(q._soundId)}),j.addSystem("audio-sync").setPriority(Z).inPhase(N).inGroup(U).setOnInitialize((q)=>{W=q.eventBus;let z=q.tryGetResource("$assets");if(z)D=(J)=>z.get(J);q.addReactiveQuery("audio-sources",{with:["audioSource"],onEnter:(J)=>{let M=J.components.audioSource;if(!D)return;if(M._soundId!==-1)return;let $=D(M.sound);$.volume(H(M.volume,M.channel)),$.loop(M.loop);let Q=$.play();M._soundId=Q,M.playing=!0;let Y={howl:$,soundId:Q,channel:M.channel,individualVolume:M.volume,assetKey:M.sound,entityId:J.id};P.set(Q,Y),$.once("end",()=>{if(P.delete(Q),M.playing=!1,W?.publish("soundEnded",{entityId:J.id,soundId:Q,sound:M.sound}),M.autoRemove)q.commands.removeEntity(J.id)},Q)},onExit:(J)=>{}})}).setEventHandlers({playSound:{handler(q,z){z.getResource("audioState").play(q.sound,{channel:q.channel,volume:q.volume,loop:q.loop})}},stopMusic:{handler(q,z){z.getResource("audioState").stopMusic(q.channel)}}}).setOnDetach(()=>{for(let q of P.values())q.howl.stop(q.soundId);P.clear(),X.clear(),W=null,D=null}).and(),j.withReactiveQueryNames()}function l(F){return{bundle:y(F),createAudioSource:C,loadSound:v}}export{v as loadSound,u as defineAudioChannels,C as createAudioSource,l as createAudioKit,y as createAudioBundle};
|
|
2
|
-
|
|
3
|
-
//# debugId=08A2E01637CA322364756E2164756E21
|
|
4
|
-
//# sourceMappingURL=audio.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/bundles/audio.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"/**\n * Audio Bundle for ECSpresso\n *\n * Web Audio API integration via Howler.js for sound effects and music playback.\n * User-defined channels with type-safe volume control, hybrid resource + component API,\n * and asset manager integration.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase, AssetsOfWorld } from 'ecspresso';\nimport type { Howl } from 'howler';\n\n// ==================== Channel Definition ====================\n\n/**\n * Configuration for a single audio channel.\n */\nexport interface AudioChannelConfig {\n\treadonly volume: number;\n}\n\n/**\n * Define audio channels with type-safe names and initial volumes.\n * Mirrors `defineCollisionLayers` pattern.\n *\n * @param channels Object mapping channel names to their configuration\n * @returns Frozen channel configuration with inferred channel name union\n *\n * @example\n * ```typescript\n * const channels = defineAudioChannels({\n * sfx: { volume: 1 },\n * music: { volume: 0.7 },\n * ui: { volume: 0.8 },\n * });\n * type Ch = ChannelsOf<typeof channels>; // 'sfx' | 'music' | 'ui'\n * ```\n */\nexport function defineAudioChannels<const T extends Record<string, AudioChannelConfig>>(\n\tchannels: T\n): Readonly<T> {\n\treturn Object.freeze(channels);\n}\n\n/**\n * Extract channel name union from a `defineAudioChannels` result.\n */\nexport type ChannelsOf<T> = T extends Record<infer K extends string, AudioChannelConfig> ? K : never;\n\n// ==================== Component Types ====================\n\n/**\n * Audio source component attached to entities for positional/entity-bound audio.\n */\nexport interface AudioSource<Ch extends string = string> {\n\t/** Asset key for the sound */\n\treadonly sound: string;\n\t/** Channel this sound plays on */\n\treadonly channel: Ch;\n\t/** Individual volume (0-1) */\n\tvolume: number;\n\t/** Whether sound loops */\n\tloop: boolean;\n\t/** Remove entity when sound ends (like timer autoRemove) */\n\tautoRemove: boolean;\n\t/** Whether sound is currently playing (system-managed) */\n\tplaying: boolean;\n\t/** Howler sound ID (system-managed, -1 = not started) */\n\t_soundId: number;\n}\n\n/**\n * Component types provided by the audio bundle.\n */\nexport interface AudioComponentTypes<Ch extends string = string> {\n\taudioSource: AudioSource<Ch>;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event to trigger fire-and-forget sound playback from any system.\n */\nexport interface PlaySoundEvent<Ch extends string = string> {\n\t/** Asset key for the sound */\n\tsound: string;\n\t/** Channel to play on */\n\tchannel?: Ch;\n\t/** Individual volume (0-1) */\n\tvolume?: number;\n\t/** Whether sound loops */\n\tloop?: boolean;\n}\n\n/**\n * Event to stop music on a channel.\n */\nexport interface StopMusicEvent<Ch extends string = string> {\n\t/** Channel to stop music on. If omitted, stops all music. */\n\tchannel?: Ch;\n}\n\n/**\n * Event published when a sound finishes playing.\n */\nexport interface SoundEndedEvent {\n\t/** Entity ID if sound was entity-attached, -1 for fire-and-forget */\n\tentityId: number;\n\t/** Howler sound ID */\n\tsoundId: number;\n\t/** Asset key of the sound */\n\tsound: string;\n}\n\n/**\n * Event types provided by the audio bundle.\n */\nexport interface AudioEventTypes<Ch extends string = string> {\n\tplaySound: PlaySoundEvent<Ch>;\n\tstopMusic: StopMusicEvent<Ch>;\n\tsoundEnded: SoundEndedEvent;\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Play options for fire-and-forget sound effects.\n */\nexport interface PlayOptions<Ch extends string = string> {\n\t/** Channel to play on (uses first defined channel if omitted) */\n\tchannel?: Ch;\n\t/** Individual volume (0-1, default: 1) */\n\tvolume?: number;\n\t/** Whether to loop (default: false) */\n\tloop?: boolean;\n}\n\n/**\n * Music playback options.\n */\nexport interface MusicOptions<Ch extends string = string> {\n\t/** Channel to play music on (uses first defined channel if omitted) */\n\tchannel?: Ch;\n\t/** Volume (0-1, default: 1) */\n\tvolume?: number;\n\t/** Whether to loop (default: true) */\n\tloop?: boolean;\n}\n\n/**\n * Audio state resource providing fire-and-forget SFX and music control.\n * Effective volume = individual * channel * master.\n */\nexport interface AudioState<Ch extends string = string> {\n\t/** Play a fire-and-forget sound effect. Returns the Howler sound ID. */\n\tplay(sound: string, options?: PlayOptions<Ch>): number;\n\t/** Stop a specific sound by its Howler sound ID. */\n\tstop(soundId: number): void;\n\n\t/** Play music on a channel. Stops any existing music on that channel first. */\n\tplayMusic(sound: string, options?: MusicOptions<Ch>): void;\n\t/** Stop music on a channel. If omitted, stops all music. */\n\tstopMusic(channel?: Ch): void;\n\t/** Pause music on a channel. If omitted, pauses all music. */\n\tpauseMusic(channel?: Ch): void;\n\t/** Resume music on a channel. If omitted, resumes all music. */\n\tresumeMusic(channel?: Ch): void;\n\n\t/** Set volume for a channel (0-1). */\n\tsetChannelVolume(channel: Ch, volume: number): void;\n\t/** Get current volume for a channel. */\n\tgetChannelVolume(channel: Ch): number;\n\t/** Set master volume (0-1). */\n\tsetMasterVolume(volume: number): void;\n\t/** Get current master volume. */\n\tgetMasterVolume(): number;\n\t/** Mute all audio. */\n\tmute(): void;\n\t/** Unmute all audio. */\n\tunmute(): void;\n\t/** Toggle mute state. */\n\ttoggleMute(): void;\n\t/** Check if audio is muted. */\n\tisMuted(): boolean;\n}\n\n/**\n * Resource types provided by the audio bundle.\n */\nexport interface AudioResourceTypes<Ch extends string = string> {\n\taudioState: AudioState<Ch>;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the audio bundle.\n */\nexport interface AudioBundleOptions<Ch extends string, G extends string = 'audio'> {\n\t/** Channel definitions from defineAudioChannels */\n\tchannels: Readonly<Record<Ch, AudioChannelConfig>>;\n\t/** System group name (default: 'audio') */\n\tsystemGroup?: G;\n\t/** Priority for audio sync system (default: 0) */\n\tpriority?: number;\n\t/** Execution phase (default: 'update') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create an audioSource component for entity-attached audio.\n *\n * @param sound Asset key for the sound\n * @param channel Channel to play on\n * @param options Optional configuration\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createAudioSource('explosion', 'sfx'),\n * ...createTransform(100, 200),\n * });\n * ```\n */\nexport function createAudioSource<Ch extends string>(\n\tsound: string,\n\tchannel: Ch,\n\toptions?: { volume?: number; loop?: boolean; autoRemove?: boolean }\n): Pick<AudioComponentTypes<Ch>, 'audioSource'> {\n\treturn {\n\t\taudioSource: {\n\t\t\tsound,\n\t\t\tchannel,\n\t\t\tvolume: options?.volume ?? 1,\n\t\t\tloop: options?.loop ?? false,\n\t\t\tautoRemove: options?.autoRemove ?? false,\n\t\t\tplaying: false,\n\t\t\t_soundId: -1,\n\t\t},\n\t};\n}\n\n/**\n * Create a loader function for use with the asset manager.\n * Returns a factory function that loads a Howl when called.\n *\n * @param src URL(s) for the sound file\n * @param options Optional Howl configuration\n * @returns Factory function compatible with asset manager's loader parameter\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withAssets(a => a\n * .add('explosion', loadSound('/sounds/explosion.mp3'))\n * .add('bgm', loadSound(['/sounds/bgm.webm', '/sounds/bgm.mp3']))\n * )\n * .build();\n * ```\n */\nexport function loadSound(\n\tsrc: string | string[],\n\toptions?: { html5?: boolean; preload?: boolean }\n): () => Promise<Howl> {\n\treturn () => import('howler').then(({ Howl: HowlClass }) =>\n\t\tnew Promise<Howl>((resolve, reject) => {\n\t\t\tlet howl: Howl;\n\t\t\tlet resolved = false;\n\t\t\thowl = new HowlClass({\n\t\t\t\tsrc: Array.isArray(src) ? src : [src],\n\t\t\t\thtml5: options?.html5 ?? false,\n\t\t\t\tpreload: options?.preload ?? true,\n\t\t\t\tonload: () => {\n\t\t\t\t\tresolved = true;\n\t\t\t\t\tresolve(howl);\n\t\t\t\t},\n\t\t\t\tonloaderror: (_id: number, err: unknown) => reject(\n\t\t\t\t\terr instanceof Error ? err : new Error(String(err))\n\t\t\t\t),\n\t\t\t});\n\t\t\t// If onload fired synchronously during construction (e.g. cached),\n\t\t\t// howl is now assigned and the promise is already resolved.\n\t\t\tif (!resolved && (howl as unknown as { state(): string }).state?.() === 'loaded') {\n\t\t\t\tresolve(howl);\n\t\t\t}\n\t\t})\n\t);\n}\n\n// ==================== Internal Types ====================\n\ninterface ActiveSound<Ch extends string> {\n\thowl: Howl;\n\tsoundId: number;\n\tchannel: Ch;\n\tindividualVolume: number;\n\tassetKey: string;\n\tentityId: number;\n}\n\ninterface MusicEntry<Ch extends string> {\n\thowl: Howl;\n\tsoundId: number;\n\tchannel: Ch;\n\tindividualVolume: number;\n\tassetKey: string;\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create an audio bundle for ECSpresso.\n *\n * Provides:\n * - `audioState` resource for fire-and-forget SFX and music\n * - `audioSource` component for entity-attached sounds\n * - Volume hierarchy: individual * channel * master\n * - `playSound` / `stopMusic` event handlers\n * - `soundEnded` event on completion\n * - Automatic cleanup on entity removal (dispose callback)\n *\n * Sounds must be preloaded through the asset pipeline (`loadSound` helper).\n *\n * @example\n * ```typescript\n * const channels = defineAudioChannels({\n * sfx: { volume: 1 },\n * music: { volume: 0.7 },\n * });\n *\n * const ecs = ECSpresso.create()\n * .withAssets(a => a.add('explosion', loadSound('/sfx/boom.mp3')))\n * .withBundle(createAudioBundle({ channels }))\n * .build();\n *\n * await ecs.initialize();\n * const audio = ecs.getResource('audioState');\n * audio.play('explosion', { channel: 'sfx' });\n * ```\n */\nexport function createAudioBundle<Ch extends string, G extends string = 'audio'>(\n\toptions: AudioBundleOptions<Ch, G>\n): Bundle<AudioComponentTypes<Ch>, AudioEventTypes<Ch>, AudioResourceTypes<Ch>, {}, {}, 'audio-sync', G, never, 'audio-sources'> {\n\tconst {\n\t\tchannels: channelDefs,\n\t\tsystemGroup = 'audio',\n\t\tpriority = 0,\n\t\tphase = 'update',\n\t} = options;\n\n\t// Closure state\n\tconst channelVolumes = new Map<Ch, number>();\n\tconst activeSounds = new Map<number, ActiveSound<Ch>>();\n\tconst musicByChannel = new Map<Ch, MusicEntry<Ch>>();\n\tlet masterVolume = 1;\n\tlet muted = false;\n\n\t// Initialize channel volumes from definitions\n\tconst channelNames: Ch[] = [];\n\tfor (const [name, config] of Object.entries(channelDefs) as Array<[Ch, AudioChannelConfig]>) {\n\t\tchannelVolumes.set(name, config.volume);\n\t\tchannelNames.push(name);\n\t}\n\n\tconst defaultChannel = channelNames[0] as Ch;\n\n\t// Volume computation\n\tfunction effectiveVolume(individualVol: number, channel: Ch): number {\n\t\tif (muted) return 0;\n\t\tconst chanVol = channelVolumes.get(channel) ?? 1;\n\t\treturn individualVol * chanVol * masterVolume;\n\t}\n\n\t// Propagate volume changes to all active sounds on a channel\n\tfunction propagateChannelVolume(channel: Ch): void {\n\t\tfor (const sound of activeSounds.values()) {\n\t\t\tif (sound.channel !== channel) continue;\n\t\t\tsound.howl.volume(effectiveVolume(sound.individualVolume, channel), sound.soundId);\n\t\t}\n\t\tconst music = musicByChannel.get(channel);\n\t\tif (music) {\n\t\t\tmusic.howl.volume(effectiveVolume(music.individualVolume, channel), music.soundId);\n\t\t}\n\t}\n\n\t// Propagate volume to all sounds across all channels\n\tfunction propagateAllVolumes(): void {\n\t\tfor (const ch of channelNames) {\n\t\t\tpropagateChannelVolume(ch);\n\t\t}\n\t}\n\n\t// Stop a sound by its Howler sound ID\n\tfunction stopSoundById(soundId: number): void {\n\t\tconst entry = activeSounds.get(soundId);\n\t\tif (!entry) return;\n\t\tentry.howl.stop(soundId);\n\t\tactiveSounds.delete(soundId);\n\t}\n\n\t// Event bus reference, set during initialization\n\tlet eventBusRef: { publish(event: string, data: unknown): void } | null = null;\n\n\t// Resolve Howl from asset key\n\tlet getAsset: ((key: string) => Howl) | null = null;\n\n\t// AudioState resource implementation\n\tconst audioState: AudioState<Ch> = {\n\t\tplay(sound, playOpts) {\n\t\t\tif (!getAsset) return -1;\n\t\t\tconst channel = playOpts?.channel ?? defaultChannel;\n\t\t\tconst individualVol = playOpts?.volume ?? 1;\n\t\t\tconst loop = playOpts?.loop ?? false;\n\n\t\t\tconst howl = getAsset(sound);\n\t\t\thowl.volume(effectiveVolume(individualVol, channel));\n\t\t\thowl.loop(loop);\n\t\t\tconst soundId = howl.play();\n\n\t\t\tconst entry: ActiveSound<Ch> = {\n\t\t\t\thowl,\n\t\t\t\tsoundId,\n\t\t\t\tchannel,\n\t\t\t\tindividualVolume: individualVol,\n\t\t\t\tassetKey: sound,\n\t\t\t\tentityId: -1,\n\t\t\t};\n\t\t\tactiveSounds.set(soundId, entry);\n\n\t\t\thowl.once('end', () => {\n\t\t\t\tactiveSounds.delete(soundId);\n\t\t\t\teventBusRef?.publish('soundEnded', {\n\t\t\t\t\tentityId: -1,\n\t\t\t\t\tsoundId,\n\t\t\t\t\tsound,\n\t\t\t\t} satisfies SoundEndedEvent);\n\t\t\t}, soundId);\n\n\t\t\treturn soundId;\n\t\t},\n\n\t\tstop(soundId) {\n\t\t\tstopSoundById(soundId);\n\t\t},\n\n\t\tplayMusic(sound, musicOpts) {\n\t\t\tif (!getAsset) return;\n\t\t\tconst channel = musicOpts?.channel ?? defaultChannel;\n\t\t\tconst individualVol = musicOpts?.volume ?? 1;\n\t\t\tconst loop = musicOpts?.loop ?? true;\n\n\t\t\t// Stop existing music on this channel\n\t\t\tconst existing = musicByChannel.get(channel);\n\t\t\tif (existing) {\n\t\t\t\texisting.howl.stop(existing.soundId);\n\t\t\t\tactiveSounds.delete(existing.soundId);\n\t\t\t}\n\n\t\t\tconst howl = getAsset(sound);\n\t\t\thowl.volume(effectiveVolume(individualVol, channel));\n\t\t\thowl.loop(loop);\n\t\t\tconst soundId = howl.play();\n\n\t\t\tconst entry: MusicEntry<Ch> = {\n\t\t\t\thowl,\n\t\t\t\tsoundId,\n\t\t\t\tchannel,\n\t\t\t\tindividualVolume: individualVol,\n\t\t\t\tassetKey: sound,\n\t\t\t};\n\t\t\tmusicByChannel.set(channel, entry);\n\t\t\tactiveSounds.set(soundId, {\n\t\t\t\t...entry,\n\t\t\t\tentityId: -1,\n\t\t\t});\n\n\t\t\thowl.once('end', () => {\n\t\t\t\tactiveSounds.delete(soundId);\n\t\t\t\tconst current = musicByChannel.get(channel);\n\t\t\t\tif (current?.soundId === soundId) {\n\t\t\t\t\tmusicByChannel.delete(channel);\n\t\t\t\t}\n\t\t\t}, soundId);\n\t\t},\n\n\t\tstopMusic(channel) {\n\t\t\tif (channel !== undefined) {\n\t\t\t\tconst entry = musicByChannel.get(channel);\n\t\t\t\tif (entry) {\n\t\t\t\t\tentry.howl.stop(entry.soundId);\n\t\t\t\t\tactiveSounds.delete(entry.soundId);\n\t\t\t\t\tmusicByChannel.delete(channel);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (const [ch, entry] of musicByChannel) {\n\t\t\t\t\tentry.howl.stop(entry.soundId);\n\t\t\t\t\tactiveSounds.delete(entry.soundId);\n\t\t\t\t\tmusicByChannel.delete(ch);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tpauseMusic(channel) {\n\t\t\tif (channel !== undefined) {\n\t\t\t\tconst entry = musicByChannel.get(channel);\n\t\t\t\tif (entry) entry.howl.pause(entry.soundId);\n\t\t\t} else {\n\t\t\t\tfor (const entry of musicByChannel.values()) {\n\t\t\t\t\tentry.howl.pause(entry.soundId);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tresumeMusic(channel) {\n\t\t\tif (channel !== undefined) {\n\t\t\t\tconst entry = musicByChannel.get(channel);\n\t\t\t\tif (entry) entry.howl.play(entry.soundId);\n\t\t\t} else {\n\t\t\t\tfor (const entry of musicByChannel.values()) {\n\t\t\t\t\tentry.howl.play(entry.soundId);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tsetChannelVolume(channel, volume) {\n\t\t\tchannelVolumes.set(channel, volume);\n\t\t\tpropagateChannelVolume(channel);\n\t\t},\n\n\t\tgetChannelVolume(channel) {\n\t\t\treturn channelVolumes.get(channel) ?? 1;\n\t\t},\n\n\t\tsetMasterVolume(volume) {\n\t\t\tmasterVolume = volume;\n\t\t\tpropagateAllVolumes();\n\t\t},\n\n\t\tgetMasterVolume() {\n\t\t\treturn masterVolume;\n\t\t},\n\n\t\tmute() {\n\t\t\tmuted = true;\n\t\t\tpropagateAllVolumes();\n\t\t},\n\n\t\tunmute() {\n\t\t\tmuted = false;\n\t\t\tpropagateAllVolumes();\n\t\t},\n\n\t\ttoggleMute() {\n\t\t\tmuted = !muted;\n\t\t\tpropagateAllVolumes();\n\t\t},\n\n\t\tisMuted() {\n\t\t\treturn muted;\n\t\t},\n\t};\n\n\t// Build bundle\n\tconst bundle = new Bundle<AudioComponentTypes<Ch>, AudioEventTypes<Ch>, AudioResourceTypes<Ch>>('audio');\n\n\tbundle.addResource('audioState', audioState);\n\n\t// Dispose callback: stop sounds when audioSource component is removed\n\tbundle.registerDispose('audioSource', (source: AudioSource<Ch>) => {\n\t\tif (source._soundId !== -1) {\n\t\t\tstopSoundById(source._soundId);\n\t\t}\n\t});\n\n\tbundle\n\t\t.addSystem('audio-sync')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.setOnInitialize((ecs) => {\n\t\t\teventBusRef = ecs.eventBus;\n\n\t\t\t// Resolve asset getter - works with $assets resource if available\n\t\t\tconst assets = ecs.tryGetResource<{ get(k: string): unknown }>('$assets');\n\t\t\tif (assets) {\n\t\t\t\tgetAsset = (key: string) => assets.get(key) as Howl;\n\t\t\t}\n\n\t\t\t// Register reactive query for audioSource components\n\t\t\tecs.addReactiveQuery('audio-sources', {\n\t\t\t\twith: ['audioSource'],\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst source = entity.components.audioSource;\n\t\t\t\t\tif (!getAsset) return;\n\t\t\t\t\tif (source._soundId !== -1) return; // Already started\n\n\t\t\t\t\tconst howl = getAsset(source.sound);\n\t\t\t\t\thowl.volume(effectiveVolume(source.volume, source.channel));\n\t\t\t\t\thowl.loop(source.loop);\n\t\t\t\t\tconst soundId = howl.play();\n\n\t\t\t\t\tsource._soundId = soundId;\n\t\t\t\t\tsource.playing = true;\n\n\t\t\t\t\tconst entry: ActiveSound<Ch> = {\n\t\t\t\t\t\thowl,\n\t\t\t\t\t\tsoundId,\n\t\t\t\t\t\tchannel: source.channel,\n\t\t\t\t\t\tindividualVolume: source.volume,\n\t\t\t\t\t\tassetKey: source.sound,\n\t\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\t};\n\t\t\t\t\tactiveSounds.set(soundId, entry);\n\n\t\t\t\t\thowl.once('end', () => {\n\t\t\t\t\t\tactiveSounds.delete(soundId);\n\t\t\t\t\t\tsource.playing = false;\n\n\t\t\t\t\t\teventBusRef?.publish('soundEnded', {\n\t\t\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\t\t\tsoundId,\n\t\t\t\t\t\t\tsound: source.sound,\n\t\t\t\t\t\t} satisfies SoundEndedEvent);\n\n\t\t\t\t\t\tif (source.autoRemove) {\n\t\t\t\t\t\t\tecs.commands.removeEntity(entity.id);\n\t\t\t\t\t\t}\n\t\t\t\t\t}, soundId);\n\t\t\t\t},\n\t\t\t\tonExit: (_entityId) => {\n\t\t\t\t\t// Cleanup handled by dispose callback\n\t\t\t\t},\n\t\t\t});\n\t\t})\n\t\t.setEventHandlers({\n\t\t\tplaySound: {\n\t\t\t\thandler(data, ecs) {\n\t\t\t\t\tconst audio = ecs.getResource('audioState');\n\t\t\t\t\taudio.play(data.sound, {\n\t\t\t\t\t\tchannel: data.channel,\n\t\t\t\t\t\tvolume: data.volume,\n\t\t\t\t\t\tloop: data.loop,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t},\n\t\t\tstopMusic: {\n\t\t\t\thandler(data, ecs) {\n\t\t\t\t\tconst audio = ecs.getResource('audioState');\n\t\t\t\t\taudio.stopMusic(data.channel);\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\t.setOnDetach(() => {\n\t\t\t// Stop all active sounds\n\t\t\tfor (const entry of activeSounds.values()) {\n\t\t\t\tentry.howl.stop(entry.soundId);\n\t\t\t}\n\t\t\tactiveSounds.clear();\n\t\t\tmusicByChannel.clear();\n\t\t\teventBusRef = null;\n\t\t\tgetAsset = null;\n\t\t})\n\t\t.and();\n\n\t// Declare reactive query names\n\tconst typedBundle = bundle.withReactiveQueryNames<'audio-sources'>();\n\n\treturn typedBundle as Bundle<AudioComponentTypes<Ch>, AudioEventTypes<Ch>, AudioResourceTypes<Ch>, {}, {}, 'audio-sync', G, never, 'audio-sources'>;\n}\n\n// ==================== Kit Pattern ====================\n\ntype AnyECSpresso = import('ecspresso').default<any, any, any, any, any, any, any>;\n\n/**\n * Kit result from createAudioKit.\n */\nexport interface AudioKit<W extends AnyECSpresso, Ch extends string, G extends string = 'audio'> {\n\tbundle: Bundle<AudioComponentTypes<Ch>, AudioEventTypes<Ch>, AudioResourceTypes<Ch>, {}, {}, 'audio-sync', G, never, 'audio-sources'>;\n\tcreateAudioSource: (\n\t\tsound: keyof AssetsOfWorld<W> & string,\n\t\tchannel: Ch,\n\t\toptions?: { volume?: number; loop?: boolean; autoRemove?: boolean }\n\t) => Pick<AudioComponentTypes<Ch>, 'audioSource'>;\n\tloadSound: typeof loadSound;\n}\n\n/**\n * Create a typed audio kit that captures the world type W and channel type Ch.\n *\n * The returned `createAudioSource` validates sound keys against the world's\n * asset types at compile time.\n *\n * @template W - Concrete ECS world type (e.g. `typeof ecs`)\n * @template Ch - Channel name union from defineAudioChannels\n * @template G - System group name (default: 'audio')\n * @param options - Bundle configuration including channel definitions\n * @returns A kit object with bundle, createAudioSource, loadSound\n *\n * @example\n * ```typescript\n * const channels = defineAudioChannels({ sfx: { volume: 1 }, music: { volume: 0.7 } });\n * type Ch = ChannelsOf<typeof channels>;\n * const kit = createAudioKit<typeof ecs, Ch>({ channels });\n *\n * const ecs = ECSpresso.create()\n * .withBundle(kit.bundle)\n * .withAssets(a => a.add('boom', loadSound('/sfx/boom.mp3')))\n * .build();\n *\n * // Type-safe: 'boom' must be a registered asset\n * kit.createAudioSource('boom', 'sfx');\n * ```\n */\nexport function createAudioKit<W extends AnyECSpresso, Ch extends string, G extends string = 'audio'>(\n\toptions: AudioBundleOptions<Ch, G>\n): AudioKit<W, Ch, G> {\n\treturn {\n\t\tbundle: createAudioBundle<Ch, G>(options),\n\t\tcreateAudioSource: createAudioSource as AudioKit<W, Ch, G>['createAudioSource'],\n\t\tloadSound,\n\t};\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "08A2E01637CA322364756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
var{defineProperty:B,getOwnPropertyNames:q,getOwnPropertyDescriptor:M}=Object,w=Object.prototype.hasOwnProperty;var R=new WeakMap,y=(j)=>{var k=R.get(j),z;if(k)return k;if(k=B({},"__esModule",{value:!0}),j&&typeof j==="object"||typeof j==="function")q(j).map((F)=>!w.call(k,F)&&B(k,F,{get:()=>j[F],enumerable:!(z=M(j,F))||z.enumerable}));return R.set(j,k),k};var x=(j,k)=>{for(var z in k)B(j,z,{get:k[z],enumerable:!0,configurable:!0,set:(F)=>k[z]=()=>F})};var b=(j,k)=>()=>(j&&(k=j(j=0)),k);var f=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,z)=>(typeof require<"u"?require:k)[z]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{Bundle as h}from"ecspresso";function c(j,k,z,F){let U={width:j,height:k};if(z!==void 0)U.x=z;if(F!==void 0)U.y=F;return U}function g(j){return{destroyOutOfBounds:j!==void 0?{padding:j}:{}}}function u(j){return{clampToBounds:j!==void 0?{margin:j}:{}}}function p(j){return{wrapAtBounds:j!==void 0?{padding:j}:{}}}function l(j){let{systemGroup:k="physics",priority:z=50,boundsResourceKey:F="bounds",autoRemove:U=!0,phase:S="postUpdate"}=j??{},W=new h("bounds");return W.addSystem("bounds-destroy").setPriority(z).inPhase(S).inGroup(k).addQuery("entities",{with:["worldTransform","destroyOutOfBounds"]}).setProcess((A,E,L)=>{let J=L.getResource(F),N=J.x??0,P=J.y??0,$=N+J.width,v=P+J.height;for(let Q of A.entities){let{worldTransform:V,destroyOutOfBounds:I}=Q.components,D=I.padding??0,C=T(V,N,P,$,v,D);if(!C)continue;if(L.eventBus.publish("entityOutOfBounds",{entityId:Q.id,exitEdge:C}),U)L.commands.removeEntity(Q.id)}}).and(),W.addSystem("bounds-clamp").setPriority(z-1).inPhase(S).inGroup(k).addQuery("entities",{with:["localTransform","worldTransform","clampToBounds"]}).setProcess((A,E,L)=>{let J=L.getResource(F),N=J.x??0,P=J.y??0,$=N+J.width,v=P+J.height;for(let Q of A.entities){let{localTransform:V,worldTransform:I,clampToBounds:D}=Q.components,C=D.margin??0,Z=N+C,_=P+C,H=$-C,O=v-C,G=0,K=0;if(I.x<Z)G=Z-I.x;if(I.x>H)G=H-I.x;if(I.y<_)K=_-I.y;if(I.y>O)K=O-I.y;if(G!==0||K!==0)V.x+=G,V.y+=K,L.markChanged(Q.id,"localTransform")}}).and(),W.addSystem("bounds-wrap").setPriority(z-2).inPhase(S).inGroup(k).addQuery("entities",{with:["localTransform","worldTransform","wrapAtBounds"]}).setProcess((A,E,L)=>{let J=L.getResource(F),N=J.x??0,P=J.y??0,$=N+J.width,v=P+J.height;for(let Q of A.entities){let{localTransform:V,worldTransform:I,wrapAtBounds:D}=Q.components,C=D.padding??0,Z=0,_=0,H=$-N,O=v-P;if(I.x>$+C)Z=-(H+2*C);else if(I.x<N-C)Z=H+2*C;if(I.y>v+C)_=-(O+2*C);else if(I.y<P-C)_=O+2*C;if(Z!==0||_!==0)V.x+=Z,V.y+=_,L.markChanged(Q.id,"localTransform")}}).and(),W}function T(j,k,z,F,U,S){if(j.x>F+S)return"right";if(j.x<k-S)return"left";if(j.y>U+S)return"bottom";if(j.y<z-S)return"top";return null}export{p as createWrapAtBounds,g as createDestroyOutOfBounds,u as createClampToBounds,l as createBoundsBundle,c as createBounds};
|
|
2
|
-
|
|
3
|
-
//# debugId=88C2C3EC79B631F264756E2164756E21
|
|
4
|
-
//# sourceMappingURL=bounds.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/bundles/bounds.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"/**\n * Bounds Bundle for ECSpresso\n *\n * Provides screen bounds enforcement for entities with transforms.\n * Reads worldTransform for position checking; modifies localTransform for corrections.\n * Supports destroy, clamp, and wrap behaviors.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { TransformComponentTypes } from './transform';\n\n// ==================== Component Types ====================\n\n/**\n * Component that marks an entity for destruction when outside bounds.\n */\nexport interface DestroyOutOfBounds {\n\t/** Extra padding beyond bounds before destruction (default: 0) */\n\tpadding?: number;\n}\n\n/**\n * Component that clamps an entity's position to stay within bounds.\n */\nexport interface ClampToBounds {\n\t/** Margin to shrink the valid area (default: 0) */\n\tmargin?: number;\n}\n\n/**\n * Component that wraps an entity's position to the opposite edge.\n */\nexport interface WrapAtBounds {\n\t/** Padding beyond bounds before wrapping (default: 0) */\n\tpadding?: number;\n}\n\n/**\n * Component types provided by the bounds bundle.\n * Included automatically via `.withBundle(createBoundsBundle())`.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createTransformBundle())\n * .withBundle(createBoundsBundle({ width: 800, height: 600 }))\n * .withComponentTypes<{ sprite: Sprite }>()\n * .build();\n * ```\n */\nexport interface BoundsComponentTypes {\n\tdestroyOutOfBounds: DestroyOutOfBounds;\n\tclampToBounds: ClampToBounds;\n\twrapAtBounds: WrapAtBounds;\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Bounds rectangle definition.\n */\nexport interface BoundsRect {\n\t/** Left edge x coordinate (default: 0) */\n\tx?: number;\n\t/** Top edge y coordinate (default: 0) */\n\ty?: number;\n\t/** Width of the bounds area */\n\twidth: number;\n\t/** Height of the bounds area */\n\theight: number;\n}\n\n/**\n * Resource types provided by the bounds bundle.\n */\nexport interface BoundsResourceTypes {\n\tbounds: BoundsRect;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when an entity exits bounds.\n */\nexport interface EntityOutOfBoundsEvent {\n\t/** The entity that exited bounds */\n\tentityId: number;\n\t/** The edge the entity exited through */\n\texitEdge: 'top' | 'bottom' | 'left' | 'right';\n}\n\n/**\n * Event types provided by the bounds bundle.\n */\nexport interface BoundsEventTypes {\n\tentityOutOfBounds: EntityOutOfBoundsEvent;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the bounds bundle.\n */\nexport interface BoundsBundleOptions<G extends string = 'physics'> {\n\t/** System group name (default: 'physics') */\n\tsystemGroup?: G;\n\t/** Priority for bounds systems (default: 50) */\n\tpriority?: number;\n\t/** Resource key for bounds rectangle (default: 'bounds') */\n\tboundsResourceKey?: string;\n\t/** Whether to auto-remove entities when out of bounds (default: true) */\n\tautoRemove?: boolean;\n\t/** Execution phase (default: 'postUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a bounds rectangle resource.\n *\n * @param width The width of the bounds area\n * @param height The height of the bounds area\n * @param x The left edge x coordinate (default: 0)\n * @param y The top edge y coordinate (default: 0)\n * @returns Bounds rectangle suitable for use as a resource\n *\n * @example\n * ```typescript\n * ECSpresso.create()\n * .withResource('bounds', createBounds(800, 600))\n * .build();\n * ```\n */\nexport function createBounds(width: number, height: number, x?: number, y?: number): BoundsRect {\n\tconst bounds: BoundsRect = { width, height };\n\tif (x !== undefined) bounds.x = x;\n\tif (y !== undefined) bounds.y = y;\n\treturn bounds;\n}\n\n/**\n * Create a destroyOutOfBounds component.\n *\n * @param padding Extra padding beyond bounds before destruction\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createDestroyOutOfBounds(20),\n * });\n * ```\n */\nexport function createDestroyOutOfBounds(padding?: number): Pick<BoundsComponentTypes, 'destroyOutOfBounds'> {\n\treturn {\n\t\tdestroyOutOfBounds: padding !== undefined ? { padding } : {},\n\t};\n}\n\n/**\n * Create a clampToBounds component.\n *\n * @param margin Margin to shrink the valid area\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createClampToBounds(30),\n * });\n * ```\n */\nexport function createClampToBounds(margin?: number): Pick<BoundsComponentTypes, 'clampToBounds'> {\n\treturn {\n\t\tclampToBounds: margin !== undefined ? { margin } : {},\n\t};\n}\n\n/**\n * Create a wrapAtBounds component.\n *\n * @param padding Padding beyond bounds before wrapping\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createWrapAtBounds(10),\n * });\n * ```\n */\nexport function createWrapAtBounds(padding?: number): Pick<BoundsComponentTypes, 'wrapAtBounds'> {\n\treturn {\n\t\twrapAtBounds: padding !== undefined ? { padding } : {},\n\t};\n}\n\n// ==================== Internal Types ====================\n\ntype CombinedComponentTypes = BoundsComponentTypes & TransformComponentTypes;\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a bounds bundle for ECSpresso.\n *\n * This bundle provides:\n * - Destroy out of bounds system - removes entities that exit bounds\n * - Clamp to bounds system - constrains entities within bounds\n * - Wrap at bounds system - wraps entities to opposite edge\n *\n * Uses worldTransform for position checking (world-space) and modifies\n * localTransform for corrections. Works best with entities that don't\n * have parent transforms (orphan entities).\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso\n * .create<Components, Events, Resources>()\n * .withResource('bounds', createBounds(800, 600))\n * .withBundle(createTransformBundle())\n * .withBundle(createBoundsBundle())\n * .build();\n *\n * // Entity that gets destroyed when leaving screen\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createDestroyOutOfBounds(),\n * });\n * ```\n */\nexport function createBoundsBundle<ResourceTypes extends BoundsResourceTypes = BoundsResourceTypes, G extends string = 'physics'>(\n\toptions?: BoundsBundleOptions<G>\n): Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes, {}, {}, 'bounds-destroy' | 'bounds-clamp' | 'bounds-wrap', G> {\n\tconst {\n\t\tsystemGroup = 'physics',\n\t\tpriority = 50,\n\t\tboundsResourceKey = 'bounds',\n\t\tautoRemove = true,\n\t\tphase = 'postUpdate',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes>('bounds');\n\n\t// Destroy out of bounds system\n\tbundle\n\t\t.addSystem('bounds-destroy')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['worldTransform', 'destroyOutOfBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { worldTransform, destroyOutOfBounds } = entity.components;\n\t\t\t\tconst padding = destroyOutOfBounds.padding ?? 0;\n\n\t\t\t\tconst exitEdge = getExitEdge(worldTransform, minX, minY, maxX, maxY, padding);\n\t\t\t\tif (!exitEdge) continue;\n\n\t\t\t\tecs.eventBus.publish('entityOutOfBounds', {\n\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\texitEdge,\n\t\t\t\t});\n\n\t\t\t\tif (autoRemove) {\n\t\t\t\t\tecs.commands.removeEntity(entity.id);\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// Clamp to bounds system\n\tbundle\n\t\t.addSystem('bounds-clamp')\n\t\t.setPriority(priority - 1)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['localTransform', 'worldTransform', 'clampToBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { localTransform, worldTransform, clampToBounds } = entity.components;\n\t\t\t\tconst margin = clampToBounds.margin ?? 0;\n\n\t\t\t\tconst clampedMinX = minX + margin;\n\t\t\t\tconst clampedMinY = minY + margin;\n\t\t\t\tconst clampedMaxX = maxX - margin;\n\t\t\t\tconst clampedMaxY = maxY - margin;\n\n\t\t\t\t// Calculate world-space correction and apply to local transform\n\t\t\t\t// For entities without parents, this is equivalent to direct position clamping\n\t\t\t\tlet deltaX = 0;\n\t\t\t\tlet deltaY = 0;\n\n\t\t\t\tif (worldTransform.x < clampedMinX) deltaX = clampedMinX - worldTransform.x;\n\t\t\t\tif (worldTransform.x > clampedMaxX) deltaX = clampedMaxX - worldTransform.x;\n\t\t\t\tif (worldTransform.y < clampedMinY) deltaY = clampedMinY - worldTransform.y;\n\t\t\t\tif (worldTransform.y > clampedMaxY) deltaY = clampedMaxY - worldTransform.y;\n\n\t\t\t\tif (deltaX !== 0 || deltaY !== 0) {\n\t\t\t\t\tlocalTransform.x += deltaX;\n\t\t\t\t\tlocalTransform.y += deltaY;\n\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// Wrap at bounds system\n\tbundle\n\t\t.addSystem('bounds-wrap')\n\t\t.setPriority(priority - 2)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['localTransform', 'worldTransform', 'wrapAtBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { localTransform, worldTransform, wrapAtBounds } = entity.components;\n\t\t\t\tconst padding = wrapAtBounds.padding ?? 0;\n\n\t\t\t\tlet deltaX = 0;\n\t\t\t\tlet deltaY = 0;\n\t\t\t\tconst boundsWidth = maxX - minX;\n\t\t\t\tconst boundsHeight = maxY - minY;\n\n\t\t\t\t// Wrap horizontally\n\t\t\t\tif (worldTransform.x > maxX + padding) {\n\t\t\t\t\tdeltaX = -(boundsWidth + 2 * padding);\n\t\t\t\t} else if (worldTransform.x < minX - padding) {\n\t\t\t\t\tdeltaX = boundsWidth + 2 * padding;\n\t\t\t\t}\n\n\t\t\t\t// Wrap vertically\n\t\t\t\tif (worldTransform.y > maxY + padding) {\n\t\t\t\t\tdeltaY = -(boundsHeight + 2 * padding);\n\t\t\t\t} else if (worldTransform.y < minY - padding) {\n\t\t\t\t\tdeltaY = boundsHeight + 2 * padding;\n\t\t\t\t}\n\n\t\t\t\tif (deltaX !== 0 || deltaY !== 0) {\n\t\t\t\t\tlocalTransform.x += deltaX;\n\t\t\t\t\tlocalTransform.y += deltaY;\n\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\treturn bundle as Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes, {}, {}, 'bounds-destroy' | 'bounds-clamp' | 'bounds-wrap', G>;\n}\n\n/**\n * Determine which edge an entity has exited through, if any.\n */\nfunction getExitEdge(\n\ttransform: { x: number; y: number },\n\tminX: number,\n\tminY: number,\n\tmaxX: number,\n\tmaxY: number,\n\tpadding: number\n): 'top' | 'bottom' | 'left' | 'right' | null {\n\tif (transform.x > maxX + padding) return 'right';\n\tif (transform.x < minX - padding) return 'left';\n\tif (transform.y > maxY + padding) return 'bottom';\n\tif (transform.y < minY - padding) return 'top';\n\treturn null;\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "88C2C3EC79B631F264756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
var{defineProperty:T,getOwnPropertyNames:M,getOwnPropertyDescriptor:B}=Object,v=Object.prototype.hasOwnProperty;var q=new WeakMap,k=(j)=>{var C=q.get(j),A;if(C)return C;if(C=T({},"__esModule",{value:!0}),j&&typeof j==="object"||typeof j==="function")M(j).map((J)=>!v.call(C,J)&&T(C,J,{get:()=>j[J],enumerable:!(A=B(j,J))||A.enumerable}));return q.set(j,C),C};var X=(j,C)=>{for(var A in C)T(j,A,{get:C[A],enumerable:!0,configurable:!0,set:(J)=>C[A]=()=>J})};var Y=(j,C)=>()=>(j&&(C=j(j=0)),C);var g=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(C,A)=>(typeof require<"u"?require:C)[A]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{Bundle as F}from"ecspresso";var h={x:0,y:0,zoom:1,rotation:0},f={x:0,y:0,zoom:1,rotation:0,shakeOffsetX:0,shakeOffsetY:0,shakeRotation:0,viewportWidth:800,viewportHeight:600};function y(j=0,C=0,A=1,J=0){return{camera:{x:j,y:C,zoom:A,rotation:J}}}function u(j,C){return{cameraFollow:{target:j,smoothing:C?.smoothing??5,deadzoneX:C?.deadzoneX??0,deadzoneY:C?.deadzoneY??0,offsetX:C?.offsetX??0,offsetY:C?.offsetY??0}}}function p(j){return{cameraShake:{trauma:j?.trauma??0,traumaDecay:j?.traumaDecay??1,maxOffsetX:j?.maxOffsetX??10,maxOffsetY:j?.maxOffsetY??10,maxRotation:j?.maxRotation??0.05}}}function d(j,C,A,J){return{cameraBounds:{minX:j,minY:C,maxX:A,maxY:J}}}function l(j,C,A){let J=j.entityManager.getComponent(C,"cameraShake");if(!J)return;J.trauma=Math.min(1,Math.max(0,J.trauma+A))}function m(j,C,A){let J=j-(A.x+A.shakeOffsetX),U=C-(A.y+A.shakeOffsetY),V=-(A.rotation+A.shakeRotation),P=Math.cos(V),Q=Math.sin(V),Z=J*P-U*Q,O=J*Q+U*P;return{x:Z*A.zoom+A.viewportWidth/2,y:O*A.zoom+A.viewportHeight/2}}function c(j,C,A){let J=(j-A.viewportWidth/2)/A.zoom,U=(C-A.viewportHeight/2)/A.zoom,V=A.rotation+A.shakeRotation,P=Math.cos(V),Q=Math.sin(V),Z=J*P-U*Q,O=J*Q+U*P;return{x:Z+A.x+A.shakeOffsetX,y:O+A.y+A.shakeOffsetY}}function i(j){let{viewportWidth:C=800,viewportHeight:A=600,systemGroup:J="camera",phase:U="postUpdate",randomFn:V=Math.random}=j??{},P=new F("camera");return P.addResource("cameraState",{x:0,y:0,zoom:1,rotation:0,shakeOffsetX:0,shakeOffsetY:0,shakeRotation:0,viewportWidth:C,viewportHeight:A}),P.addSystem("camera-follow").setPriority(400).inPhase(U).inGroup(J).addQuery("cameras",{with:["camera","cameraFollow"]}).setProcess((Q,Z,O)=>{let E=O.entityManager,z=Math.min(1,Z);for(let N of Q.cameras){let{camera:L,cameraFollow:K}=N.components,R;try{R=E.getComponent(K.target,"worldTransform")}catch{continue}if(!R)continue;let D=R.x+K.offsetX,I=R.y+K.offsetY,_=D-L.x,$=I-L.y,W=Math.abs(_),b=Math.abs($);if(W>K.deadzoneX){let H=_>0?1:-1,S=_-H*K.deadzoneX,G=Math.min(1,K.smoothing*z);L.x+=S*G}if(b>K.deadzoneY){let H=$>0?1:-1,S=$-H*K.deadzoneY,G=Math.min(1,K.smoothing*z);L.y+=S*G}}}).and(),P.addSystem("camera-shake-update").setPriority(390).inPhase(U).inGroup(J).addQuery("shakeCameras",{with:["camera","cameraShake"]}).setProcess((Q,Z)=>{for(let O of Q.shakeCameras){let{cameraShake:E}=O.components;E.trauma=Math.max(0,E.trauma-E.traumaDecay*Z)}}).and(),P.addSystem("camera-bounds").setPriority(380).inPhase(U).inGroup(J).addQuery("boundedCameras",{with:["camera","cameraBounds"]}).setProcess((Q,Z,O)=>{let E=O.getResource("cameraState");for(let z of Q.boundedCameras){let{camera:N,cameraBounds:L}=z.components,K=E.viewportWidth/(2*N.zoom),R=E.viewportHeight/(2*N.zoom),D=L.minX+K,I=L.maxX-K,_=L.minY+R,$=L.maxY-R;if(D>I)N.x=(L.minX+L.maxX)/2;else N.x=Math.max(D,Math.min(I,N.x));if(_>$)N.y=(L.minY+L.maxY)/2;else N.y=Math.max(_,Math.min($,N.y))}}).and(),P.addSystem("camera-state-sync").setPriority(370).inPhase(U).inGroup(J).setProcess((Q,Z,O)=>{let E=O.getResource("cameraState"),N=O.getEntitiesWithQuery(["camera"])[0];if(!N){E.x=0,E.y=0,E.zoom=1,E.rotation=0,E.shakeOffsetX=0,E.shakeOffsetY=0,E.shakeRotation=0;return}let L=N.components.camera;E.x=L.x,E.y=L.y,E.zoom=L.zoom,E.rotation=L.rotation;let K=O.entityManager.getComponent(N.id,"cameraShake");if(K&&K.trauma>0){let R=K.trauma*K.trauma;E.shakeOffsetX=K.maxOffsetX*R*(V()*2-1),E.shakeOffsetY=K.maxOffsetY*R*(V()*2-1),E.shakeRotation=K.maxRotation*R*(V()*2-1)}else E.shakeOffsetX=0,E.shakeOffsetY=0,E.shakeRotation=0}).and(),P}export{m as worldToScreen,c as screenToWorld,p as createCameraShake,u as createCameraFollow,i as createCameraBundle,d as createCameraBounds,y as createCamera,l as addTrauma,f as DEFAULT_CAMERA_STATE,h as DEFAULT_CAMERA};
|
|
2
|
-
|
|
3
|
-
//# debugId=4B3DDFC6FF7BF00364756E2164756E21
|
|
4
|
-
//# sourceMappingURL=camera.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/bundles/camera.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"/**\n * Camera / Viewport Bundle for ECSpresso\n *\n * Provides a camera entity with world/screen coordinate conversion, smooth follow,\n * trauma-based shake, bounds clamping, and logical viewport dimensions.\n *\n * This bundle is renderer-agnostic. PixiJS or other renderer integration (applying\n * cameraState to a container/stage transform) is the consumer's responsibility.\n *\n * Camera uses its own x/y/zoom/rotation rather than localTransform/worldTransform.\n * It reads the target entity's worldTransform for follow, but doesn't participate\n * in the transform hierarchy itself.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type ECSpresso from 'ecspresso';\nimport type { TransformComponentTypes } from './transform';\n\n// ==================== Component Types ====================\n\nexport interface Camera {\n\tx: number;\n\ty: number;\n\tzoom: number;\n\trotation: number;\n}\n\nexport interface CameraFollow {\n\ttarget: number;\n\tsmoothing: number;\n\tdeadzoneX: number;\n\tdeadzoneY: number;\n\toffsetX: number;\n\toffsetY: number;\n}\n\nexport interface CameraShake {\n\ttrauma: number;\n\ttraumaDecay: number;\n\tmaxOffsetX: number;\n\tmaxOffsetY: number;\n\tmaxRotation: number;\n}\n\nexport interface CameraBounds {\n\tminX: number;\n\tminY: number;\n\tmaxX: number;\n\tmaxY: number;\n}\n\nexport interface CameraComponentTypes {\n\tcamera: Camera;\n\tcameraFollow: CameraFollow;\n\tcameraShake: CameraShake;\n\tcameraBounds: CameraBounds;\n}\n\ntype CombinedComponentTypes = CameraComponentTypes & TransformComponentTypes;\n\n// ==================== Resource Types ====================\n\nexport interface CameraState {\n\tx: number;\n\ty: number;\n\tzoom: number;\n\trotation: number;\n\tshakeOffsetX: number;\n\tshakeOffsetY: number;\n\tshakeRotation: number;\n\tviewportWidth: number;\n\tviewportHeight: number;\n}\n\nexport interface CameraResourceTypes {\n\tcameraState: CameraState;\n}\n\n// ==================== Bundle Options ====================\n\nexport interface CameraBundleOptions<G extends string = 'camera'> {\n\tviewportWidth?: number;\n\tviewportHeight?: number;\n\tsystemGroup?: G;\n\tphase?: SystemPhase;\n\trandomFn?: () => number;\n}\n\n// ==================== Default Values ====================\n\nexport const DEFAULT_CAMERA: Readonly<Camera> = {\n\tx: 0,\n\ty: 0,\n\tzoom: 1,\n\trotation: 0,\n};\n\nexport const DEFAULT_CAMERA_STATE: Readonly<CameraState> = {\n\tx: 0,\n\ty: 0,\n\tzoom: 1,\n\trotation: 0,\n\tshakeOffsetX: 0,\n\tshakeOffsetY: 0,\n\tshakeRotation: 0,\n\tviewportWidth: 800,\n\tviewportHeight: 600,\n};\n\n// ==================== Helper Functions ====================\n\nexport function createCamera(\n\tx = 0,\n\ty = 0,\n\tzoom = 1,\n\trotation = 0,\n): Pick<CameraComponentTypes, 'camera'> {\n\treturn {\n\t\tcamera: { x, y, zoom, rotation },\n\t};\n}\n\nexport function createCameraFollow(\n\ttarget: number,\n\toptions?: Partial<Omit<CameraFollow, 'target'>>,\n): Pick<CameraComponentTypes, 'cameraFollow'> {\n\treturn {\n\t\tcameraFollow: {\n\t\t\ttarget,\n\t\t\tsmoothing: options?.smoothing ?? 5,\n\t\t\tdeadzoneX: options?.deadzoneX ?? 0,\n\t\t\tdeadzoneY: options?.deadzoneY ?? 0,\n\t\t\toffsetX: options?.offsetX ?? 0,\n\t\t\toffsetY: options?.offsetY ?? 0,\n\t\t},\n\t};\n}\n\nexport function createCameraShake(\n\toptions?: Partial<CameraShake>,\n): Pick<CameraComponentTypes, 'cameraShake'> {\n\treturn {\n\t\tcameraShake: {\n\t\t\ttrauma: options?.trauma ?? 0,\n\t\t\ttraumaDecay: options?.traumaDecay ?? 1,\n\t\t\tmaxOffsetX: options?.maxOffsetX ?? 10,\n\t\t\tmaxOffsetY: options?.maxOffsetY ?? 10,\n\t\t\tmaxRotation: options?.maxRotation ?? 0.05,\n\t\t},\n\t};\n}\n\nexport function createCameraBounds(\n\tminX: number,\n\tminY: number,\n\tmaxX: number,\n\tmaxY: number,\n): Pick<CameraComponentTypes, 'cameraBounds'> {\n\treturn {\n\t\tcameraBounds: { minX, minY, maxX, maxY },\n\t};\n}\n\nexport function addTrauma<\n\tC extends CombinedComponentTypes,\n\tR extends CameraResourceTypes,\n>(\n\tecs: ECSpresso<C, any, R>,\n\tentityId: number,\n\tamount: number,\n): void {\n\tconst shake = ecs.entityManager.getComponent(entityId, 'cameraShake');\n\tif (!shake) return;\n\tshake.trauma = Math.min(1, Math.max(0, shake.trauma + amount));\n}\n\n// ==================== Coordinate Conversion ====================\n\nexport function worldToScreen(\n\tworldX: number,\n\tworldY: number,\n\tstate: CameraState,\n): { x: number; y: number } {\n\tconst dx = worldX - (state.x + state.shakeOffsetX);\n\tconst dy = worldY - (state.y + state.shakeOffsetY);\n\n\tconst angle = -(state.rotation + state.shakeRotation);\n\tconst cos = Math.cos(angle);\n\tconst sin = Math.sin(angle);\n\tconst rx = dx * cos - dy * sin;\n\tconst ry = dx * sin + dy * cos;\n\n\treturn {\n\t\tx: rx * state.zoom + state.viewportWidth / 2,\n\t\ty: ry * state.zoom + state.viewportHeight / 2,\n\t};\n}\n\nexport function screenToWorld(\n\tscreenX: number,\n\tscreenY: number,\n\tstate: CameraState,\n): { x: number; y: number } {\n\tconst cx = (screenX - state.viewportWidth / 2) / state.zoom;\n\tconst cy = (screenY - state.viewportHeight / 2) / state.zoom;\n\n\tconst angle = state.rotation + state.shakeRotation;\n\tconst cos = Math.cos(angle);\n\tconst sin = Math.sin(angle);\n\tconst rx = cx * cos - cy * sin;\n\tconst ry = cx * sin + cy * cos;\n\n\treturn {\n\t\tx: rx + state.x + state.shakeOffsetX,\n\t\ty: ry + state.y + state.shakeOffsetY,\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\nexport function createCameraBundle<G extends string = 'camera'>(\n\toptions?: CameraBundleOptions<G>,\n): Bundle<CombinedComponentTypes, {}, CameraResourceTypes, {}, {}, 'camera-follow' | 'camera-shake-update' | 'camera-bounds' | 'camera-state-sync', G> {\n\tconst {\n\t\tviewportWidth = 800,\n\t\tviewportHeight = 600,\n\t\tsystemGroup = 'camera',\n\t\tphase = 'postUpdate',\n\t\trandomFn = Math.random,\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<CombinedComponentTypes, {}, CameraResourceTypes>('camera');\n\n\tbundle.addResource('cameraState', {\n\t\tx: 0,\n\t\ty: 0,\n\t\tzoom: 1,\n\t\trotation: 0,\n\t\tshakeOffsetX: 0,\n\t\tshakeOffsetY: 0,\n\t\tshakeRotation: 0,\n\t\tviewportWidth,\n\t\tviewportHeight,\n\t});\n\n\t// camera-follow: priority 400 (after transform propagation at 500)\n\tbundle\n\t\t.addSystem('camera-follow')\n\t\t.setPriority(400)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('cameras', {\n\t\t\twith: ['camera', 'cameraFollow'],\n\t\t})\n\t\t.setProcess((queries, deltaTime, ecs) => {\n\t\t\tconst em = ecs.entityManager;\n\t\t\tconst t = Math.min(1, deltaTime);\n\t\t\tfor (const entity of queries.cameras) {\n\t\t\t\tconst { camera, cameraFollow } = entity.components;\n\t\t\t\tlet targetWorld;\n\t\t\t\ttry {\n\t\t\t\t\ttargetWorld = em.getComponent(cameraFollow.target, 'worldTransform');\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (!targetWorld) continue;\n\n\t\t\t\tconst goalX = targetWorld.x + cameraFollow.offsetX;\n\t\t\t\tconst goalY = targetWorld.y + cameraFollow.offsetY;\n\t\t\t\tconst dx = goalX - camera.x;\n\t\t\t\tconst dy = goalY - camera.y;\n\n\t\t\t\tconst absDx = Math.abs(dx);\n\t\t\t\tconst absDy = Math.abs(dy);\n\n\t\t\t\tif (absDx > cameraFollow.deadzoneX) {\n\t\t\t\t\tconst sign = dx > 0 ? 1 : -1;\n\t\t\t\t\tconst excessX = dx - sign * cameraFollow.deadzoneX;\n\t\t\t\t\tconst factor = Math.min(1, cameraFollow.smoothing * t);\n\t\t\t\t\tcamera.x += excessX * factor;\n\t\t\t\t}\n\t\t\t\tif (absDy > cameraFollow.deadzoneY) {\n\t\t\t\t\tconst sign = dy > 0 ? 1 : -1;\n\t\t\t\t\tconst excessY = dy - sign * cameraFollow.deadzoneY;\n\t\t\t\t\tconst factor = Math.min(1, cameraFollow.smoothing * t);\n\t\t\t\t\tcamera.y += excessY * factor;\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// camera-shake-update: priority 390\n\tbundle\n\t\t.addSystem('camera-shake-update')\n\t\t.setPriority(390)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('shakeCameras', {\n\t\t\twith: ['camera', 'cameraShake'],\n\t\t})\n\t\t.setProcess((queries, deltaTime) => {\n\t\t\tfor (const entity of queries.shakeCameras) {\n\t\t\t\tconst { cameraShake } = entity.components;\n\t\t\t\tcameraShake.trauma = Math.max(0, cameraShake.trauma - cameraShake.traumaDecay * deltaTime);\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// camera-bounds: priority 380\n\tbundle\n\t\t.addSystem('camera-bounds')\n\t\t.setPriority(380)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('boundedCameras', {\n\t\t\twith: ['camera', 'cameraBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst state = ecs.getResource('cameraState');\n\t\t\tfor (const entity of queries.boundedCameras) {\n\t\t\t\tconst { camera, cameraBounds } = entity.components;\n\t\t\t\tconst halfW = state.viewportWidth / (2 * camera.zoom);\n\t\t\t\tconst halfH = state.viewportHeight / (2 * camera.zoom);\n\n\t\t\t\tconst effectiveMinX = cameraBounds.minX + halfW;\n\t\t\t\tconst effectiveMaxX = cameraBounds.maxX - halfW;\n\t\t\t\tconst effectiveMinY = cameraBounds.minY + halfH;\n\t\t\t\tconst effectiveMaxY = cameraBounds.maxY - halfH;\n\n\t\t\t\tif (effectiveMinX > effectiveMaxX) {\n\t\t\t\t\tcamera.x = (cameraBounds.minX + cameraBounds.maxX) / 2;\n\t\t\t\t} else {\n\t\t\t\t\tcamera.x = Math.max(effectiveMinX, Math.min(effectiveMaxX, camera.x));\n\t\t\t\t}\n\n\t\t\t\tif (effectiveMinY > effectiveMaxY) {\n\t\t\t\t\tcamera.y = (cameraBounds.minY + cameraBounds.maxY) / 2;\n\t\t\t\t} else {\n\t\t\t\t\tcamera.y = Math.max(effectiveMinY, Math.min(effectiveMaxY, camera.y));\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// camera-state-sync: priority 370\n\tbundle\n\t\t.addSystem('camera-state-sync')\n\t\t.setPriority(370)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.setProcess((_queries, _deltaTime, ecs) => {\n\t\t\tconst state = ecs.getResource('cameraState');\n\t\t\tconst cameras = ecs.getEntitiesWithQuery(['camera']);\n\t\t\tconst first = cameras[0];\n\n\t\t\tif (!first) {\n\t\t\t\tstate.x = 0;\n\t\t\t\tstate.y = 0;\n\t\t\t\tstate.zoom = 1;\n\t\t\t\tstate.rotation = 0;\n\t\t\t\tstate.shakeOffsetX = 0;\n\t\t\t\tstate.shakeOffsetY = 0;\n\t\t\t\tstate.shakeRotation = 0;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst camera = first.components.camera;\n\t\t\tstate.x = camera.x;\n\t\t\tstate.y = camera.y;\n\t\t\tstate.zoom = camera.zoom;\n\t\t\tstate.rotation = camera.rotation;\n\n\t\t\tconst shake = ecs.entityManager.getComponent(first.id, 'cameraShake');\n\t\t\tif (shake && shake.trauma > 0) {\n\t\t\t\tconst intensity = shake.trauma * shake.trauma;\n\t\t\t\tstate.shakeOffsetX = shake.maxOffsetX * intensity * (randomFn() * 2 - 1);\n\t\t\t\tstate.shakeOffsetY = shake.maxOffsetY * intensity * (randomFn() * 2 - 1);\n\t\t\t\tstate.shakeRotation = shake.maxRotation * intensity * (randomFn() * 2 - 1);\n\t\t\t} else {\n\t\t\t\tstate.shakeOffsetX = 0;\n\t\t\t\tstate.shakeOffsetY = 0;\n\t\t\t\tstate.shakeRotation = 0;\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\treturn bundle as Bundle<CombinedComponentTypes, {}, CameraResourceTypes, {}, {}, 'camera-follow' | 'camera-shake-update' | 'camera-bounds' | 'camera-state-sync', G>;\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "4B3DDFC6FF7BF00364756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
var{defineProperty:H,getOwnPropertyNames:A,getOwnPropertyDescriptor:k}=Object,v=Object.prototype.hasOwnProperty;var K=new WeakMap,f=(z)=>{var J=K.get(z),O;if(J)return J;if(J=H({},"__esModule",{value:!0}),z&&typeof z==="object"||typeof z==="function")A(z).map((Q)=>!v.call(J,Q)&&H(J,Q,{get:()=>z[Q],enumerable:!(O=k(z,Q))||O.enumerable}));return K.set(z,J),J};var u=(z,J)=>{for(var O in J)H(z,O,{get:J[O],enumerable:!0,configurable:!0,set:(Q)=>J[O]=()=>Q})};var h=(z,J)=>()=>(z&&(J=z(z=0)),J);var p=((z)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(z,{get:(J,O)=>(typeof require<"u"?require:J)[O]}):z)(function(z){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+z+'" is not supported')});import{Bundle as B}from"ecspresso";function W(z,J,O,Q,Z,$,N){if(!$&&!N)return null;let F={entityId:z,x:J,y:O,layer:Q,collidesWith:Z};if($)F.x+=$.offsetX??0,F.y+=$.offsetY??0,F.aabb={halfWidth:$.width/2,halfHeight:$.height/2};if(N)F.x+=N.offsetX??0,F.y+=N.offsetY??0,F.circle={radius:N.radius};return F}function q(z){return z("spatialIndex")??null}function C(z,J,O,Q,Z,$,N,F){let j=Z-z,M=$-J,V=O+N-Math.abs(j),E=Q+F-Math.abs(M);if(V<=0||E<=0)return null;if(V<E)return{normalX:j>=0?1:-1,normalY:0,depth:V};return{normalX:0,normalY:M>=0?1:-1,depth:E}}function X(z,J,O,Q,Z,$){let N=Q-z,F=Z-J,j=N*N+F*F,M=O+$;if(j>=M*M)return null;let V=Math.sqrt(j);if(V===0)return{normalX:1,normalY:0,depth:M};return{normalX:N/V,normalY:F/V,depth:M-V}}function T(z,J,O,Q,Z,$,N){let F=Math.max(z-O,Math.min(Z,z+O)),j=Math.max(J-Q,Math.min($,J+Q)),M=Z-F,V=$-j,E=M*M+V*V;if(E>=N*N)return null;if(E===0){let U=Z-(z-O),G=z+O-Z,D=$-(J-Q),P=J+Q-$,R=Math.min(U,G,D,P);if(R===G)return{normalX:1,normalY:0,depth:G+N};if(R===U)return{normalX:-1,normalY:0,depth:U+N};if(R===P)return{normalX:0,normalY:1,depth:P+N};return{normalX:0,normalY:-1,depth:D+N}}let _=Math.sqrt(E);return{normalX:M/_,normalY:V/_,depth:N-_}}function L(z,J){if(z.aabb&&J.aabb)return C(z.x,z.y,z.aabb.halfWidth,z.aabb.halfHeight,J.x,J.y,J.aabb.halfWidth,J.aabb.halfHeight);if(z.circle&&J.circle)return X(z.x,z.y,z.circle.radius,J.x,J.y,J.circle.radius);if(z.aabb&&J.circle)return T(z.x,z.y,z.aabb.halfWidth,z.aabb.halfHeight,J.x,J.y,J.circle.radius);if(z.circle&&J.aabb){let O=T(J.x,J.y,J.aabb.halfWidth,J.aabb.halfHeight,z.x,z.y,z.circle.radius);if(!O)return null;return{normalX:-O.normalX,normalY:-O.normalY,depth:O.depth}}return null}var g=new Set;function S(z,J,O,Q){if(J)m(z,J,O,Q);else Y(z,O,Q)}function Y(z,J,O){for(let Q=0;Q<z.length;Q++){let Z=z[Q];for(let $=Q+1;$<z.length;$++){let N=z[$];if(!Z.collidesWith.includes(N.layer)&&!N.collidesWith.includes(Z.layer))continue;let F=L(Z,N);if(!F)continue;J(Z,N,F,O)}}}function m(z,J,O,Q){let Z=new Map;for(let $=0;$<z.length;$++){let N=z[$];Z.set(N.entityId,N)}for(let $=0;$<z.length;$++){let N=z[$],F=N.aabb?N.aabb.halfWidth:N.circle?N.circle.radius:0,j=N.aabb?N.aabb.halfHeight:N.circle?N.circle.radius:0;g.clear(),J.queryRectInto(N.x-F,N.y-j,N.x+F,N.y+j,g);for(let M of g){if(M<=N.entityId)continue;let V=Z.get(M);if(!V)continue;if(!N.collidesWith.includes(V.layer)&&!V.collidesWith.includes(N.layer))continue;let E=L(N,V);if(!E)continue;O(N,V,E,Q)}}}function s(z,J,O,Q){let Z={width:z,height:J};if(O!==void 0)Z.offsetX=O;if(Q!==void 0)Z.offsetY=Q;return{aabbCollider:Z}}function r(z,J,O){let Q={radius:z};if(J!==void 0)Q.offsetX=J;if(O!==void 0)Q.offsetY=O;return{circleCollider:Q}}function w(z,J){return{collisionLayer:{layer:z,collidesWith:J}}}function l(z){let J={};for(let O of Object.keys(z)){let Q=z[O];J[O]=()=>w(O,Q)}return J}function I(z){let J=z.indexOf(":");if(J===-1)throw Error(`Invalid collision pair key "${z}": must contain a colon separator (e.g. "player:enemy")`);let O=z.slice(0,J),Q=z.slice(J+1);if(O===""||Q==="")throw Error(`Invalid collision pair key "${z}": layer names must not be empty`);return[O,Q]}function o(z){let J=new Map,O=new Set;for(let Q of Object.keys(z))I(Q),O.add(Q);for(let Q of Object.keys(z)){let[Z,$]=I(Q),N=z[Q];if(!N)continue;J.set(Q,{callback:N,swapped:!1});let F=`${$}:${Z}`;if(F!==Q&&!O.has(F))J.set(F,{callback:N,swapped:!0})}return function(Z,$){let N=J.get(Z.layerA+":"+Z.layerB);if(!N)return;if(N.swapped)N.callback(Z.entityB,Z.entityA,$);else N.callback(Z.entityA,Z.entityB,$)}}function x(z,J,O,Q){Q.publish("collision",{entityA:z.entityId,entityB:J.entityId,layerA:z.layer,layerB:J.layer,normal:{x:O.normalX,y:O.normalY},depth:O.depth})}function c(z){let{systemGroup:J="physics",priority:O=0,phase:Q="postUpdate"}=z,Z=new B("collision");return Z.addSystem("collision-detection").setPriority(O).inPhase(Q).inGroup(J).addQuery("collidables",{with:["worldTransform","collisionLayer"]}).setProcess(($,N,F)=>{let j=[];for(let V of $.collidables){let{worldTransform:E,collisionLayer:_}=V.components,U=W(V.id,E.x,E.y,_.layer,_.collidesWith,F.entityManager.getComponent(V.id,"aabbCollider"),F.entityManager.getComponent(V.id,"circleCollider"));if(U)j.push(U)}let M=q(F.tryGetResource.bind(F));S(j,M,x,F.eventBus)}).and(),Z}export{l as defineCollisionLayers,o as createCollisionPairHandler,w as createCollisionLayer,c as createCollisionBundle,r as createCircleCollider,s as createAABBCollider};
|
|
2
|
-
|
|
3
|
-
//# debugId=7E185AB0A21012C364756E2164756E21
|
|
4
|
-
//# sourceMappingURL=collision.js.map
|