canvasengine 2.0.0-beta.37 → 2.0.0-beta.39

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 (45) hide show
  1. package/dist/{DebugRenderer-CTWPthRt.js → DebugRenderer-Rrw9FlTd.js} +2 -2
  2. package/dist/{DebugRenderer-CTWPthRt.js.map → DebugRenderer-Rrw9FlTd.js.map} +1 -1
  3. package/dist/components/Button.d.ts +50 -3
  4. package/dist/components/Button.d.ts.map +1 -1
  5. package/dist/components/Canvas.d.ts.map +1 -1
  6. package/dist/components/Joystick.d.ts +36 -0
  7. package/dist/components/Joystick.d.ts.map +1 -0
  8. package/dist/components/Sprite.d.ts +2 -0
  9. package/dist/components/Sprite.d.ts.map +1 -1
  10. package/dist/components/index.d.ts +1 -0
  11. package/dist/components/index.d.ts.map +1 -1
  12. package/dist/components/types/Spritesheet.d.ts +0 -118
  13. package/dist/components/types/Spritesheet.d.ts.map +1 -1
  14. package/dist/directives/Controls.d.ts +16 -7
  15. package/dist/directives/Controls.d.ts.map +1 -1
  16. package/dist/directives/GamepadControls.d.ts +3 -1
  17. package/dist/directives/GamepadControls.d.ts.map +1 -1
  18. package/dist/directives/JoystickControls.d.ts +172 -0
  19. package/dist/directives/JoystickControls.d.ts.map +1 -0
  20. package/dist/directives/index.d.ts +1 -0
  21. package/dist/directives/index.d.ts.map +1 -1
  22. package/dist/engine/reactive.d.ts.map +1 -1
  23. package/dist/engine/signal.d.ts.map +1 -1
  24. package/dist/{index-BqwprEPH.js → index-BQ99FClW.js} +6057 -5433
  25. package/dist/index-BQ99FClW.js.map +1 -0
  26. package/dist/index.global.js +7 -7
  27. package/dist/index.global.js.map +1 -1
  28. package/dist/index.js +59 -57
  29. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  30. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  31. package/package.json +1 -1
  32. package/src/components/Button.ts +168 -41
  33. package/src/components/Canvas.ts +3 -0
  34. package/src/components/Joystick.ts +361 -0
  35. package/src/components/Sprite.ts +82 -16
  36. package/src/components/index.ts +2 -1
  37. package/src/components/types/Spritesheet.ts +0 -118
  38. package/src/directives/Controls.ts +42 -8
  39. package/src/directives/GamepadControls.ts +40 -18
  40. package/src/directives/JoystickControls.ts +396 -0
  41. package/src/directives/index.ts +1 -0
  42. package/src/engine/reactive.ts +362 -242
  43. package/src/engine/signal.ts +8 -2
  44. package/src/utils/GlobalAssetLoader.ts +257 -0
  45. package/dist/index-BqwprEPH.js.map +0 -1
@@ -0,0 +1,361 @@
1
+ /*
2
+ * Joystick
3
+ *
4
+ * Inspired by https://github.com/endel/pixi-virtual-joystick
5
+ */
6
+
7
+ import * as PIXI from "pixi.js";
8
+ import { Container, Graphics, Sprite, h, signal, isSignal } from "../";
9
+
10
+ export interface JoystickChangeEvent {
11
+ angle: number;
12
+ direction: Direction;
13
+ power: number;
14
+ }
15
+
16
+ export enum Direction {
17
+ LEFT = "left",
18
+ TOP = "top",
19
+ BOTTOM = "bottom",
20
+ RIGHT = "right",
21
+ TOP_LEFT = "top_left",
22
+ TOP_RIGHT = "top_right",
23
+ BOTTOM_LEFT = "bottom_left",
24
+ BOTTOM_RIGHT = "bottom_right",
25
+ }
26
+
27
+ export interface JoystickSettings {
28
+ outer?: string;
29
+ inner?: string;
30
+ outerScale?: { x: number; y: number };
31
+ innerScale?: { x: number; y: number };
32
+ innerColor?: string;
33
+ outerColor?: string;
34
+ onChange?: (data: JoystickChangeEvent) => void;
35
+ onStart?: () => void;
36
+ onEnd?: () => void;
37
+ /** Controls instance to automatically apply joystick events to (e.g., JoystickControls or ControlsDirective) */
38
+ controls?: any;
39
+ }
40
+
41
+ export function Joystick(opts: JoystickSettings = {}) {
42
+ const settings = Object.assign(
43
+ {
44
+ outerScale: { x: 1, y: 1 },
45
+ innerScale: { x: 1, y: 1 },
46
+ innerColor: "black",
47
+ outerColor: "black",
48
+ },
49
+ opts
50
+ );
51
+
52
+ // Unwrap controls if it's a signal
53
+ const getControls = () => {
54
+ if (isSignal(settings.controls)) {
55
+ return settings.controls();
56
+ }
57
+ return settings.controls;
58
+ };
59
+
60
+ let outerRadius = 70;
61
+ let innerRadius = 10;
62
+ const innerAlphaStandby = 0.5;
63
+
64
+ let dragging = false;
65
+ let startPosition: PIXI.PointData | null = null;
66
+ let power = 0;
67
+
68
+ const innerPositionX = signal(0);
69
+ const innerPositionY = signal(0);
70
+ const innerAlpha = signal(innerAlphaStandby);
71
+
72
+ function getPower(centerPoint: PIXI.Point) {
73
+ const a = centerPoint.x - 0;
74
+ const b = centerPoint.y - 0;
75
+ return Math.min(1, Math.sqrt(a * a + b * b) / outerRadius);
76
+ }
77
+
78
+ function getDirection(center: PIXI.Point) {
79
+ let rad = Math.atan2(center.y, center.x); // [-PI, PI]
80
+ if ((rad >= -Math.PI / 8 && rad < 0) || (rad >= 0 && rad < Math.PI / 8)) {
81
+ return Direction.RIGHT;
82
+ } else if (rad >= Math.PI / 8 && rad < (3 * Math.PI) / 8) {
83
+ return Direction.BOTTOM_RIGHT;
84
+ } else if (rad >= (3 * Math.PI) / 8 && rad < (5 * Math.PI) / 8) {
85
+ return Direction.BOTTOM;
86
+ } else if (rad >= (5 * Math.PI) / 8 && rad < (7 * Math.PI) / 8) {
87
+ return Direction.BOTTOM_LEFT;
88
+ } else if (
89
+ (rad >= (7 * Math.PI) / 8 && rad < Math.PI) ||
90
+ (rad >= -Math.PI && rad < (-7 * Math.PI) / 8)
91
+ ) {
92
+ return Direction.LEFT;
93
+ } else if (rad >= (-7 * Math.PI) / 8 && rad < (-5 * Math.PI) / 8) {
94
+ return Direction.TOP_LEFT;
95
+ } else if (rad >= (-5 * Math.PI) / 8 && rad < (-3 * Math.PI) / 8) {
96
+ return Direction.TOP;
97
+ } else {
98
+ return Direction.TOP_RIGHT;
99
+ }
100
+ }
101
+
102
+ function handleDragStart(event: any) {
103
+ startPosition = event.getLocalPosition(this);
104
+ dragging = true;
105
+ innerAlpha.set(1);
106
+ settings.onStart?.();
107
+
108
+ // Notify controls if provided
109
+ const controls = getControls();
110
+ if (controls) {
111
+ // Check if it's JoystickControls instance
112
+ if (controls.handleJoystickStart) {
113
+ controls.handleJoystickStart();
114
+ }
115
+ // Check if it's ControlsDirective with joystick getter
116
+ else if (controls.joystick && controls.joystick.handleJoystickStart) {
117
+ controls.joystick.handleJoystickStart();
118
+ }
119
+ }
120
+ }
121
+
122
+ function handleDragEnd() {
123
+ if (!dragging) return;
124
+ innerPositionX.set(0);
125
+ innerPositionY.set(0);
126
+ dragging = false;
127
+ innerAlpha.set(innerAlphaStandby);
128
+ settings.onEnd?.();
129
+
130
+ // Notify controls if provided
131
+ const controls = getControls();
132
+ if (controls) {
133
+ // Check if it's JoystickControls instance
134
+ if (controls.handleJoystickEnd) {
135
+ controls.handleJoystickEnd();
136
+ }
137
+ // Check if it's ControlsDirective with joystick getter
138
+ else if (controls.joystick && controls.joystick.handleJoystickEnd) {
139
+ controls.joystick.handleJoystickEnd();
140
+ }
141
+ }
142
+ }
143
+
144
+ function handleDragMove(event: any) {
145
+ if (dragging == false) {
146
+ return;
147
+ }
148
+
149
+ let newPosition = event.getLocalPosition(this);
150
+
151
+ let sideX = newPosition.x - (startPosition?.x ?? 0);
152
+ let sideY = newPosition.y - (startPosition?.y ?? 0);
153
+
154
+ let centerPoint = new PIXI.Point(0, 0);
155
+ let angle = 0;
156
+
157
+ if (sideX == 0 && sideY == 0) {
158
+ return;
159
+ }
160
+
161
+ let calRadius = 0;
162
+
163
+ if (sideX * sideX + sideY * sideY >= outerRadius * outerRadius) {
164
+ calRadius = outerRadius;
165
+ } else {
166
+ calRadius = outerRadius - innerRadius;
167
+ }
168
+
169
+ /**
170
+ * x: -1 <-> 1
171
+ * y: -1 <-> 1
172
+ * Y
173
+ * ^
174
+ * |
175
+ * 180 | 90
176
+ * ------------> X
177
+ * 270 | 360
178
+ * |
179
+ * |
180
+ */
181
+
182
+ let direction = Direction.LEFT;
183
+
184
+ if (sideX == 0) {
185
+ if (sideY > 0) {
186
+ centerPoint.set(0, sideY > outerRadius ? outerRadius : sideY);
187
+ angle = 270;
188
+ direction = Direction.BOTTOM;
189
+ } else {
190
+ centerPoint.set(
191
+ 0,
192
+ -(Math.abs(sideY) > outerRadius ? outerRadius : Math.abs(sideY))
193
+ );
194
+ angle = 90;
195
+ direction = Direction.TOP;
196
+ }
197
+ innerPositionX.set(centerPoint.x);
198
+ innerPositionY.set(centerPoint.y);
199
+ power = getPower(centerPoint);
200
+ const changeEvent = { angle, direction, power };
201
+ settings.onChange?.(changeEvent);
202
+
203
+ // Notify controls if provided
204
+ const controls = getControls();
205
+ if (controls) {
206
+ // Check if it's JoystickControls instance
207
+ if (controls.handleJoystickChange) {
208
+ controls.handleJoystickChange(changeEvent);
209
+ }
210
+ // Check if it's ControlsDirective with joystick getter
211
+ else if (controls.joystick && controls.joystick.handleJoystickChange) {
212
+ controls.joystick.handleJoystickChange(changeEvent);
213
+ }
214
+ }
215
+ return;
216
+ }
217
+
218
+ if (sideY == 0) {
219
+ if (sideX > 0) {
220
+ centerPoint.set(
221
+ Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX),
222
+ 0
223
+ );
224
+ angle = 0;
225
+ direction = Direction.RIGHT;
226
+ } else {
227
+ centerPoint.set(
228
+ -(Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX)),
229
+ 0
230
+ );
231
+ angle = 180;
232
+ direction = Direction.LEFT;
233
+ }
234
+
235
+ innerPositionX.set(centerPoint.x);
236
+ innerPositionY.set(centerPoint.y);
237
+ power = getPower(centerPoint);
238
+ const changeEvent = { angle, direction, power };
239
+ settings.onChange?.(changeEvent);
240
+
241
+ // Notify controls if provided
242
+ const controls = getControls();
243
+ if (controls) {
244
+ // Check if it's JoystickControls instance
245
+ if (controls.handleJoystickChange) {
246
+ controls.handleJoystickChange(changeEvent);
247
+ }
248
+ // Check if it's ControlsDirective with joystick getter
249
+ else if (controls.joystick && controls.joystick.handleJoystickChange) {
250
+ controls.joystick.handleJoystickChange(changeEvent);
251
+ }
252
+ }
253
+ return;
254
+ }
255
+
256
+ let tanVal = Math.abs(sideY / sideX);
257
+ let radian = Math.atan(tanVal);
258
+ angle = (radian * 180) / Math.PI;
259
+
260
+ let centerX = 0;
261
+ let centerY = 0;
262
+
263
+ if (sideX * sideX + sideY * sideY >= outerRadius * outerRadius) {
264
+ centerX = outerRadius * Math.cos(radian);
265
+ centerY = outerRadius * Math.sin(radian);
266
+ } else {
267
+ centerX = Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX);
268
+ centerY = Math.abs(sideY) > outerRadius ? outerRadius : Math.abs(sideY);
269
+ }
270
+
271
+ if (sideY < 0) {
272
+ centerY = -Math.abs(centerY);
273
+ }
274
+ if (sideX < 0) {
275
+ centerX = -Math.abs(centerX);
276
+ }
277
+
278
+ if (sideX > 0 && sideY < 0) {
279
+ // < 90
280
+ } else if (sideX < 0 && sideY < 0) {
281
+ // 90 ~ 180
282
+ angle = 180 - angle;
283
+ } else if (sideX < 0 && sideY > 0) {
284
+ // 180 ~ 270
285
+ angle = angle + 180;
286
+ } else if (sideX > 0 && sideY > 0) {
287
+ // 270 ~ 369
288
+ angle = 360 - angle;
289
+ }
290
+ centerPoint.set(centerX, centerY);
291
+ power = getPower(centerPoint);
292
+
293
+ direction = getDirection(centerPoint);
294
+ innerPositionX.set(centerPoint.x);
295
+ innerPositionY.set(centerPoint.y);
296
+ const changeEvent = { angle, direction, power };
297
+ settings.onChange?.(changeEvent);
298
+
299
+ // Notify controls if provided
300
+ const controls = getControls();
301
+ if (controls) {
302
+ // Check if it's JoystickControls instance
303
+ if (controls.handleJoystickChange) {
304
+ controls.handleJoystickChange(changeEvent);
305
+ }
306
+ // Check if it's ControlsDirective with joystick getter
307
+ else if (controls.joystick && controls.joystick.handleJoystickChange) {
308
+ controls.joystick.handleJoystickChange(changeEvent);
309
+ }
310
+ }
311
+ }
312
+
313
+ let innerElement;
314
+ let outerElement;
315
+
316
+ if (!settings.outer) {
317
+ outerElement = h(Graphics, {
318
+ draw: (g) => {
319
+ g.circle(0, 0, outerRadius).fill(settings.outerColor);
320
+ },
321
+ alpha: 0.5,
322
+ });
323
+ } else {
324
+ outerElement = h(Sprite, {
325
+ image: settings.outer,
326
+ anchor: { x: 0.5, y: 0.5 },
327
+ scale: settings.outerScale,
328
+ });
329
+ }
330
+
331
+ const innerOptions: any = {
332
+ scale: settings.innerScale,
333
+ x: innerPositionX,
334
+ y: innerPositionY,
335
+ alpha: innerAlpha,
336
+ };
337
+
338
+ if (!settings.inner) {
339
+ innerElement = h(Graphics, {
340
+ draw: (g) => {
341
+ g.circle(0, 0, innerRadius * 2.5).fill(settings.innerColor);
342
+ },
343
+ ...innerOptions,
344
+ });
345
+ } else {
346
+ innerElement = settings.inner
347
+ }
348
+
349
+ return h(
350
+ Container,
351
+ {
352
+ ...opts,
353
+ pointerdown: handleDragStart,
354
+ pointerup: handleDragEnd,
355
+ pointerupoutside: handleDragEnd,
356
+ pointermove: handleDragMove,
357
+ },
358
+ outerElement,
359
+ innerElement,
360
+ );
361
+ }
@@ -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
 
@@ -13,4 +13,5 @@ export { NineSliceSprite } from './NineSliceSprite'
13
13
  export { type ComponentInstance } from './DisplayObject'
14
14
  export { DOMContainer } from './DOMContainer'
15
15
  export { DOMElement } from './DOMElement'
16
- export { Button, ButtonState, type ButtonProps, type ButtonStyle } from './Button'
16
+ export { Button, ButtonState, type ButtonProps, type ButtonStyle } from './Button'
17
+ export { Joystick, type JoystickSettings, type JoystickChangeEvent } from './Joystick'
@@ -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
  }