canvasengine 2.0.0-beta.37 → 2.0.0-beta.38

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { h as a } from "./index-BqwprEPH.js";
2
- import { A as r, X as o, Y as n, q as l, v as c, r as p, C as S, d as u, U as m, W as g, ai as d, f as b, D as C, ah as h, af as T, w as j, j as w, G as D, t as f, c as v, I as E, K as O, M as k, Q as y, O as P, P as V, ag as x, R as A, z as B, S as G, g as H, e as M, B as N, y as R, J as q, L as F, T as I, x as K, b as U, H as z, N as J, V as L, ae as Q, ad as W, ab as X, k as Y, a2 as Z, a0 as _, a3 as $, l as aa, a7 as sa, ac as ea, m as ta, n as ia, Z as ra, o as oa, i as na, _ as la, p as ca, a8 as pa, a1 as Sa, a5 as ua, a4 as ma, aa as ga, $ as da, s as ba, a6 as Ca, a9 as ha, a as Ta, u as ja } from "./index-BqwprEPH.js";
1
+ import { h as a } from "./index-BgNWflRE.js";
2
+ import { A as r, X as o, Y as n, q as l, v as c, r as p, C as S, d as u, U as m, W as g, ai as d, f as b, D as C, ah as h, af as T, w as j, j as w, G as D, t as f, c as v, I as E, K as O, M as k, Q as y, O as P, P as V, ag as x, R as A, z as B, S as G, g as H, e as M, B as N, y as R, J as q, L as F, T as I, x as K, b as U, H as z, N as J, V as L, ae as Q, ad as W, ab as X, k as Y, a2 as Z, a0 as _, a3 as $, l as aa, a7 as sa, ac as ea, m as ta, n as ia, Z as ra, o as oa, i as na, _ as la, p as ca, a8 as pa, a1 as Sa, a5 as ua, a4 as ma, aa as ga, $ as da, s as ba, a6 as Ca, a9 as ha, a as Ta, u as ja } from "./index-BgNWflRE.js";
3
3
  const e = a.Howler;
4
4
  export {
5
5
  r as ArraySubject,
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Global Asset Loader
3
+ *
4
+ * Tracks the loading progress of all assets (images, spritesheets, etc.) across all sprites in a component tree.
5
+ * This allows components to know when all assets are loaded, useful for displaying loaders or progress bars.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const loader = new GlobalAssetLoader();
10
+ *
11
+ * loader.onProgress((progress) => {
12
+ * console.log(`Loading: ${(progress * 100).toFixed(0)}%`);
13
+ * });
14
+ *
15
+ * loader.onComplete(() => {
16
+ * console.log('All assets loaded!');
17
+ * });
18
+ *
19
+ * // Register assets as they start loading
20
+ * const assetId = loader.registerAsset('path/to/image.png');
21
+ *
22
+ * // Update progress
23
+ * loader.updateProgress(assetId, 0.5);
24
+ *
25
+ * // Mark as complete
26
+ * loader.completeAsset(assetId);
27
+ * ```
28
+ */
29
+ export declare class GlobalAssetLoader {
30
+ private assets;
31
+ private onProgressCallbacks;
32
+ private onCompleteCallbacks;
33
+ private assetCounter;
34
+ private isComplete;
35
+ /**
36
+ * Registers a new asset to track
37
+ *
38
+ * @param assetPath - The path or identifier of the asset being loaded
39
+ * @returns A unique ID for this asset that should be used for progress updates
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const assetId = loader.registerAsset('path/to/image.png');
44
+ * ```
45
+ */
46
+ registerAsset(assetPath: string): string;
47
+ /**
48
+ * Updates the progress of a specific asset
49
+ *
50
+ * @param assetId - The ID returned by registerAsset
51
+ * @param progress - Progress value between 0 and 1
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * loader.updateProgress(assetId, 0.5); // 50% loaded
56
+ * ```
57
+ */
58
+ updateProgress(assetId: string, progress: number): void;
59
+ /**
60
+ * Marks an asset as completely loaded
61
+ *
62
+ * @param assetId - The ID returned by registerAsset
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * loader.completeAsset(assetId);
67
+ * ```
68
+ */
69
+ completeAsset(assetId: string): void;
70
+ /**
71
+ * Removes an asset from tracking (useful for cleanup)
72
+ *
73
+ * @param assetId - The ID returned by registerAsset
74
+ */
75
+ removeAsset(assetId: string): void;
76
+ /**
77
+ * Registers a callback that will be called whenever the global progress changes
78
+ *
79
+ * @param callback - Function that receives the global progress (0-1)
80
+ * @returns A function to unregister the callback
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const unsubscribe = loader.onProgress((progress) => {
85
+ * console.log(`Loading: ${(progress * 100).toFixed(0)}%`);
86
+ * });
87
+ *
88
+ * // Later, to unsubscribe:
89
+ * unsubscribe();
90
+ * ```
91
+ */
92
+ onProgress(callback: (progress: number) => void): () => void;
93
+ /**
94
+ * Registers a callback that will be called when all assets are loaded
95
+ *
96
+ * @param callback - Function to call when all assets are complete
97
+ * @returns A function to unregister the callback
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const unsubscribe = loader.onComplete(() => {
102
+ * console.log('All assets loaded!');
103
+ * });
104
+ *
105
+ * // Later, to unsubscribe:
106
+ * unsubscribe();
107
+ * ```
108
+ */
109
+ onComplete(callback: () => void): () => void;
110
+ /**
111
+ * Gets the current global progress (0-1)
112
+ *
113
+ * @returns Progress value between 0 and 1
114
+ */
115
+ getGlobalProgress(): number;
116
+ /**
117
+ * Gets the number of assets currently being tracked
118
+ *
119
+ * @returns Number of registered assets
120
+ */
121
+ getAssetCount(): number;
122
+ /**
123
+ * Gets the number of completed assets
124
+ *
125
+ * @returns Number of completed assets
126
+ */
127
+ getCompletedCount(): number;
128
+ /**
129
+ * Checks if all assets are loaded and triggers onComplete callbacks
130
+ */
131
+ private checkCompletion;
132
+ /**
133
+ * Updates global progress and notifies all progress callbacks
134
+ */
135
+ private updateGlobalProgress;
136
+ /**
137
+ * Resets the loader, clearing all assets and callbacks
138
+ */
139
+ reset(): void;
140
+ }
141
+ //# sourceMappingURL=GlobalAssetLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GlobalAssetLoader.d.ts","sourceRoot":"","sources":["../../src/utils/GlobalAssetLoader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAoE;IAClF,OAAO,CAAC,mBAAmB,CAA8C;IACzE,OAAO,CAAC,mBAAmB,CAA8B;IACzD,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,UAAU,CAAkB;IAEpC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAQxC;;;;;;;;;;OAUG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAWvD;;;;;;;;;OASG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAapC;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKlC;;;;;;;;;;;;;;;OAeG;IACH,UAAU,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAe5D;;;;;;;;;;;;;;;OAeG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAa5C;;;;OAIG;IACH,iBAAiB,IAAI,MAAM;IAa3B;;;;OAIG;IACH,aAAa,IAAI,MAAM;IAIvB;;;;OAIG;IACH,iBAAiB,IAAI,MAAM;IAQ3B;;OAEG;IACH,OAAO,CAAC,eAAe;IAiBvB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;OAEG;IACH,KAAK,IAAI,IAAI;CAOd"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasengine",
3
- "version": "2.0.0-beta.37",
3
+ "version": "2.0.0-beta.38",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,6 +12,7 @@ import { ComponentFunction } from "../engine/signal";
12
12
  import { SignalOrPrimitive } from "./types";
13
13
  import { Size } from "./types/DisplayObject";
14
14
  import { Scheduler, Tick } from "../directives/Scheduler";
15
+ import { GlobalAssetLoader } from "../utils/GlobalAssetLoader";
15
16
 
16
17
  interface CanvasElement extends Element<ComponentInstance> {
17
18
  render: (rootElement: HTMLElement, app?: Application) => void;
@@ -49,11 +50,13 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
49
50
  });
50
51
 
51
52
  props.isRoot = true;
53
+ const globalLoader = new GlobalAssetLoader();
52
54
  const options: CanvasProps = {
53
55
  ...props,
54
56
  context: {
55
57
  canvasSize,
56
58
  app: signal(null),
59
+ globalLoader,
57
60
  },
58
61
  width: width?.(),
59
62
  height: height?.(),
@@ -28,6 +28,7 @@ import { ComponentFunction } from "../engine/signal";
28
28
  import { DisplayObjectProps } from "./types/DisplayObject";
29
29
  import { AnimatedSignal, isAnimatedSignal } from "../engine/animation";
30
30
  import { Layout } from '@pixi/layout';
31
+ import { GlobalAssetLoader } from "../utils/GlobalAssetLoader";
31
32
 
32
33
  const log = console.log;
33
34
 
@@ -79,6 +80,8 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
79
80
  private sheetCurrentAnimation: string = StandardAnimation.Stand;
80
81
  private app: Application | null = null;
81
82
  onFinish: () => void;
83
+ private globalLoader: GlobalAssetLoader | null = null;
84
+ private trackedAssetIds: Set<string> = new Set();
82
85
 
83
86
  get renderer() {
84
87
  return this.app?.renderer;
@@ -103,8 +106,25 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
103
106
  if (!imagePath || typeof imagePath !== 'string' || imagePath.trim() === '') {
104
107
  throw new Error(`Invalid image path provided to detectImageDimensions: ${imagePath}`);
105
108
  }
106
-
107
- const texture = await Assets.load(imagePath);
109
+
110
+ // Register asset in global loader if available
111
+ let assetId: string | null = null;
112
+ if (this.globalLoader) {
113
+ assetId = this.globalLoader.registerAsset(imagePath);
114
+ this.trackedAssetIds.add(assetId);
115
+ }
116
+
117
+ const texture = await Assets.load(imagePath, (progress) => {
118
+ if (this.globalLoader && assetId) {
119
+ this.globalLoader.updateProgress(assetId, progress);
120
+ }
121
+ });
122
+
123
+ // Mark as complete
124
+ if (this.globalLoader && assetId) {
125
+ this.globalLoader.completeAsset(assetId);
126
+ }
127
+
108
128
  return {
109
129
  width: texture.width,
110
130
  height: texture.height,
@@ -145,14 +165,30 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
145
165
  options: Required<TextureOptionsMerging>
146
166
  ): Promise<Texture[][]> {
147
167
  let { width, height, framesHeight, framesWidth, image, offset } = options;
148
-
168
+
149
169
  if (!image || typeof image !== 'string' || image.trim() === '') {
150
170
  console.warn('Invalid image path provided to createTextures:', image);
151
171
  return [];
152
172
  }
153
-
154
- const texture = await Assets.load(image);
155
-
173
+
174
+ // Register asset in global loader if available
175
+ let assetId: string | null = null;
176
+ if (this.globalLoader) {
177
+ assetId = this.globalLoader.registerAsset(image);
178
+ this.trackedAssetIds.add(assetId);
179
+ }
180
+
181
+ const texture = await Assets.load(image, (progress) => {
182
+ if (this.globalLoader && assetId) {
183
+ this.globalLoader.updateProgress(assetId, progress);
184
+ }
185
+ });
186
+
187
+ // Mark as complete
188
+ if (this.globalLoader && assetId) {
189
+ this.globalLoader.completeAsset(assetId);
190
+ }
191
+
156
192
  // Auto-detect width and height from the image if not provided
157
193
  if (!width || width <= 0) {
158
194
  width = texture.width;
@@ -162,7 +198,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
162
198
  height = texture.height;
163
199
  options.height = height;
164
200
  }
165
-
201
+
166
202
  const spriteWidth = options.spriteWidth;
167
203
  const spriteHeight = options.spriteHeight;
168
204
  const frames: Texture[][] = [];
@@ -228,11 +264,11 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
228
264
  framesHeight = 1,
229
265
  image,
230
266
  } = optionsTextures;
231
-
267
+
232
268
  // Auto-detect width and height from the image if not provided
233
269
  let width = widthOption;
234
270
  let height = heightOption;
235
-
271
+
236
272
  if (image && ((!width || width <= 0) || (!height || height <= 0))) {
237
273
  const dimensions = await this.detectImageDimensions(image);
238
274
  if (!width || width <= 0) {
@@ -244,7 +280,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
244
280
  optionsTextures.height = height;
245
281
  }
246
282
  }
247
-
283
+
248
284
  optionsTextures.spriteWidth = rectWidth ? rectWidth : width / framesWidth;
249
285
  optionsTextures.spriteHeight = rectHeight
250
286
  ? rectHeight
@@ -268,6 +304,8 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
268
304
  const sheet = props.sheet ?? {};
269
305
  const definition = props.sheet?.definition ?? {};
270
306
  this.app = props.context.app();
307
+ // Get global loader from context if available
308
+ this.globalLoader = props.context?.globalLoader || null;
271
309
  if (sheet?.onFinish) {
272
310
  this.onFinish = sheet.onFinish;
273
311
  }
@@ -320,7 +358,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
320
358
  this.sheetCurrentAnimation = StandardAnimation.Stand;
321
359
  }
322
360
 
323
- if (this.spritesheet) this.play(this.sheetCurrentAnimation, [this.sheetParams]);
361
+ if (this.spritesheet && this.has(this.sheetCurrentAnimation)) this.play(this.sheetCurrentAnimation, [this.sheetParams]);
324
362
  });
325
363
  super.onMount(params);
326
364
  }
@@ -329,16 +367,37 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
329
367
  if (this.destroyed) return
330
368
  super.onUpdate(props);
331
369
 
370
+ // Initialize globalLoader from context if not already set
371
+ if (!this.globalLoader && props.context?.globalLoader) {
372
+ this.globalLoader = props.context.globalLoader;
373
+ }
374
+
332
375
  const setTexture = async (image: string) => {
333
376
  if (!image || typeof image !== 'string' || image.trim() === '') {
334
377
  console.warn('Invalid image path provided to setTexture:', image);
335
378
  return null;
336
379
  }
337
-
380
+
381
+ // Register asset in global loader if available
382
+ let assetId: string | null = null;
383
+ if (this.globalLoader) {
384
+ assetId = this.globalLoader.registerAsset(image);
385
+ this.trackedAssetIds.add(assetId);
386
+ }
387
+
338
388
  const onProgress = this.fullProps.loader?.onProgress;
339
389
  const texture = await Assets.load(image, (progress) => {
390
+ // Update global loader progress
391
+ if (this.globalLoader && assetId) {
392
+ this.globalLoader.updateProgress(assetId, progress);
393
+ }
394
+ // Call local loader callback if provided
340
395
  if (onProgress) onProgress(progress);
341
396
  if (progress == 1) {
397
+ // Mark as complete in global loader
398
+ if (this.globalLoader && assetId) {
399
+ this.globalLoader.completeAsset(assetId);
400
+ }
342
401
  const onComplete = this.fullProps.loader?.onComplete;
343
402
  if (onComplete) {
344
403
  // hack to memoize the texture
@@ -379,7 +438,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
379
438
  if (isElement(props.texture)) {
380
439
  const textureInstance = props.texture.componentInstance;
381
440
  textureInstance.subjectInit
382
- .subscribe()
441
+ .subscribe()
383
442
  this.texture = this.renderer?.generateTexture(props.texture.componentInstance);
384
443
  } else {
385
444
  this.texture = props.texture;
@@ -399,6 +458,13 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
399
458
 
400
459
  async onDestroy(parent: Element, afterDestroy: () => void): Promise<void> {
401
460
  const _afterDestroy = async () => {
461
+ // Clean up tracked assets from global loader
462
+ if (this.globalLoader) {
463
+ this.trackedAssetIds.forEach((assetId) => {
464
+ this.globalLoader!.removeAsset(assetId);
465
+ });
466
+ this.trackedAssetIds.clear();
467
+ }
402
468
  this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
403
469
  this.subscriptionTick.unsubscribe();
404
470
  if (this.currentAnimationContainer && this.parent instanceof Container) {
@@ -500,16 +566,16 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
500
566
  async resetAnimations(): Promise<void> {
501
567
  // Stop current animation
502
568
  this.stop();
503
-
569
+
504
570
  // Clear all animations and textures
505
571
  this.animations.clear();
506
-
572
+
507
573
  // Reset animation state
508
574
  this.currentAnimation = null;
509
575
  this.currentAnimationContainer = null;
510
576
  this.time = 0;
511
577
  this.frameIndex = 0;
512
-
578
+
513
579
  // Clear children
514
580
  this.removeChildren();
515
581
 
@@ -208,124 +208,6 @@ export interface TexturesOptions extends TextureOptions, TransformOptions {
208
208
  }
209
209
 
210
210
  export interface SpritesheetOptions extends TransformOptions, TextureOptions {
211
- /**
212
- * Object containing all animations.
213
- * The key to the object is the name of the animation. The value is a two-dimensional array
214
- *
215
- * ```ts
216
- * textures: {
217
- * myanim: {
218
- * animations: [
219
- * [ { time: 0, frameX: 0, frameY: 0 } ]
220
- * ]
221
- * }
222
- * }
223
- * ```
224
- *
225
- * The first array represents an animation group. You can put several of them together to create an animation cluster. For example, several explosions with the same spritesheet
226
- * The second array represents the animation itself which will animate over time. The object indicates, over a period of time (in frame), which part of the spritesheet will be taken (`frameX`, `frameY`)
227
- *
228
- * Here are the properties:
229
- *
230
- * * `time`: Time in frame
231
- * * `frameX`: Retrieve a frame from the spritesheet on the X-axis
232
- * * `frameY`: Retrieve a frame from the spritesheet on the Y-axis
233
- * * `opacity`
234
- * * `pivot`
235
- * * `anchor`
236
- * * `rotation`
237
- * * `angle`
238
- * * `scale`
239
- * * `skew`
240
- * * `x`
241
- * * `y`
242
- * * `visible`
243
- * * `sound`: The sound that will be played during the frame
244
- *
245
- * ---
246
- * **Extract Animation of Spritesheet**
247
- *
248
- * Sometimes the animation is part of the image
249
- *
250
- * ```ts
251
- * textures: {
252
- * myanim: {
253
- * rectWidth: 64,
254
- * rectHeight: 64,
255
- * framesWidth: 10,
256
- * framesHeight: 2,
257
- * offset: {x: 0, y: 230},
258
- * sound: 'my-sound-id', // You can put a sound just for the animation
259
- * animations: [
260
- * [ { time: 0, frameX: 0, frameY: 0 } ]
261
- * ]
262
- * }
263
- * }
264
- * ```
265
- *
266
- * Above, we can specify which part we want to recover
267
- *
268
- * 1. We go to the position {0, 230} of the image (`offset`)
269
- * 2. We recover cells of 64px (`rectWidth` and `rectHeight`)
270
- * 3. And we get 20 cells (10 on the width, 2 on the height) (`frameX` and `frameY`)
271
- *
272
- * ---
273
- *
274
- * **Advanced**
275
- *
276
- * You can create an animation that will be linked to a data. For example, different animation according to a direction of the character.
277
- *
278
- * Full example:
279
- *
280
- * ```ts
281
- * import { Spritesheet, Animation, Direction } from '@rpgjs/client'
282
- *
283
- * @Spritesheet({
284
- * id: 'chest',
285
- * image: require('./assets/chest.png'),
286
- * width: 124,
287
- * height: 61,
288
- * framesHeight: 2,
289
- * framesWidth: 4,
290
- * textures: {
291
- * [Animation.Stand]: {
292
- * animations: direction => [[ {time: 0, frameX: 3, frameY: direction == Direction.Up ? 0 : 1 } ]]
293
- * }
294
- * })
295
- * })
296
- * export class Chest { }
297
- * ```
298
- *
299
- * > It is important to know that `Animation.Stand` animation is called if it exists. it only works in the case of an event that doesn't move. The direction is then sent
300
- *
301
- * As you can see, the property contains a function that returns the array for the animation. Here, it is the direction but the parameters depend on the call of the animation. Example:
302
- *
303
- * ```ts
304
- * import { Spritesheet, Animation, Direction, RpgSprite, ISpriteCharacter } from '@rpgjs/client'
305
- *
306
- * @Spritesheet({
307
- * id: 'chest',
308
- * image: require('./assets/chest.png'),
309
- * width: 124,
310
- * height: 61,
311
- * framesHeight: 2,
312
- * framesWidth: 4,
313
- * textures: {
314
- * [Animation.Stand]: {
315
- * animations: str => [[ {time: 0, frameX: 3, frameY: str == 'hello' ? 0 : 1 } ]]
316
- * }
317
- * }
318
- * })
319
- * export class Chest implements ISpriteCharacter {
320
- * onCharacterStand(sprite: RpgSprite) {
321
- * sprite.animation.play(Animation.Stand, ['hello'])
322
- * }
323
- * }
324
- * ```
325
- *
326
- * @prop { { [animName: string]: { animations: Array<Array<FrameOptions>> | Function, ...other } } } [textures]
327
- * @memberof Spritesheet
328
- * */
329
211
  textures?: {
330
212
  [animationName: string]: Partial<TexturesOptions> & Pick<TexturesOptions, 'animations'>
331
213
  }
@@ -129,13 +129,6 @@ export class GamepadControls extends ControlsBase {
129
129
  clearInterval(this.gamepadMoveInterval);
130
130
  this.gamepadMoveInterval = null;
131
131
  }
132
-
133
- if (this.joypad) {
134
- this.joypad.off('connect');
135
- this.joypad.off('disconnect');
136
- this.joypad.off('button_press');
137
- this.joypad.off('axis_move');
138
- }
139
132
  }
140
133
 
141
134
  /**