canvasengine 2.0.0-beta.4 → 2.0.0-beta.40

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.
Files changed (142) hide show
  1. package/dist/DebugRenderer-DgECR3yZ.js +172 -0
  2. package/dist/DebugRenderer-DgECR3yZ.js.map +1 -0
  3. package/dist/components/Button.d.ts +183 -0
  4. package/dist/components/Button.d.ts.map +1 -0
  5. package/dist/components/Canvas.d.ts +18 -0
  6. package/dist/components/Canvas.d.ts.map +1 -0
  7. package/dist/components/DOMElement.d.ts +44 -0
  8. package/dist/components/DOMElement.d.ts.map +1 -0
  9. package/dist/components/Graphic.d.ts +65 -0
  10. package/dist/components/Graphic.d.ts.map +1 -0
  11. package/dist/components/Joystick.d.ts +36 -0
  12. package/dist/components/Joystick.d.ts.map +1 -0
  13. package/dist/components/NineSliceSprite.d.ts +17 -0
  14. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  15. package/dist/components/ParticleEmitter.d.ts +5 -0
  16. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  17. package/dist/components/Scene.d.ts +2 -0
  18. package/dist/components/Scene.d.ts.map +1 -0
  19. package/dist/components/Text.d.ts +26 -0
  20. package/dist/components/Text.d.ts.map +1 -0
  21. package/dist/components/TilingSprite.d.ts +18 -0
  22. package/dist/components/TilingSprite.d.ts.map +1 -0
  23. package/dist/components/Video.d.ts +15 -0
  24. package/dist/components/Video.d.ts.map +1 -0
  25. package/dist/components/index.d.ts +18 -0
  26. package/dist/components/index.d.ts.map +1 -0
  27. package/dist/components/types/DisplayObject.d.ts +110 -0
  28. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  29. package/dist/components/types/MouseEvent.d.ts +4 -0
  30. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  31. package/dist/components/types/Spritesheet.d.ts +248 -0
  32. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  33. package/dist/components/types/index.d.ts +5 -0
  34. package/dist/components/types/index.d.ts.map +1 -0
  35. package/dist/directives/Controls.d.ts +113 -0
  36. package/dist/directives/Controls.d.ts.map +1 -0
  37. package/dist/directives/ControlsBase.d.ts +198 -0
  38. package/dist/directives/ControlsBase.d.ts.map +1 -0
  39. package/dist/directives/Drag.d.ts +70 -0
  40. package/dist/directives/Drag.d.ts.map +1 -0
  41. package/dist/directives/Flash.d.ts +117 -0
  42. package/dist/directives/Flash.d.ts.map +1 -0
  43. package/dist/directives/GamepadControls.d.ts +225 -0
  44. package/dist/directives/GamepadControls.d.ts.map +1 -0
  45. package/dist/directives/JoystickControls.d.ts +172 -0
  46. package/dist/directives/JoystickControls.d.ts.map +1 -0
  47. package/dist/directives/KeyboardControls.d.ts +219 -0
  48. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  49. package/dist/directives/Scheduler.d.ts +36 -0
  50. package/dist/directives/Scheduler.d.ts.map +1 -0
  51. package/dist/directives/Shake.d.ts +98 -0
  52. package/dist/directives/Shake.d.ts.map +1 -0
  53. package/dist/directives/Sound.d.ts +26 -0
  54. package/dist/directives/Sound.d.ts.map +1 -0
  55. package/dist/directives/Transition.d.ts +11 -0
  56. package/dist/directives/Transition.d.ts.map +1 -0
  57. package/dist/directives/ViewportCull.d.ts +12 -0
  58. package/dist/directives/ViewportCull.d.ts.map +1 -0
  59. package/dist/directives/ViewportFollow.d.ts +19 -0
  60. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  61. package/dist/directives/index.d.ts +13 -0
  62. package/dist/directives/index.d.ts.map +1 -0
  63. package/dist/engine/animation.d.ts +73 -0
  64. package/dist/engine/animation.d.ts.map +1 -0
  65. package/dist/engine/bootstrap.d.ts +16 -0
  66. package/dist/engine/bootstrap.d.ts.map +1 -0
  67. package/dist/engine/directive.d.ts +14 -0
  68. package/dist/engine/directive.d.ts.map +1 -0
  69. package/dist/engine/reactive.d.ts +105 -0
  70. package/dist/engine/reactive.d.ts.map +1 -0
  71. package/dist/engine/signal.d.ts +72 -0
  72. package/dist/engine/signal.d.ts.map +1 -0
  73. package/dist/engine/trigger.d.ts +50 -0
  74. package/dist/engine/trigger.d.ts.map +1 -0
  75. package/dist/engine/utils.d.ts +90 -0
  76. package/dist/engine/utils.d.ts.map +1 -0
  77. package/dist/hooks/addContext.d.ts +2 -0
  78. package/dist/hooks/addContext.d.ts.map +1 -0
  79. package/dist/hooks/useProps.d.ts +42 -0
  80. package/dist/hooks/useProps.d.ts.map +1 -0
  81. package/dist/hooks/useRef.d.ts +5 -0
  82. package/dist/hooks/useRef.d.ts.map +1 -0
  83. package/dist/index-gb763Hyx.js +12560 -0
  84. package/dist/index-gb763Hyx.js.map +1 -0
  85. package/dist/index.d.ts +15 -1083
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.global.js +29 -0
  88. package/dist/index.global.js.map +1 -0
  89. package/dist/index.js +81 -3041
  90. package/dist/index.js.map +1 -1
  91. package/dist/utils/Ease.d.ts +17 -0
  92. package/dist/utils/Ease.d.ts.map +1 -0
  93. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  94. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  95. package/dist/utils/RadialGradient.d.ts +58 -0
  96. package/dist/utils/RadialGradient.d.ts.map +1 -0
  97. package/dist/utils/functions.d.ts +2 -0
  98. package/dist/utils/functions.d.ts.map +1 -0
  99. package/package.json +13 -7
  100. package/src/components/Button.ts +396 -0
  101. package/src/components/Canvas.ts +61 -45
  102. package/src/components/Container.ts +21 -2
  103. package/src/components/DOMContainer.ts +123 -0
  104. package/src/components/DOMElement.ts +421 -0
  105. package/src/components/DisplayObject.ts +350 -197
  106. package/src/components/Graphic.ts +200 -34
  107. package/src/components/Joystick.ts +361 -0
  108. package/src/components/Mesh.ts +222 -0
  109. package/src/components/NineSliceSprite.ts +4 -1
  110. package/src/components/ParticleEmitter.ts +12 -8
  111. package/src/components/Sprite.ts +306 -30
  112. package/src/components/Text.ts +125 -18
  113. package/src/components/Video.ts +110 -0
  114. package/src/components/Viewport.ts +59 -43
  115. package/src/components/index.ts +8 -2
  116. package/src/components/types/DisplayObject.ts +34 -0
  117. package/src/components/types/Spritesheet.ts +0 -118
  118. package/src/directives/Controls.ts +254 -0
  119. package/src/directives/ControlsBase.ts +266 -0
  120. package/src/directives/Drag.ts +357 -52
  121. package/src/directives/Flash.ts +409 -0
  122. package/src/directives/GamepadControls.ts +537 -0
  123. package/src/directives/JoystickControls.ts +396 -0
  124. package/src/directives/KeyboardControls.ts +66 -424
  125. package/src/directives/Shake.ts +282 -0
  126. package/src/directives/Sound.ts +94 -31
  127. package/src/directives/ViewportFollow.ts +35 -7
  128. package/src/directives/index.ts +12 -6
  129. package/src/engine/animation.ts +175 -21
  130. package/src/engine/bootstrap.ts +23 -3
  131. package/src/engine/directive.ts +2 -2
  132. package/src/engine/reactive.ts +780 -177
  133. package/src/engine/signal.ts +35 -4
  134. package/src/engine/trigger.ts +21 -4
  135. package/src/engine/utils.ts +19 -3
  136. package/src/hooks/useProps.ts +1 -1
  137. package/src/index.ts +4 -2
  138. package/src/utils/GlobalAssetLoader.ts +257 -0
  139. package/src/utils/functions.ts +7 -0
  140. package/testing/index.ts +12 -0
  141. package/tsconfig.json +17 -0
  142. package/vite.config.ts +39 -0
@@ -0,0 +1,282 @@
1
+ import { Container, Point } from 'pixi.js';
2
+ import { Directive, registerDirective } from '../engine/directive';
3
+ import { Element } from '../engine/reactive';
4
+ import { effect } from '@signe/reactive';
5
+ import { on, isTrigger, Trigger } from '../engine/trigger';
6
+ import { useProps } from '../hooks/useProps';
7
+ import { SignalOrPrimitive } from '../components/types';
8
+ import { animatedSignal, AnimatedSignal } from '../engine/animation';
9
+ import { Subscription } from 'rxjs';
10
+
11
+ export type ShakeProps = {
12
+ /**
13
+ * Trigger that activates the shake animation
14
+ * When the trigger is activated, the shake animation will start
15
+ */
16
+ trigger?: Trigger<any>;
17
+ /**
18
+ * Intensity of the shake effect (in pixels)
19
+ * @default 10
20
+ */
21
+ intensity?: SignalOrPrimitive<number>;
22
+ /**
23
+ * Duration of the shake animation in milliseconds
24
+ * @default 500
25
+ */
26
+ duration?: SignalOrPrimitive<number>;
27
+ /**
28
+ * Number of shake oscillations during the animation
29
+ * Higher values create more rapid shaking
30
+ * @default 10
31
+ */
32
+ frequency?: SignalOrPrimitive<number>;
33
+ /**
34
+ * Direction of the shake: 'x', 'y', or 'both'
35
+ * @default 'both'
36
+ */
37
+ direction?: SignalOrPrimitive<'x' | 'y' | 'both'>;
38
+ /**
39
+ * Callback function called when shake starts
40
+ */
41
+ onStart?: () => void;
42
+ /**
43
+ * Callback function called when shake completes
44
+ */
45
+ onComplete?: () => void;
46
+ }
47
+
48
+ /**
49
+ * Shake directive that animates a display object's position when a trigger is activated.
50
+ * Creates a shake effect by rapidly oscillating the x and/or y position.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * // Basic usage with trigger
55
+ * const shakeTrigger = trigger();
56
+ *
57
+ * onMount(element) {
58
+ * // Element will shake when trigger is activated
59
+ * element.props.shake = { trigger: shakeTrigger };
60
+ * }
61
+ *
62
+ * // Trigger the shake
63
+ * shakeTrigger.start();
64
+ * ```
65
+ */
66
+ export class Shake extends Directive {
67
+ private elementRef: Element<Container> | null = null;
68
+ private originalPosition: Point = new Point();
69
+ private progressSignal: AnimatedSignal<number> | null = null;
70
+ private shakeSubscription: any = null;
71
+ private positionEffect: Subscription | null = null;
72
+ private currentShakeConfig: {
73
+ intensity: number;
74
+ frequency: number;
75
+ direction: 'x' | 'y' | 'both';
76
+ randomSeed: number;
77
+ } | null = null;
78
+
79
+ /**
80
+ * Initializes the shake directive
81
+ * @param element - The element to attach the shake effect to
82
+ */
83
+ onInit(element: Element<Container>) {
84
+ this.elementRef = element;
85
+ }
86
+
87
+ /**
88
+ * Mounts the shake directive and sets up trigger listener
89
+ * @param element - The element being mounted
90
+ */
91
+ onMount(element: Element<Container>) {
92
+ const instance = element.componentInstance;
93
+ if (!instance) return;
94
+
95
+ const shakeProps = this.shakeProps;
96
+
97
+ // Check if trigger is provided
98
+ if (!shakeProps.trigger || !isTrigger(shakeProps.trigger)) {
99
+ return;
100
+ }
101
+
102
+ // Store original position
103
+ this.originalPosition.set(instance.position.x, instance.position.y);
104
+
105
+ // Listen to trigger activation
106
+ this.shakeSubscription = on(shakeProps.trigger, async (data) => {
107
+ await this.performShake(data);
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Gets the shake props with default values
113
+ * @returns ShakeProps with defaults applied
114
+ */
115
+ get shakeProps(): ShakeProps {
116
+ const shake = this.elementRef?.props.shake;
117
+ return useProps(shake?.value ?? shake, {
118
+ intensity: 10,
119
+ duration: 500,
120
+ frequency: 10,
121
+ direction: 'both',
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Performs the shake animation using animatedSignal
127
+ * @param data - Optional data passed from the trigger that can override default options
128
+ */
129
+ private async performShake(data?: any): Promise<void> {
130
+ if (!this.elementRef?.componentInstance) return;
131
+
132
+ const instance = this.elementRef.componentInstance;
133
+ const shakeProps = this.shakeProps;
134
+
135
+ // Use data from trigger to override defaults if provided
136
+ const intensity = data?.intensity ?? shakeProps.intensity();
137
+ const duration = data?.duration ?? shakeProps.duration();
138
+ const frequency = data?.frequency ?? shakeProps.frequency();
139
+ const direction = data?.direction ?? shakeProps.direction();
140
+
141
+ // Stop any existing animation and clean up
142
+ if (this.positionEffect) {
143
+ this.positionEffect.unsubscribe();
144
+ this.positionEffect = null;
145
+ }
146
+
147
+ // Reset position to original before starting new shake
148
+ this.originalPosition.set(instance.position.x, instance.position.y);
149
+ instance.position.x = this.originalPosition.x;
150
+ instance.position.y = this.originalPosition.y;
151
+
152
+ // Call onStart callback
153
+ shakeProps.onStart?.();
154
+
155
+ // Store current shake configuration for use in effect
156
+ this.currentShakeConfig = {
157
+ intensity,
158
+ frequency,
159
+ direction,
160
+ randomSeed: Math.random() * 1000, // Fixed random seed for this shake
161
+ };
162
+
163
+ // Create or recreate progress signal for shake animation
164
+ // We recreate it to ensure a fresh animation state
165
+ if (this.progressSignal) {
166
+ // Reset to 0 immediately without animation
167
+ this.progressSignal.set(0, { duration: 0 });
168
+ // Wait a bit to ensure the reset is complete
169
+ await new Promise(resolve => setTimeout(resolve, 0));
170
+ } else {
171
+ this.progressSignal = animatedSignal(0, {
172
+ duration: duration,
173
+ ease: (t) => t, // Linear ease, we'll handle oscillation in effect
174
+ });
175
+ }
176
+
177
+ const shakeX = direction === 'y' ? false : true;
178
+ const shakeY = direction === 'x' ? false : true;
179
+
180
+ // Create effect to update position based on progress
181
+ this.positionEffect = effect(() => {
182
+ if (!instance || !this.progressSignal || !this.currentShakeConfig) return;
183
+
184
+ const progress = this.progressSignal();
185
+ const config = this.currentShakeConfig;
186
+
187
+ // Calculate decay factor (shake intensity decreases over time)
188
+ const decay = 1 - progress;
189
+
190
+ // Generate oscillation based on progress and frequency
191
+ // progress goes from 0 to 1, so we multiply by frequency to get oscillations
192
+ const time = progress * config.frequency;
193
+ const oscillation = Math.sin(time * Math.PI * 2);
194
+
195
+ // Apply shake with decay and consistent random variation
196
+ // Use the stored random seed for consistency during this shake
197
+ const randomValue = (Math.sin(config.randomSeed + progress * 10) * 0.25 + 1); // Between 0.75 and 1.25
198
+ const offsetX = shakeX ? oscillation * config.intensity * decay * randomValue : 0;
199
+ const offsetY = shakeY ? oscillation * config.intensity * decay * randomValue : 0;
200
+
201
+ // Update position
202
+ instance.position.x = this.originalPosition.x + offsetX;
203
+ instance.position.y = this.originalPosition.y + offsetY;
204
+ }).subscription;
205
+
206
+ // Start animation and wait for completion
207
+ // Note: animatedSignal.set() replaces onComplete with resolve, so we call onComplete after await
208
+ await this.progressSignal.set(1, {
209
+ duration: duration,
210
+ });
211
+
212
+ // Animation completed - clean up and call callbacks
213
+ // Reset to original position
214
+ if (instance) {
215
+ instance.position.x = this.originalPosition.x;
216
+ instance.position.y = this.originalPosition.y;
217
+ }
218
+
219
+ // Clean up position effect
220
+ if (this.positionEffect) {
221
+ this.positionEffect.unsubscribe();
222
+ this.positionEffect = null;
223
+ }
224
+
225
+ // Clear shake config
226
+ this.currentShakeConfig = null;
227
+
228
+ // Call onComplete callback
229
+ shakeProps.onComplete?.();
230
+ }
231
+
232
+ /**
233
+ * Updates the shake directive when props change
234
+ * @param props - Updated props
235
+ */
236
+ onUpdate(props: any) {
237
+ // Re-mount if props change significantly
238
+ if (props.type && props.type === 'reset') {
239
+ this.onDestroy();
240
+ if (this.elementRef) {
241
+ this.onMount(this.elementRef);
242
+ }
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Cleans up the shake directive
248
+ */
249
+ onDestroy() {
250
+ // Stop any running animation by resetting progress
251
+ if (this.progressSignal) {
252
+ this.progressSignal.set(0, { duration: 0 });
253
+ this.progressSignal = null;
254
+ }
255
+
256
+ // Clean up position effect
257
+ if (this.positionEffect) {
258
+ this.positionEffect.unsubscribe();
259
+ this.positionEffect = null;
260
+ }
261
+
262
+ // Clear shake config
263
+ this.currentShakeConfig = null;
264
+
265
+ // Reset position to original
266
+ if (this.elementRef?.componentInstance) {
267
+ const instance = this.elementRef.componentInstance;
268
+ instance.position.x = this.originalPosition.x;
269
+ instance.position.y = this.originalPosition.y;
270
+ }
271
+
272
+ // Clean up subscription
273
+ if (this.shakeSubscription) {
274
+ this.shakeSubscription = null;
275
+ }
276
+
277
+ this.elementRef = null;
278
+ }
279
+ }
280
+
281
+ registerDirective('shake', Shake);
282
+
@@ -8,8 +8,18 @@ import { calculateDistance, error } from '../engine/utils';
8
8
 
9
9
  const EVENTS = ['load', 'loaderror', 'playerror', 'play', 'end', 'pause', 'stop', 'mute', 'volume', 'rate', 'seek', 'fade', 'unlock']
10
10
 
11
+ /**
12
+ * Sound directive for playing audio with support for spatial audio and multiple sound sources
13
+ *
14
+ * This directive manages audio playback using Howler.js library. It supports:
15
+ * - Single or multiple sound sources
16
+ * - Spatial audio with distance-based volume calculation
17
+ * - All standard audio controls (play, pause, volume, etc.)
18
+ * - Event handling for audio lifecycle
19
+ *
20
+ */
11
21
  export class Sound extends Directive {
12
- private sound: Howl
22
+ private sounds: Howl[] = []
13
23
  private eventsFn: ((...args: any[]) => void)[] = []
14
24
  private maxVolume: number = 1
15
25
  private maxDistance: number = 100
@@ -20,21 +30,42 @@ export class Sound extends Directive {
20
30
  onMount(element: Element<Container>) {
21
31
  const { props } = element
22
32
  const tick = props.context.tick
23
- const { src, autoplay, loop, volume, spatial } = props.sound
24
- this.sound = new Howl({
25
- src,
26
- autoplay,
27
- loop,
28
- volume
29
- })
30
- for (let event of EVENTS) {
31
- if (!props.sound[event]) continue
32
- const fn = props.sound[event]
33
- this.eventsFn.push(fn)
34
- this.sound.on(event, fn);
33
+ const propsSound = props.sound.value ?? props.sound
34
+
35
+ // Check if src is null or undefined
36
+ if (!propsSound.src) {
37
+ return
35
38
  }
36
39
 
37
- if (spatial) {
40
+ const { src, autoplay, loop, volume, spatial } = propsSound
41
+
42
+ // Handle multiple sources
43
+ const sources = Array.isArray(src) ? src : [src]
44
+
45
+ // Create Howl instances for each source
46
+ for (const source of sources) {
47
+ if (!source) continue // Skip null/undefined sources
48
+
49
+ const sound = new Howl({
50
+ src: source,
51
+ autoplay,
52
+ loop,
53
+ volume
54
+ })
55
+
56
+ // Add event listeners for each sound
57
+ for (let event of EVENTS) {
58
+ if (!propsSound[event]) continue
59
+ const fn = propsSound[event]
60
+ this.eventsFn.push(fn)
61
+ sound.on(event, fn);
62
+ }
63
+
64
+ this.sounds.push(sound)
65
+ }
66
+
67
+ // Setup spatial audio if enabled
68
+ if (spatial && this.sounds.length > 0) {
38
69
  const { soundListenerPosition } = props.context
39
70
  if (!soundListenerPosition) {
40
71
  throw new error('SoundListenerPosition directive is required for spatial sound in component parent')
@@ -45,39 +76,71 @@ export class Sound extends Directive {
45
76
  const { x, y } = element.componentInstance
46
77
  const distance = calculateDistance(x, y, listenerX(), listenerY());
47
78
  const volume = Math.max(this.maxVolume - (distance / this.maxDistance), 0)
48
- this.sound.volume(volume)
79
+
80
+ // Apply volume to all sounds
81
+ this.sounds.forEach(sound => sound.volume(volume))
49
82
  }).subscription
50
83
  }
84
+
85
+ this.onUpdate(propsSound)
51
86
  }
52
87
 
53
88
  onUpdate(props: any) {
54
- const { volume, loop, mute, seek, playing, rate, spatial } = props
55
- if (volume != undefined) this.sound.volume(volume)
56
- if (loop != undefined) this.sound.loop(loop)
57
- if (mute != undefined) this.sound.mute(mute)
58
- if (seek != undefined) this.sound.seek(seek)
59
- if (playing != undefined) {
60
- if (playing) this.sound.play()
61
- else this.sound.pause()
62
- }
89
+ const soundProps = props.value ?? props
90
+ const { volume, loop, mute, seek, playing, rate, spatial } = soundProps
91
+ // Apply updates to all sounds
92
+ this.sounds.forEach(sound => {
93
+ if (volume !== undefined) sound.volume(volume)
94
+ if (loop !== undefined) sound.loop(loop)
95
+ if (mute !== undefined) sound.mute(mute)
96
+ if (seek !== undefined) sound.seek(seek)
97
+ if (playing !== undefined) {
98
+ if (playing) sound.play()
99
+ else sound.pause()
100
+ }
101
+ if (rate !== undefined) sound.rate(rate)
102
+ })
103
+
104
+ // Update spatial audio settings
63
105
  if (spatial) {
64
106
  this.maxVolume = spatial.maxVolume ?? this.maxVolume
65
107
  this.maxDistance = spatial.maxDistance ?? this.maxDistance
66
108
  }
67
- if (rate != undefined) this.sound.rate(rate)
68
109
  }
69
110
 
70
111
  onDestroy() {
71
- this.sound.stop()
72
- this.tickSubscription?.unsubscribe()
73
- for (let event of EVENTS) {
74
- if (this.eventsFn[event]) {
75
- this.sound.off(event, this.eventsFn[event]);
112
+ // Stop and clean up all sounds
113
+ this.sounds.forEach(sound => {
114
+ sound.stop()
115
+
116
+ // Remove event listeners
117
+ for (let event of EVENTS) {
118
+ const eventFn = this.eventsFn.find(fn => fn === this.eventsFn[event])
119
+ if (eventFn) {
120
+ sound.off(event, eventFn);
121
+ }
76
122
  }
77
- }
123
+ })
124
+
125
+ this.sounds = []
126
+ this.eventsFn = []
127
+ this.tickSubscription?.unsubscribe()
78
128
  }
79
129
  }
80
130
 
131
+ /**
132
+ * SoundListenerPosition directive for spatial audio
133
+ *
134
+ * This directive provides the listener position for spatial audio calculations.
135
+ * It should be placed on a parent component that contains spatial sound sources.
136
+ *
137
+ * @example
138
+ * ```tsx
139
+ * <Player soundListenerPosition={{ x: playerX, y: playerY }}>
140
+ * <Enemy sound={{ src: 'growl.mp3', spatial: { maxDistance: 100 } }} />
141
+ * </Player>
142
+ * ```
143
+ */
81
144
  class SoundListenerPosition extends Directive {
82
145
  onMount(element: Element<any>) {
83
146
  element.props.context.soundListenerPosition = element.propObservables?.soundListenerPosition
@@ -1,25 +1,53 @@
1
1
  import { ComponentInstance } from '../components/DisplayObject';
2
+ import { SignalOrPrimitive } from '../components/types';
2
3
  import { Directive, registerDirective } from '../engine/directive';
3
4
  import { Element } from '../engine/reactive';
4
5
  import { error } from '../engine/utils';
6
+ import { useProps } from '../hooks/useProps';
7
+
8
+ export type ViewportFollowProps = {
9
+ viewportFollow?: boolean | {
10
+ speed?: SignalOrPrimitive<number>;
11
+ acceleration?: SignalOrPrimitive<number>;
12
+ radius?: SignalOrPrimitive<number>;
13
+ };
14
+ }
5
15
 
6
16
  export class ViewportFollow extends Directive {
7
17
  onInit(element: Element<ComponentInstance>) {
8
18
 
9
19
  }
10
20
  onMount(element: Element) {
11
- const { viewportFollow } = element.props
21
+ this.onUpdate(element.props.viewportFollow, element)
22
+ }
23
+ onUpdate(viewportFollow: any, element: Element) {
12
24
  const { viewport } = element.props.context
13
25
  if (!viewport) {
14
26
  throw error('ViewportFollow directive requires a Viewport component to be mounted in the same context')
15
27
  }
16
- viewport.follow(element.componentInstance)
17
- }
18
- onUpdate(props: any) {
19
-
28
+ if (viewportFollow) {
29
+ if (viewportFollow === true) {
30
+ viewport.follow(element.componentInstance)
31
+ } else {
32
+ const options = useProps(viewportFollow, {
33
+ speed: undefined,
34
+ acceleration: undefined,
35
+ radius: undefined
36
+ })
37
+ viewport.follow(element.componentInstance, {
38
+ speed: options.speed(),
39
+ acceleration: options.acceleration(),
40
+ radius: options.radius()
41
+ })
42
+ }
43
+ } else if (viewportFollow === null) {
44
+ viewport.plugins.remove('follow')
45
+ }
20
46
  }
21
- onDestroy() {
22
-
47
+ onDestroy(element: Element) {
48
+ const { viewportFollow } = element.props
49
+ const { viewport } = element.props.context
50
+ if (viewportFollow) viewport.plugins.remove('follow')
23
51
  }
24
52
  }
25
53
 
@@ -1,7 +1,13 @@
1
- import './KeyboardControls'
2
- import './Scheduler'
3
- import './ViewportFollow'
1
+ export * from './ControlsBase'
2
+ export * from './KeyboardControls'
3
+ export * from './GamepadControls'
4
+ export * from './JoystickControls'
5
+ export * from './Controls'
6
+ export * from './Scheduler'
7
+ export * from './ViewportFollow'
4
8
  //import './ViewportCull'
5
- import './Sound'
6
- import './Drag'
7
- import './Transition'
9
+ export * from './Sound'
10
+ export * from './Drag'
11
+ export * from './Transition'
12
+ export * from './Shake'
13
+ export * from './Flash'