canvasengine 2.0.0-beta.6 → 2.0.0-beta.61

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 (164) hide show
  1. package/dist/DebugRenderer-DkjTAc48.js +1384 -0
  2. package/dist/DebugRenderer-DkjTAc48.js.map +1 -0
  3. package/dist/components/Button.d.ts +185 -0
  4. package/dist/components/Button.d.ts.map +1 -0
  5. package/dist/components/Canvas.d.ts +17 -0
  6. package/dist/components/Canvas.d.ts.map +1 -0
  7. package/dist/components/DOMElement.d.ts +54 -0
  8. package/dist/components/DOMElement.d.ts.map +1 -0
  9. package/dist/components/DOMSprite.d.ts +127 -0
  10. package/dist/components/DOMSprite.d.ts.map +1 -0
  11. package/dist/components/FocusContainer.d.ts +129 -0
  12. package/dist/components/FocusContainer.d.ts.map +1 -0
  13. package/dist/components/Graphic.d.ts +64 -0
  14. package/dist/components/Graphic.d.ts.map +1 -0
  15. package/dist/components/Joystick.d.ts +36 -0
  16. package/dist/components/Joystick.d.ts.map +1 -0
  17. package/dist/components/NineSliceSprite.d.ts +16 -0
  18. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  19. package/dist/components/ParticleEmitter.d.ts +4 -0
  20. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  21. package/dist/components/Scene.d.ts +2 -0
  22. package/dist/components/Scene.d.ts.map +1 -0
  23. package/dist/components/Text.d.ts +24 -0
  24. package/dist/components/Text.d.ts.map +1 -0
  25. package/dist/components/TilingSprite.d.ts +17 -0
  26. package/dist/components/TilingSprite.d.ts.map +1 -0
  27. package/dist/components/Video.d.ts +14 -0
  28. package/dist/components/Video.d.ts.map +1 -0
  29. package/dist/components/index.d.ts +20 -0
  30. package/dist/components/index.d.ts.map +1 -0
  31. package/dist/components/types/DisplayObject.d.ts +118 -0
  32. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  33. package/dist/components/types/MouseEvent.d.ts +4 -0
  34. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  35. package/dist/components/types/Spritesheet.d.ts +248 -0
  36. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  37. package/dist/components/types/index.d.ts +4 -0
  38. package/dist/components/types/index.d.ts.map +1 -0
  39. package/dist/directives/Controls.d.ts +112 -0
  40. package/dist/directives/Controls.d.ts.map +1 -0
  41. package/dist/directives/ControlsBase.d.ts +199 -0
  42. package/dist/directives/ControlsBase.d.ts.map +1 -0
  43. package/dist/directives/Drag.d.ts +69 -0
  44. package/dist/directives/Drag.d.ts.map +1 -0
  45. package/dist/directives/Flash.d.ts +116 -0
  46. package/dist/directives/Flash.d.ts.map +1 -0
  47. package/dist/directives/FocusNavigation.d.ts +52 -0
  48. package/dist/directives/FocusNavigation.d.ts.map +1 -0
  49. package/dist/directives/FogVisibility.d.ts +47 -0
  50. package/dist/directives/FogVisibility.d.ts.map +1 -0
  51. package/dist/directives/GamepadControls.d.ts +224 -0
  52. package/dist/directives/GamepadControls.d.ts.map +1 -0
  53. package/dist/directives/JoystickControls.d.ts +171 -0
  54. package/dist/directives/JoystickControls.d.ts.map +1 -0
  55. package/dist/directives/KeyboardControls.d.ts +219 -0
  56. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  57. package/dist/directives/Scheduler.d.ts +36 -0
  58. package/dist/directives/Scheduler.d.ts.map +1 -0
  59. package/dist/directives/Shake.d.ts +98 -0
  60. package/dist/directives/Shake.d.ts.map +1 -0
  61. package/dist/directives/Sound.d.ts +25 -0
  62. package/dist/directives/Sound.d.ts.map +1 -0
  63. package/dist/directives/Transition.d.ts +10 -0
  64. package/dist/directives/Transition.d.ts.map +1 -0
  65. package/dist/directives/ViewportCull.d.ts +11 -0
  66. package/dist/directives/ViewportCull.d.ts.map +1 -0
  67. package/dist/directives/ViewportFollow.d.ts +18 -0
  68. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  69. package/dist/directives/index.d.ts +14 -0
  70. package/dist/directives/index.d.ts.map +1 -0
  71. package/dist/dist-BOOc43Qm.js +778 -0
  72. package/dist/dist-BOOc43Qm.js.map +1 -0
  73. package/dist/engine/FocusManager.d.ts +174 -0
  74. package/dist/engine/FocusManager.d.ts.map +1 -0
  75. package/dist/engine/animation.d.ts +72 -0
  76. package/dist/engine/animation.d.ts.map +1 -0
  77. package/dist/engine/bootstrap.d.ts +52 -0
  78. package/dist/engine/bootstrap.d.ts.map +1 -0
  79. package/dist/engine/directive.d.ts +13 -0
  80. package/dist/engine/directive.d.ts.map +1 -0
  81. package/dist/engine/reactive.d.ts +135 -0
  82. package/dist/engine/reactive.d.ts.map +1 -0
  83. package/dist/engine/signal.d.ts +73 -0
  84. package/dist/engine/signal.d.ts.map +1 -0
  85. package/dist/engine/trigger.d.ts +54 -0
  86. package/dist/engine/trigger.d.ts.map +1 -0
  87. package/dist/engine/utils.d.ts +89 -0
  88. package/dist/engine/utils.d.ts.map +1 -0
  89. package/dist/hooks/addContext.d.ts +2 -0
  90. package/dist/hooks/addContext.d.ts.map +1 -0
  91. package/dist/hooks/useFocus.d.ts +60 -0
  92. package/dist/hooks/useFocus.d.ts.map +1 -0
  93. package/dist/hooks/useProps.d.ts +42 -0
  94. package/dist/hooks/useProps.d.ts.map +1 -0
  95. package/dist/hooks/useRef.d.ts +4 -0
  96. package/dist/hooks/useRef.d.ts.map +1 -0
  97. package/dist/index.d.ts +19 -1107
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.global.js +8 -0
  100. package/dist/index.global.js.map +1 -0
  101. package/dist/index.js +14708 -3135
  102. package/dist/index.js.map +1 -1
  103. package/dist/utils/Ease.d.ts +17 -0
  104. package/dist/utils/Ease.d.ts.map +1 -0
  105. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  106. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  107. package/dist/utils/RadialGradient.d.ts +57 -0
  108. package/dist/utils/RadialGradient.d.ts.map +1 -0
  109. package/dist/utils/functions.d.ts +2 -0
  110. package/dist/utils/functions.d.ts.map +1 -0
  111. package/dist/utils/tabindex.d.ts +16 -0
  112. package/dist/utils/tabindex.d.ts.map +1 -0
  113. package/package.json +16 -9
  114. package/src/components/Button.ts +399 -0
  115. package/src/components/Canvas.ts +82 -51
  116. package/src/components/Container.ts +21 -2
  117. package/src/components/DOMContainer.ts +379 -0
  118. package/src/components/DOMElement.ts +556 -0
  119. package/src/components/DOMSprite.ts +1040 -0
  120. package/src/components/DisplayObject.ts +422 -201
  121. package/src/components/FocusContainer.ts +368 -0
  122. package/src/components/Graphic.ts +239 -73
  123. package/src/components/Joystick.ts +363 -0
  124. package/src/components/Mesh.ts +222 -0
  125. package/src/components/NineSliceSprite.ts +4 -1
  126. package/src/components/ParticleEmitter.ts +12 -8
  127. package/src/components/Sprite.ts +418 -52
  128. package/src/components/Text.ts +270 -26
  129. package/src/components/Viewport.ts +122 -63
  130. package/src/components/index.ts +9 -2
  131. package/src/components/types/DisplayObject.ts +53 -5
  132. package/src/components/types/Spritesheet.ts +0 -118
  133. package/src/directives/Controls.ts +254 -0
  134. package/src/directives/ControlsBase.ts +267 -0
  135. package/src/directives/Drag.ts +357 -52
  136. package/src/directives/Flash.ts +419 -0
  137. package/src/directives/FocusNavigation.ts +113 -0
  138. package/src/directives/FogVisibility.ts +273 -0
  139. package/src/directives/GamepadControls.ts +537 -0
  140. package/src/directives/JoystickControls.ts +396 -0
  141. package/src/directives/KeyboardControls.ts +85 -430
  142. package/src/directives/Scheduler.ts +21 -5
  143. package/src/directives/Shake.ts +298 -0
  144. package/src/directives/Sound.ts +94 -31
  145. package/src/directives/ViewportFollow.ts +40 -9
  146. package/src/directives/index.ts +13 -6
  147. package/src/engine/FocusManager.ts +510 -0
  148. package/src/engine/animation.ts +175 -21
  149. package/src/engine/bootstrap.ts +140 -6
  150. package/src/engine/directive.ts +4 -4
  151. package/src/engine/reactive.ts +980 -177
  152. package/src/engine/signal.ts +241 -47
  153. package/src/engine/trigger.ts +34 -7
  154. package/src/engine/utils.ts +19 -3
  155. package/src/hooks/useFocus.ts +91 -0
  156. package/src/hooks/useProps.ts +1 -1
  157. package/src/index.ts +8 -2
  158. package/src/types/pixi-cull.d.ts +7 -0
  159. package/src/utils/GlobalAssetLoader.ts +257 -0
  160. package/src/utils/functions.ts +7 -0
  161. package/src/utils/tabindex.ts +70 -0
  162. package/testing/index.ts +35 -4
  163. package/tsconfig.json +18 -0
  164. package/vite.config.ts +39 -0
@@ -1,5 +1,7 @@
1
- import { computed, effect, isSignal, Signal, WritableSignal } from "@signe/reactive";
1
+ import { Howl } from 'howler';
2
+ import { computed, effect, isSignal, Signal } from "@signe/reactive";
2
3
  import {
4
+ Application,
3
5
  Assets,
4
6
  Container,
5
7
  Sprite as PixiSprite,
@@ -10,7 +12,9 @@ import { Subscription } from "rxjs";
10
12
  import {
11
13
  Element,
12
14
  createComponent,
15
+ isElement,
13
16
  registerComponent,
17
+ isElementFrozen,
14
18
  } from "../engine/reactive";
15
19
  import { arrayEquals, isFunction } from "../engine/utils";
16
20
  import { DisplayObject } from "./DisplayObject";
@@ -24,6 +28,8 @@ import {
24
28
  import { ComponentFunction } from "../engine/signal";
25
29
  import { DisplayObjectProps } from "./types/DisplayObject";
26
30
  import { AnimatedSignal, isAnimatedSignal } from "../engine/animation";
31
+ import { Layout } from '@pixi/layout';
32
+ import { GlobalAssetLoader } from "../utils/GlobalAssetLoader";
27
33
 
28
34
  const log = console.log;
29
35
 
@@ -52,13 +58,21 @@ type AnimationDataFrames = {
52
58
  data: TextureOptionsMerging;
53
59
  };
54
60
 
61
+ export type HitboxAnchorMode = "top-left" | "center" | "foot";
62
+
63
+ export type Hitbox = {
64
+ w: number;
65
+ h: number;
66
+ anchorMode?: HitboxAnchorMode;
67
+ };
68
+
55
69
  export enum StandardAnimation {
56
70
  Stand = "stand",
57
71
  Walk = "walk",
58
72
  }
59
73
 
60
74
  export class CanvasSprite extends DisplayObject(PixiSprite) {
61
- public hitbox: { w: number; h: number };
75
+ public hitbox: Hitbox | null = null;
62
76
  public applyTransform: (
63
77
  frame: FrameOptionsMerging,
64
78
  data: TextureOptionsMerging,
@@ -73,15 +87,127 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
73
87
  private subscriptionSheet: Subscription[] = [];
74
88
  private sheetParams: any = {};
75
89
  private sheetCurrentAnimation: string = StandardAnimation.Stand;
90
+ private app: Application | null = null;
76
91
  onFinish: () => void;
92
+ private globalLoader: GlobalAssetLoader | null = null;
93
+ private trackedAssetIds: Set<string> = new Set();
94
+
95
+ get renderer() {
96
+ return this.app?.renderer;
97
+ }
77
98
 
78
99
  private currentAnimationContainer: Container | null = null;
79
100
 
101
+ /**
102
+ * Auto-detects image dimensions by loading the image and reading its natural size
103
+ * This is used when width/height are not explicitly provided in the spritesheet definition
104
+ *
105
+ * @param imagePath - Path to the image file
106
+ * @returns Object containing the detected width and height of the image
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const { width, height } = await sprite.detectImageDimensions('path/to/image.png');
111
+ * // width: 256, height: 128
112
+ * ```
113
+ */
114
+ private async detectImageDimensions(imagePath: string): Promise<{ width: number; height: number }> {
115
+ if (!imagePath || typeof imagePath !== 'string' || imagePath.trim() === '') {
116
+ throw new Error(`Invalid image path provided to detectImageDimensions: ${imagePath}`);
117
+ }
118
+
119
+ // Register asset in global loader if available
120
+ let assetId: string | null = null;
121
+ if (this.globalLoader) {
122
+ assetId = this.globalLoader.registerAsset(imagePath);
123
+ this.trackedAssetIds.add(assetId);
124
+ }
125
+
126
+ const texture = await Assets.load(imagePath, (progress) => {
127
+ if (this.globalLoader && assetId) {
128
+ this.globalLoader.updateProgress(assetId, progress);
129
+ }
130
+ });
131
+
132
+ // Mark as complete
133
+ if (this.globalLoader && assetId) {
134
+ this.globalLoader.completeAsset(assetId);
135
+ }
136
+
137
+ return {
138
+ width: texture.width,
139
+ height: texture.height,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Creates textures from a spritesheet image by cutting it into frames
145
+ * Automatically detects image dimensions if width/height are not provided
146
+ *
147
+ * @param options - Texture options containing image path, dimensions, and frame configuration
148
+ * @returns A 2D array of textures organized by rows and columns
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * // With explicit dimensions
153
+ * const textures = await sprite.createTextures({
154
+ * image: 'path/to/image.png',
155
+ * width: 256,
156
+ * height: 128,
157
+ * framesWidth: 4,
158
+ * framesHeight: 2,
159
+ * spriteWidth: 64,
160
+ * spriteHeight: 64
161
+ * });
162
+ *
163
+ * // Without dimensions (automatically detected)
164
+ * const textures = await sprite.createTextures({
165
+ * image: 'path/to/image.png',
166
+ * framesWidth: 4,
167
+ * framesHeight: 2,
168
+ * spriteWidth: 64,
169
+ * spriteHeight: 64
170
+ * });
171
+ * ```
172
+ */
80
173
  private async createTextures(
81
174
  options: Required<TextureOptionsMerging>
82
175
  ): Promise<Texture[][]> {
83
- const { width, height, framesHeight, framesWidth, image, offset } = options;
84
- const texture = await Assets.load(image);
176
+ let { width, height, framesHeight, framesWidth, image, offset } = options;
177
+
178
+ if (!image || typeof image !== 'string' || image.trim() === '') {
179
+ console.warn('Invalid image path provided to createTextures:', image);
180
+ return [];
181
+ }
182
+
183
+ // Register asset in global loader if available
184
+ let assetId: string | null = null;
185
+ if (this.globalLoader) {
186
+ assetId = this.globalLoader.registerAsset(image);
187
+ this.trackedAssetIds.add(assetId);
188
+ }
189
+
190
+ const texture = await Assets.load(image, (progress) => {
191
+ if (this.globalLoader && assetId) {
192
+ this.globalLoader.updateProgress(assetId, progress);
193
+ }
194
+ });
195
+
196
+ // Mark as complete
197
+ if (this.globalLoader && assetId) {
198
+ this.globalLoader.completeAsset(assetId);
199
+ }
200
+
201
+ // Auto-detect width and height from the image if not provided
202
+ if (!width || width <= 0) {
203
+ width = texture.width;
204
+ options.width = width;
205
+ }
206
+ if (!height || height <= 0) {
207
+ height = texture.height;
208
+ options.height = height;
209
+ }
210
+
85
211
  const spriteWidth = options.spriteWidth;
86
212
  const spriteHeight = options.spriteHeight;
87
213
  const frames: Texture[][] = [];
@@ -140,12 +266,30 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
140
266
  } as any;
141
267
  const {
142
268
  rectWidth,
143
- width = 0,
269
+ width: widthOption = 0,
144
270
  framesWidth = 1,
145
271
  rectHeight,
146
- height = 0,
272
+ height: heightOption = 0,
147
273
  framesHeight = 1,
274
+ image,
148
275
  } = optionsTextures;
276
+
277
+ // Auto-detect width and height from the image if not provided
278
+ let width = widthOption;
279
+ let height = heightOption;
280
+
281
+ if (image && ((!width || width <= 0) || (!height || height <= 0))) {
282
+ const dimensions = await this.detectImageDimensions(image);
283
+ if (!width || width <= 0) {
284
+ width = dimensions.width;
285
+ optionsTextures.width = width;
286
+ }
287
+ if (!height || height <= 0) {
288
+ height = dimensions.height;
289
+ optionsTextures.height = height;
290
+ }
291
+ }
292
+
149
293
  optionsTextures.spriteWidth = rectWidth ? rectWidth : width / framesWidth;
150
294
  optionsTextures.spriteHeight = rectHeight
151
295
  ? rectHeight
@@ -163,20 +307,37 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
163
307
  }
164
308
  }
165
309
 
166
- async onMount(params: Element<CanvasSprite>) {
310
+ async onMount(params: Element<any>) {
311
+ // Set #element manually for freeze checking before calling super.onMount
312
+ // We need to set it early so update() can check freeze state
313
+ (this as any)['#element'] = params;
314
+
167
315
  const { props, propObservables } = params;
168
316
  const tick: Signal = props.context.tick;
169
317
  const sheet = props.sheet ?? {};
318
+ const definition = props.sheet?.definition ?? {};
319
+ this.app = props.context.app();
320
+ // Get global loader from context if available
321
+ this.globalLoader = props.context?.globalLoader || null;
170
322
  if (sheet?.onFinish) {
171
323
  this.onFinish = sheet.onFinish;
172
324
  }
173
325
  this.subscriptionTick = tick.observable.subscribe((value) => {
326
+ if (this.destroyed) return
174
327
  this.update(value);
175
328
  });
176
- if (props.sheet?.definition) {
177
- this.spritesheet = props.sheet.definition;
329
+ if (definition) {
330
+ const resolvedDefinition = definition instanceof Promise ? await definition : definition;
331
+ this.spritesheet = resolvedDefinition.value ?? resolvedDefinition;
178
332
  await this.createAnimations();
179
333
  }
334
+ if (sheet?.params) {
335
+ this.sheetParams = sheet.params;
336
+ }
337
+ if (sheet?.playing && this.has(sheet.playing)) {
338
+ this.sheetCurrentAnimation = sheet.playing;
339
+ this.play(this.sheetCurrentAnimation, [this.sheetParams]);
340
+ }
180
341
  if (sheet.params) {
181
342
  for (let key in propObservables?.sheet["params"]) {
182
343
  const value = propObservables?.sheet["params"][key] as Signal;
@@ -184,11 +345,13 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
184
345
  this.subscriptionSheet.push(
185
346
  value.observable.subscribe((value) => {
186
347
  if (this.animations.size == 0) return;
187
- this.play(this.sheetCurrentAnimation, [{ [key]: value }]);
348
+ if (!this.has(this.sheetCurrentAnimation)) return;
349
+ this.play(this.sheetCurrentAnimation, [{ ...this.sheetParams, [key]: value }]);
188
350
  })
189
351
  );
190
352
  } else {
191
- this.play(this.sheetCurrentAnimation, [{ [key]: value }]);
353
+ if (!this.has(this.sheetCurrentAnimation)) continue;
354
+ this.play(this.sheetCurrentAnimation, [{ ...this.sheetParams, [key]: value }]);
192
355
  }
193
356
  }
194
357
  }
@@ -212,26 +375,50 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
212
375
 
213
376
  if (!this.isMounted) return;
214
377
 
215
- if (_isMoving) {
216
- this.sheetCurrentAnimation = StandardAnimation.Walk;
217
- } else {
218
- this.sheetCurrentAnimation = StandardAnimation.Stand;
219
- }
378
+ const animationName = this.getMovementAnimationName(_isMoving);
379
+ if (!animationName) return;
220
380
 
221
- this.play(this.sheetCurrentAnimation, [this.sheetParams]);
381
+ this.sheetCurrentAnimation = animationName;
382
+ if (this.spritesheet) this.play(this.sheetCurrentAnimation, [this.sheetParams]);
222
383
  });
223
-
224
384
  super.onMount(params);
225
385
  }
226
386
 
227
387
  async onUpdate(props) {
388
+ if (this.destroyed) return
228
389
  super.onUpdate(props);
229
390
 
391
+ // Initialize globalLoader from context if not already set
392
+ if (!this.globalLoader && props.context?.globalLoader) {
393
+ this.globalLoader = props.context.globalLoader;
394
+ }
395
+
230
396
  const setTexture = async (image: string) => {
397
+ if (!image || typeof image !== 'string' || image.trim() === '') {
398
+ console.warn('Invalid image path provided to setTexture:', image);
399
+ return null;
400
+ }
401
+
402
+ // Register asset in global loader if available
403
+ let assetId: string | null = null;
404
+ if (this.globalLoader) {
405
+ assetId = this.globalLoader.registerAsset(image);
406
+ this.trackedAssetIds.add(assetId);
407
+ }
408
+
231
409
  const onProgress = this.fullProps.loader?.onProgress;
232
410
  const texture = await Assets.load(image, (progress) => {
411
+ // Update global loader progress
412
+ if (this.globalLoader && assetId) {
413
+ this.globalLoader.updateProgress(assetId, progress);
414
+ }
415
+ // Call local loader callback if provided
233
416
  if (onProgress) onProgress(progress);
234
417
  if (progress == 1) {
418
+ // Mark as complete in global loader
419
+ if (this.globalLoader && assetId) {
420
+ this.globalLoader.completeAsset(assetId);
421
+ }
235
422
  const onComplete = this.fullProps.loader?.onComplete;
236
423
  if (onComplete) {
237
424
  // hack to memoize the texture
@@ -245,41 +432,79 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
245
432
  return texture
246
433
  }
247
434
 
248
- const sheet = props.sheet;
435
+ const sheet = props.sheet
436
+ const definition = props.sheet?.definition ?? {};
437
+
438
+ if (definition?.type === 'reset') {
439
+ const resolvedValue = definition.value instanceof Promise ? await definition.value : definition.value;
440
+ this.spritesheet = resolvedValue ?? definition;
441
+ await this.resetAnimations();
442
+ }
443
+
249
444
  if (sheet?.params) this.sheetParams = sheet?.params;
250
445
 
251
- if (sheet?.playing && this.isMounted) {
446
+ if (sheet?.playing && this.isMounted && this.spritesheet && this.animations.size > 0) {
252
447
  this.sheetCurrentAnimation = sheet?.playing;
253
448
  this.play(this.sheetCurrentAnimation, [this.sheetParams]);
254
449
  }
255
450
 
256
- if (props.hitbox) this.hitbox = props.hitbox;
451
+ if (props.hitbox !== undefined) {
452
+ this.hitbox = this.normalizeHitbox(props.hitbox);
453
+ }
257
454
 
258
455
  if (props.scaleMode) this.baseTexture.scaleMode = props.scaleMode;
259
456
  else if (props.image && this.fullProps.rectangle === undefined) {
260
- this.texture = await setTexture(this.fullProps.image);
457
+ const texture = await setTexture(this.fullProps.image);
458
+ if (texture) {
459
+ this.texture = texture;
460
+ }
261
461
  } else if (props.texture) {
262
- this.texture = props.texture;
462
+ if (isElement(props.texture)) {
463
+ const textureInstance = props.texture.componentInstance;
464
+ textureInstance.subjectInit
465
+ .subscribe()
466
+ this.texture = this.renderer?.generateTexture(props.texture.componentInstance);
467
+ } else {
468
+ this.texture = props.texture;
469
+ }
263
470
  }
264
471
  if (props.rectangle !== undefined) {
265
472
  const { x, y, width, height } = props.rectangle?.value ?? props.rectangle;
266
473
  const texture = await setTexture(this.fullProps.image);
267
- this.texture = new Texture({
268
- source: texture.source,
269
- frame: new Rectangle(x, y, width, height),
270
- });
474
+ if (texture) {
475
+ this.texture = new Texture({
476
+ source: texture.source,
477
+ frame: new Rectangle(x, y, width, height),
478
+ });
479
+ }
271
480
  }
272
- }
273
481
 
274
- onDestroy(): void {
275
- super.onDestroy();
276
- this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
277
- this.subscriptionTick.unsubscribe();
278
- if (this.currentAnimationContainer && this.parent instanceof Container) {
279
- this.parent.removeChild(this.currentAnimationContainer);
482
+ if (this.hitbox && !this.spritesheet) {
483
+ this.applyHitboxAnchor(this.texture.width, this.texture.height);
280
484
  }
281
485
  }
282
486
 
487
+ async onDestroy(parent: Element, afterDestroy: () => void): Promise<void> {
488
+ const _afterDestroy = async () => {
489
+ // Clean up tracked assets from global loader
490
+ if (this.globalLoader) {
491
+ this.trackedAssetIds.forEach((assetId) => {
492
+ this.globalLoader!.removeAsset(assetId);
493
+ });
494
+ this.trackedAssetIds.clear();
495
+ }
496
+ this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
497
+ this.subscriptionTick.unsubscribe();
498
+ if (this.currentAnimationContainer && this.parent instanceof Container) {
499
+ this.parent.removeChild(this.currentAnimationContainer);
500
+ }
501
+ if (afterDestroy) {
502
+ afterDestroy();
503
+ }
504
+ };
505
+ await super.onDestroy(parent, _afterDestroy);
506
+ }
507
+
283
508
  has(name: string): boolean {
284
509
  return this.animations.has(name);
285
510
  }
@@ -294,9 +519,37 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
294
519
  return this.currentAnimation.name == name;
295
520
  }
296
521
 
522
+ private getFirstAnimationName(): string | undefined {
523
+ return this.animations.keys().next().value;
524
+ }
525
+
526
+ private getPlayableAnimationName(preferred?: string): string | undefined {
527
+ if (preferred && this.has(preferred)) {
528
+ return preferred;
529
+ }
530
+ return this.getFirstAnimationName();
531
+ }
532
+
533
+ private getMovementAnimationName(isMoving: boolean): string | undefined {
534
+ const standardAnimation = isMoving
535
+ ? StandardAnimation.Walk
536
+ : StandardAnimation.Stand;
537
+
538
+ if (this.has(standardAnimation)) {
539
+ return standardAnimation;
540
+ }
541
+
542
+ if (this.sheetCurrentAnimation && this.has(this.sheetCurrentAnimation)) {
543
+ return this.sheetCurrentAnimation;
544
+ }
545
+
546
+ if (this.currentAnimation?.name && this.has(this.currentAnimation.name)) {
547
+ return this.currentAnimation.name;
548
+ }
549
+ }
550
+
297
551
  stop() {
298
552
  this.currentAnimation = null;
299
- this.destroy();
300
553
  }
301
554
 
302
555
  play(name: string, params: any[] = []) {
@@ -338,7 +591,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
338
591
  const sound = this.currentAnimation.data.sound;
339
592
 
340
593
  if (sound) {
341
- //RpgSound.get(sound).play()
594
+ new Howl({
595
+ src: sound,
596
+ autoplay: true,
597
+ loop: false,
598
+ volume: 1,
599
+ })
342
600
  }
343
601
 
344
602
  // Updates immediately to avoid flickering
@@ -347,7 +605,55 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
347
605
  });
348
606
  }
349
607
 
608
+ /**
609
+ * Resets the sprite by destroying and recreating all animations
610
+ * This method clears the current animation state, destroys existing textures,
611
+ * and recreates all animations from the spritesheet
612
+ *
613
+ * @example
614
+ * ```typescript
615
+ * // Reset all animations to their initial state
616
+ * sprite.resetAnimations();
617
+ *
618
+ * // Reset and then play a specific animation
619
+ * await sprite.resetAnimations();
620
+ * sprite.play('walk');
621
+ * ```
622
+ */
623
+ async resetAnimations(): Promise<void> {
624
+ // Stop current animation
625
+ this.stop();
626
+
627
+ // Clear all animations and textures
628
+ this.animations.clear();
629
+
630
+ // Reset animation state
631
+ this.currentAnimation = null;
632
+ this.currentAnimationContainer = null;
633
+ this.time = 0;
634
+ this.frameIndex = 0;
635
+
636
+ // Clear children
637
+ this.removeChildren();
638
+
639
+ // Recreate animations from spritesheet
640
+ if (this.spritesheet) {
641
+ await this.createAnimations();
642
+ const animationName = this.getPlayableAnimationName(this.sheetCurrentAnimation);
643
+ if (animationName) {
644
+ this.sheetCurrentAnimation = animationName;
645
+ this.play(animationName, [this.sheetParams]);
646
+ }
647
+ }
648
+ }
649
+
350
650
  update({ deltaRatio }) {
651
+ // Block animation update if element is frozen
652
+ const element = this.getElement();
653
+ if (element && isElementFrozen(element)) {
654
+ return;
655
+ }
656
+
351
657
  if (
352
658
  !this.isPlaying() ||
353
659
  !this.currentAnimation ||
@@ -408,26 +714,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
408
714
  }
409
715
 
410
716
  const realSize = getVal<"spriteRealSize">("spriteRealSize");
411
- const heightOfSprite =
412
- typeof realSize == "number" ? realSize : realSize?.height;
413
- const widthOfSprite =
414
- typeof realSize == "number" ? realSize : realSize?.width;
415
-
416
- const applyAnchorBySize = () => {
417
- if (heightOfSprite && this.hitbox) {
418
- const { spriteWidth, spriteHeight } = data;
419
- const w = (spriteWidth - this.hitbox.w) / 2 / spriteWidth;
420
- const gap = (spriteHeight - heightOfSprite) / 2;
421
- const h = (spriteHeight - this.hitbox.h - gap) / spriteHeight;
422
- this.anchor.set(w, h);
423
- }
424
- };
425
717
 
426
718
  if (frame.sound) {
427
719
  //RpgSound.get(frame.sound).play()
428
720
  }
429
721
 
430
- applyAnchorBySize();
722
+ this.applyHitboxAnchor(data.spriteWidth, data.spriteHeight, realSize);
431
723
 
432
724
  applyTransform("anchor");
433
725
  applyTransform("scale");
@@ -455,9 +747,80 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
455
747
  this.frameIndex++;
456
748
  }
457
749
  }
750
+
751
+ private applyHitboxAnchor(
752
+ spriteWidth: number,
753
+ spriteHeight: number,
754
+ realSize?: TextureOptionsMerging["spriteRealSize"]
755
+ ) {
756
+ if (!this.hitbox || !spriteWidth || !spriteHeight) {
757
+ return;
758
+ }
759
+
760
+ const heightOfSprite =
761
+ typeof realSize === "number" ? realSize : realSize?.height;
762
+ const resolvedHeight = heightOfSprite ?? spriteHeight;
763
+ const gap = Math.max(0, (spriteHeight - resolvedHeight) / 2);
764
+ const hitboxTopLeftX = this.clamp(
765
+ (spriteWidth - this.hitbox.w) / 2 / spriteWidth
766
+ );
767
+ const hitboxTopLeftY = this.clamp(
768
+ (spriteHeight - this.hitbox.h - gap) / spriteHeight
769
+ );
770
+ const hitboxCenterX = this.clamp(
771
+ hitboxTopLeftX + this.hitbox.w / 2 / spriteWidth
772
+ );
773
+ const hitboxCenterY = this.clamp(
774
+ hitboxTopLeftY + this.hitbox.h / 2 / spriteHeight
775
+ );
776
+ const footY = this.clamp((spriteHeight - gap) / spriteHeight);
777
+
778
+ let anchorX = hitboxTopLeftX;
779
+ let anchorY = hitboxTopLeftY;
780
+
781
+ switch (this.hitbox.anchorMode ?? "top-left") {
782
+ case "center":
783
+ anchorX = hitboxCenterX;
784
+ anchorY = hitboxCenterY;
785
+ break;
786
+ case "foot":
787
+ anchorX = hitboxCenterX;
788
+ anchorY = footY;
789
+ break;
790
+ case "top-left":
791
+ default:
792
+ break;
793
+ }
794
+
795
+ this.anchor.set(anchorX, anchorY);
796
+ }
797
+
798
+ private normalizeHitbox(hitbox: unknown): Hitbox | null {
799
+ const resolvedHitbox = (hitbox as any)?.value ?? hitbox;
800
+ if (!resolvedHitbox || typeof resolvedHitbox !== "object") {
801
+ return null;
802
+ }
803
+
804
+ const { w, h, anchorMode } = resolvedHitbox as Partial<Hitbox>;
805
+ if (typeof w !== "number" || typeof h !== "number") {
806
+ return null;
807
+ }
808
+
809
+ return {
810
+ w,
811
+ h,
812
+ anchorMode,
813
+ };
814
+ }
815
+
816
+ private clamp(value: number) {
817
+ return Math.min(1, Math.max(0, value));
818
+ }
458
819
  }
459
820
 
460
- export interface CanvasSprite extends PixiSprite {}
821
+ export interface CanvasSprite extends PixiSprite {
822
+ layout: Layout | null;
823
+ }
461
824
 
462
825
  registerComponent("Sprite", CanvasSprite);
463
826
 
@@ -469,6 +832,7 @@ export interface SpriteProps extends DisplayObjectProps {
469
832
  params?: any;
470
833
  onFinish?: () => void;
471
834
  };
835
+ hitbox?: Hitbox;
472
836
  scaleMode?: number;
473
837
  image?: string;
474
838
  rectangle?: {
@@ -510,5 +874,7 @@ export type SpritePropTypes = SpritePropsWithImage | SpritePropsWithSheet;
510
874
 
511
875
  // Update the Sprite function to use the props interface
512
876
  export const Sprite: ComponentFunction<SpritePropTypes> = (props) => {
877
+ // Ensure component is registered in test environments where module cache may differ
878
+ registerComponent("Sprite", CanvasSprite);
513
879
  return createComponent("Sprite", props);
514
880
  };