canvasengine 2.0.1-beta.1 → 2.0.1

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 (212) 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/Container.d.ts +25 -0
  8. package/dist/components/Container.d.ts.map +1 -0
  9. package/dist/components/DOMContainer.d.ts +28 -0
  10. package/dist/components/DOMContainer.d.ts.map +1 -0
  11. package/dist/components/DOMElement.d.ts +54 -0
  12. package/dist/components/DOMElement.d.ts.map +1 -0
  13. package/dist/components/DOMSprite.d.ts +127 -0
  14. package/dist/components/DOMSprite.d.ts.map +1 -0
  15. package/dist/components/DisplayObject.d.ts +18 -0
  16. package/dist/components/DisplayObject.d.ts.map +1 -0
  17. package/dist/components/FocusContainer.d.ts +129 -0
  18. package/dist/components/FocusContainer.d.ts.map +1 -0
  19. package/dist/components/Graphic.d.ts +64 -0
  20. package/dist/components/Graphic.d.ts.map +1 -0
  21. package/dist/components/Joystick.d.ts +37 -0
  22. package/dist/components/Joystick.d.ts.map +1 -0
  23. package/dist/components/Mesh.d.ts +138 -0
  24. package/dist/components/Mesh.d.ts.map +1 -0
  25. package/dist/components/NineSliceSprite.d.ts +16 -0
  26. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  27. package/dist/components/ParticleEmitter.d.ts +4 -0
  28. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  29. package/dist/components/Scene.d.ts +2 -0
  30. package/dist/components/Scene.d.ts.map +1 -0
  31. package/dist/components/Sprite.d.ts +182 -0
  32. package/dist/components/Sprite.d.ts.map +1 -0
  33. package/dist/components/Text.d.ts +24 -0
  34. package/dist/components/Text.d.ts.map +1 -0
  35. package/dist/components/TilingSprite.d.ts +17 -0
  36. package/dist/components/TilingSprite.d.ts.map +1 -0
  37. package/dist/components/Video.d.ts +14 -0
  38. package/dist/components/Video.d.ts.map +1 -0
  39. package/dist/components/Viewport.d.ts +52 -0
  40. package/dist/components/Viewport.d.ts.map +1 -0
  41. package/dist/components/index.d.ts +20 -0
  42. package/dist/components/index.d.ts.map +1 -0
  43. package/dist/components/types/DisplayObject.d.ts +127 -0
  44. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  45. package/dist/components/types/MouseEvent.d.ts +4 -0
  46. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  47. package/dist/components/types/Spritesheet.d.ts +248 -0
  48. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  49. package/dist/components/types/index.d.ts +4 -0
  50. package/dist/components/types/index.d.ts.map +1 -0
  51. package/dist/directives/Controls.d.ts +112 -0
  52. package/dist/directives/Controls.d.ts.map +1 -0
  53. package/dist/directives/ControlsBase.d.ts +199 -0
  54. package/dist/directives/ControlsBase.d.ts.map +1 -0
  55. package/dist/directives/Drag.d.ts +69 -0
  56. package/dist/directives/Drag.d.ts.map +1 -0
  57. package/dist/directives/Flash.d.ts +116 -0
  58. package/dist/directives/Flash.d.ts.map +1 -0
  59. package/dist/directives/FocusNavigation.d.ts +52 -0
  60. package/dist/directives/FocusNavigation.d.ts.map +1 -0
  61. package/dist/directives/FogVisibility.d.ts +47 -0
  62. package/dist/directives/FogVisibility.d.ts.map +1 -0
  63. package/dist/directives/GamepadControls.d.ts +224 -0
  64. package/dist/directives/GamepadControls.d.ts.map +1 -0
  65. package/dist/directives/JoystickControls.d.ts +171 -0
  66. package/dist/directives/JoystickControls.d.ts.map +1 -0
  67. package/dist/directives/KeyboardControls.d.ts +219 -0
  68. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  69. package/dist/directives/Scheduler.d.ts +36 -0
  70. package/dist/directives/Scheduler.d.ts.map +1 -0
  71. package/dist/directives/Shake.d.ts +98 -0
  72. package/dist/directives/Shake.d.ts.map +1 -0
  73. package/dist/directives/Sound.d.ts +25 -0
  74. package/dist/directives/Sound.d.ts.map +1 -0
  75. package/dist/directives/SpriteEffects.d.ts +70 -0
  76. package/dist/directives/SpriteEffects.d.ts.map +1 -0
  77. package/dist/directives/Transition.d.ts +10 -0
  78. package/dist/directives/Transition.d.ts.map +1 -0
  79. package/dist/directives/ViewportCull.d.ts +11 -0
  80. package/dist/directives/ViewportCull.d.ts.map +1 -0
  81. package/dist/directives/ViewportFollow.d.ts +18 -0
  82. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  83. package/dist/directives/index.d.ts +15 -0
  84. package/dist/directives/index.d.ts.map +1 -0
  85. package/dist/dist-BOOc43Qm.js +778 -0
  86. package/dist/dist-BOOc43Qm.js.map +1 -0
  87. package/dist/engine/FocusManager.d.ts +174 -0
  88. package/dist/engine/FocusManager.d.ts.map +1 -0
  89. package/dist/engine/animation.d.ts +72 -0
  90. package/dist/engine/animation.d.ts.map +1 -0
  91. package/dist/engine/bootstrap.d.ts +52 -0
  92. package/dist/engine/bootstrap.d.ts.map +1 -0
  93. package/dist/engine/directive.d.ts +13 -0
  94. package/dist/engine/directive.d.ts.map +1 -0
  95. package/dist/engine/reactive.d.ts +139 -0
  96. package/dist/engine/reactive.d.ts.map +1 -0
  97. package/dist/engine/signal.d.ts +74 -0
  98. package/dist/engine/signal.d.ts.map +1 -0
  99. package/dist/engine/trigger.d.ts +54 -0
  100. package/dist/engine/trigger.d.ts.map +1 -0
  101. package/dist/engine/utils.d.ts +89 -0
  102. package/dist/engine/utils.d.ts.map +1 -0
  103. package/dist/hooks/addContext.d.ts +2 -0
  104. package/dist/hooks/addContext.d.ts.map +1 -0
  105. package/dist/hooks/useFocus.d.ts +60 -0
  106. package/dist/hooks/useFocus.d.ts.map +1 -0
  107. package/dist/hooks/useProps.d.ts +53 -0
  108. package/dist/hooks/useProps.d.ts.map +1 -0
  109. package/dist/hooks/useRef.d.ts +4 -0
  110. package/dist/hooks/useRef.d.ts.map +1 -0
  111. package/dist/index.d.ts +19 -844
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.global.js +189 -0
  114. package/dist/index.global.js.map +1 -0
  115. package/dist/index.js +15156 -3287
  116. package/dist/index.js.map +1 -1
  117. package/dist/utils/Ease.d.ts +17 -0
  118. package/dist/utils/Ease.d.ts.map +1 -0
  119. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  120. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  121. package/dist/utils/RadialGradient.d.ts +57 -0
  122. package/dist/utils/RadialGradient.d.ts.map +1 -0
  123. package/dist/utils/functions.d.ts +2 -0
  124. package/dist/utils/functions.d.ts.map +1 -0
  125. package/dist/utils/tabindex.d.ts +16 -0
  126. package/dist/utils/tabindex.d.ts.map +1 -0
  127. package/index.d.ts +1 -3
  128. package/package.json +19 -52
  129. package/src/components/Button.ts +406 -0
  130. package/src/components/Canvas.ts +82 -51
  131. package/src/components/Container.ts +31 -4
  132. package/src/components/DOMContainer.ts +387 -0
  133. package/src/components/DOMElement.ts +556 -0
  134. package/src/components/DOMSprite.ts +1044 -0
  135. package/src/components/DisplayObject.ts +423 -202
  136. package/src/components/FocusContainer.ts +368 -0
  137. package/src/components/Graphic.ts +255 -68
  138. package/src/components/Joystick.ts +269 -0
  139. package/src/components/Mesh.ts +239 -0
  140. package/src/components/NineSliceSprite.ts +4 -1
  141. package/src/components/ParticleEmitter.ts +12 -8
  142. package/src/components/Sprite.ts +441 -54
  143. package/src/components/Text.ts +270 -26
  144. package/src/components/Video.ts +110 -0
  145. package/src/components/Viewport.ts +122 -63
  146. package/src/components/index.ts +12 -5
  147. package/src/components/types/DisplayObject.ts +64 -5
  148. package/src/components/types/Spritesheet.ts +0 -118
  149. package/src/directives/Controls.ts +254 -0
  150. package/src/directives/ControlsBase.ts +267 -0
  151. package/src/directives/Drag.ts +357 -52
  152. package/src/directives/Flash.ts +419 -0
  153. package/src/directives/FocusNavigation.ts +113 -0
  154. package/src/directives/FogVisibility.ts +273 -0
  155. package/src/directives/GamepadControls.ts +537 -0
  156. package/src/directives/JoystickControls.ts +396 -0
  157. package/src/directives/KeyboardControls.ts +86 -431
  158. package/src/directives/Scheduler.ts +21 -16
  159. package/src/directives/Shake.ts +298 -0
  160. package/src/directives/Sound.ts +94 -31
  161. package/src/directives/SpriteEffects.ts +461 -0
  162. package/src/directives/ViewportFollow.ts +40 -9
  163. package/src/directives/index.ts +14 -6
  164. package/src/engine/FocusManager.ts +510 -0
  165. package/src/engine/animation.ts +175 -21
  166. package/src/engine/bootstrap.ts +140 -6
  167. package/src/engine/directive.ts +4 -4
  168. package/src/engine/reactive.ts +1266 -167
  169. package/src/engine/signal.ts +335 -47
  170. package/src/engine/trigger.ts +95 -12
  171. package/src/engine/utils.ts +101 -9
  172. package/src/hooks/useFocus.ts +91 -0
  173. package/src/hooks/useProps.ts +92 -7
  174. package/src/index.ts +13 -6
  175. package/src/types/pixi-cull.d.ts +7 -0
  176. package/src/utils/GlobalAssetLoader.ts +257 -0
  177. package/src/utils/RadialGradient.ts +29 -0
  178. package/src/utils/functions.ts +7 -0
  179. package/src/utils/tabindex.ts +70 -0
  180. package/testing/index.ts +42 -0
  181. package/tsconfig.json +15 -25
  182. package/vite.config.ts +39 -0
  183. package/.cursorrules +0 -0
  184. package/.github/workflows/ci.yml +0 -29
  185. package/README.md +0 -79
  186. package/dist/compiler/vite.js +0 -119
  187. package/dist/compiler/vite.js.map +0 -1
  188. package/logo.png +0 -0
  189. package/src/compiler/grammar.pegjs +0 -180
  190. package/src/compiler/vite.ts +0 -166
  191. package/src/components/DrawMap/index.ts +0 -65
  192. package/src/components/Tilemap/Tile.ts +0 -79
  193. package/src/components/Tilemap/TileGroup.ts +0 -207
  194. package/src/components/Tilemap/TileLayer.ts +0 -163
  195. package/src/components/Tilemap/TileSet.ts +0 -41
  196. package/src/components/Tilemap/index.ts +0 -80
  197. package/src/presets/Bar.ts +0 -89
  198. package/src/presets/Button.ts +0 -0
  199. package/src/presets/Joystick.ts +0 -286
  200. package/src/presets/NightAmbiant.ts +0 -122
  201. package/src/presets/Particle.ts +0 -53
  202. package/starter/assets/logo.png +0 -0
  203. package/starter/components/app.ce +0 -18
  204. package/starter/components/hello.ce +0 -35
  205. package/starter/index.html +0 -21
  206. package/starter/main.ts +0 -6
  207. package/starter/package.json +0 -20
  208. package/starter/tsconfig.json +0 -32
  209. package/starter/vite.config.ts +0 -12
  210. package/tsconfig.node.json +0 -10
  211. package/tsup.config.ts +0 -28
  212. package/vitest.config.ts +0 -12
@@ -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,57 +375,136 @@ 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
 
230
- const sheet = props.sheet;
391
+ // Initialize globalLoader from context if not already set
392
+ if (!this.globalLoader && props.context?.globalLoader) {
393
+ this.globalLoader = props.context.globalLoader;
394
+ }
395
+
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
+
409
+ const onProgress = this.fullProps.loader?.onProgress;
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
416
+ if (onProgress) onProgress(progress);
417
+ if (progress == 1) {
418
+ // Mark as complete in global loader
419
+ if (this.globalLoader && assetId) {
420
+ this.globalLoader.completeAsset(assetId);
421
+ }
422
+ const onComplete = this.fullProps.loader?.onComplete;
423
+ if (onComplete) {
424
+ // hack to memoize the texture
425
+ setTimeout(() => {
426
+ onComplete(texture);
427
+ });
428
+ }
429
+ }
430
+ });
431
+
432
+ return texture
433
+ }
434
+
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
+
231
444
  if (sheet?.params) this.sheetParams = sheet?.params;
232
445
 
233
- if (sheet?.playing && this.isMounted) {
446
+ if (sheet?.playing && this.isMounted && this.spritesheet && this.animations.size > 0) {
234
447
  this.sheetCurrentAnimation = sheet?.playing;
235
448
  this.play(this.sheetCurrentAnimation, [this.sheetParams]);
236
449
  }
237
450
 
238
- if (props.hitbox) this.hitbox = props.hitbox;
451
+ if (props.hitbox !== undefined) {
452
+ this.hitbox = this.normalizeHitbox(props.hitbox);
453
+ }
239
454
 
240
455
  if (props.scaleMode) this.baseTexture.scaleMode = props.scaleMode;
241
456
  else if (props.image && this.fullProps.rectangle === undefined) {
242
- this.texture = await Assets.load(this.fullProps.image);
457
+ const texture = await setTexture(this.fullProps.image);
458
+ if (texture) {
459
+ this.texture = texture;
460
+ }
243
461
  } else if (props.texture) {
244
- 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
+ }
245
470
  }
246
-
247
471
  if (props.rectangle !== undefined) {
248
472
  const { x, y, width, height } = props.rectangle?.value ?? props.rectangle;
249
- const texture = await Assets.load(this.fullProps.image);
250
- this.texture = new Texture({
251
- source: texture.source,
252
- frame: new Rectangle(x, y, width, height),
253
- });
473
+ const texture = await setTexture(this.fullProps.image);
474
+ if (texture) {
475
+ this.texture = new Texture({
476
+ source: texture.source,
477
+ frame: new Rectangle(x, y, width, height),
478
+ });
479
+ }
254
480
  }
255
- }
256
481
 
257
- onDestroy(): void {
258
- super.onDestroy();
259
- this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
260
- this.subscriptionTick.unsubscribe();
261
- if (this.currentAnimationContainer && this.parent instanceof Container) {
262
- this.parent.removeChild(this.currentAnimationContainer);
482
+ if (this.hitbox && !this.spritesheet) {
483
+ this.applyHitboxAnchor(this.texture.width, this.texture.height);
263
484
  }
264
485
  }
265
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
+
266
508
  has(name: string): boolean {
267
509
  return this.animations.has(name);
268
510
  }
@@ -277,9 +519,37 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
277
519
  return this.currentAnimation.name == name;
278
520
  }
279
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
+
280
551
  stop() {
281
552
  this.currentAnimation = null;
282
- this.destroy();
283
553
  }
284
554
 
285
555
  play(name: string, params: any[] = []) {
@@ -321,7 +591,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
321
591
  const sound = this.currentAnimation.data.sound;
322
592
 
323
593
  if (sound) {
324
- //RpgSound.get(sound).play()
594
+ new Howl({
595
+ src: sound,
596
+ autoplay: true,
597
+ loop: false,
598
+ volume: 1,
599
+ })
325
600
  }
326
601
 
327
602
  // Updates immediately to avoid flickering
@@ -330,7 +605,55 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
330
605
  });
331
606
  }
332
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
+
333
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
+
334
657
  if (
335
658
  !this.isPlaying() ||
336
659
  !this.currentAnimation ||
@@ -391,26 +714,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
391
714
  }
392
715
 
393
716
  const realSize = getVal<"spriteRealSize">("spriteRealSize");
394
- const heightOfSprite =
395
- typeof realSize == "number" ? realSize : realSize?.height;
396
- const widthOfSprite =
397
- typeof realSize == "number" ? realSize : realSize?.width;
398
-
399
- const applyAnchorBySize = () => {
400
- if (heightOfSprite && this.hitbox) {
401
- const { spriteWidth, spriteHeight } = data;
402
- const w = (spriteWidth - this.hitbox.w) / 2 / spriteWidth;
403
- const gap = (spriteHeight - heightOfSprite) / 2;
404
- const h = (spriteHeight - this.hitbox.h - gap) / spriteHeight;
405
- this.anchor.set(w, h);
406
- }
407
- };
408
717
 
409
718
  if (frame.sound) {
410
719
  //RpgSound.get(frame.sound).play()
411
720
  }
412
721
 
413
- applyAnchorBySize();
722
+ this.applyHitboxAnchor(data.spriteWidth, data.spriteHeight, realSize);
414
723
 
415
724
  applyTransform("anchor");
416
725
  applyTransform("scale");
@@ -438,9 +747,80 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
438
747
  this.frameIndex++;
439
748
  }
440
749
  }
750
+
751
+ private applyHitboxAnchor(
752
+ spriteWidth: number,
753
+ spriteHeight: number,
754
+ realSize?: TextureOptionsMerging["spriteRealSize"]
755
+ ) {
756
+ if (!this.hitbox || !spriteWidth || !spriteHeight || !this.anchor) {
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
+ }
441
819
  }
442
820
 
443
- export interface CanvasSprite extends PixiSprite {}
821
+ export interface CanvasSprite extends PixiSprite {
822
+ layout: Layout | null;
823
+ }
444
824
 
445
825
  registerComponent("Sprite", CanvasSprite);
446
826
 
@@ -452,6 +832,7 @@ export interface SpriteProps extends DisplayObjectProps {
452
832
  params?: any;
453
833
  onFinish?: () => void;
454
834
  };
835
+ hitbox?: Hitbox;
455
836
  scaleMode?: number;
456
837
  image?: string;
457
838
  rectangle?: {
@@ -483,11 +864,17 @@ export interface SpritePropsWithSheet
483
864
  params?: any;
484
865
  onFinish?: () => void;
485
866
  };
867
+ loader?: {
868
+ onProgress?: (progress: number) => void;
869
+ onComplete?: (texture: Texture) => void;
870
+ };
486
871
  }
487
872
 
488
873
  export type SpritePropTypes = SpritePropsWithImage | SpritePropsWithSheet;
489
874
 
490
875
  // Update the Sprite function to use the props interface
491
876
  export const Sprite: ComponentFunction<SpritePropTypes> = (props) => {
877
+ // Ensure component is registered in test environments where module cache may differ
878
+ registerComponent("Sprite", CanvasSprite);
492
879
  return createComponent("Sprite", props);
493
880
  };