canvasengine 2.0.0-beta.4 → 2.0.0-beta.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DebugRenderer-DgECR3yZ.js +172 -0
- package/dist/DebugRenderer-DgECR3yZ.js.map +1 -0
- package/dist/components/Button.d.ts +183 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Canvas.d.ts +18 -0
- package/dist/components/Canvas.d.ts.map +1 -0
- package/dist/components/DOMElement.d.ts +44 -0
- package/dist/components/DOMElement.d.ts.map +1 -0
- package/dist/components/Graphic.d.ts +65 -0
- package/dist/components/Graphic.d.ts.map +1 -0
- package/dist/components/Joystick.d.ts +36 -0
- package/dist/components/Joystick.d.ts.map +1 -0
- package/dist/components/NineSliceSprite.d.ts +17 -0
- package/dist/components/NineSliceSprite.d.ts.map +1 -0
- package/dist/components/ParticleEmitter.d.ts +5 -0
- package/dist/components/ParticleEmitter.d.ts.map +1 -0
- package/dist/components/Scene.d.ts +2 -0
- package/dist/components/Scene.d.ts.map +1 -0
- package/dist/components/Text.d.ts +26 -0
- package/dist/components/Text.d.ts.map +1 -0
- package/dist/components/TilingSprite.d.ts +18 -0
- package/dist/components/TilingSprite.d.ts.map +1 -0
- package/dist/components/Video.d.ts +15 -0
- package/dist/components/Video.d.ts.map +1 -0
- package/dist/components/index.d.ts +18 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/types/DisplayObject.d.ts +110 -0
- package/dist/components/types/DisplayObject.d.ts.map +1 -0
- package/dist/components/types/MouseEvent.d.ts +4 -0
- package/dist/components/types/MouseEvent.d.ts.map +1 -0
- package/dist/components/types/Spritesheet.d.ts +248 -0
- package/dist/components/types/Spritesheet.d.ts.map +1 -0
- package/dist/components/types/index.d.ts +5 -0
- package/dist/components/types/index.d.ts.map +1 -0
- package/dist/directives/Controls.d.ts +113 -0
- package/dist/directives/Controls.d.ts.map +1 -0
- package/dist/directives/ControlsBase.d.ts +198 -0
- package/dist/directives/ControlsBase.d.ts.map +1 -0
- package/dist/directives/Drag.d.ts +70 -0
- package/dist/directives/Drag.d.ts.map +1 -0
- package/dist/directives/Flash.d.ts +117 -0
- package/dist/directives/Flash.d.ts.map +1 -0
- package/dist/directives/GamepadControls.d.ts +225 -0
- package/dist/directives/GamepadControls.d.ts.map +1 -0
- package/dist/directives/JoystickControls.d.ts +172 -0
- package/dist/directives/JoystickControls.d.ts.map +1 -0
- package/dist/directives/KeyboardControls.d.ts +219 -0
- package/dist/directives/KeyboardControls.d.ts.map +1 -0
- package/dist/directives/Scheduler.d.ts +36 -0
- package/dist/directives/Scheduler.d.ts.map +1 -0
- package/dist/directives/Shake.d.ts +98 -0
- package/dist/directives/Shake.d.ts.map +1 -0
- package/dist/directives/Sound.d.ts +26 -0
- package/dist/directives/Sound.d.ts.map +1 -0
- package/dist/directives/Transition.d.ts +11 -0
- package/dist/directives/Transition.d.ts.map +1 -0
- package/dist/directives/ViewportCull.d.ts +12 -0
- package/dist/directives/ViewportCull.d.ts.map +1 -0
- package/dist/directives/ViewportFollow.d.ts +19 -0
- package/dist/directives/ViewportFollow.d.ts.map +1 -0
- package/dist/directives/index.d.ts +13 -0
- package/dist/directives/index.d.ts.map +1 -0
- package/dist/engine/animation.d.ts +73 -0
- package/dist/engine/animation.d.ts.map +1 -0
- package/dist/engine/bootstrap.d.ts +16 -0
- package/dist/engine/bootstrap.d.ts.map +1 -0
- package/dist/engine/directive.d.ts +14 -0
- package/dist/engine/directive.d.ts.map +1 -0
- package/dist/engine/reactive.d.ts +105 -0
- package/dist/engine/reactive.d.ts.map +1 -0
- package/dist/engine/signal.d.ts +72 -0
- package/dist/engine/signal.d.ts.map +1 -0
- package/dist/engine/trigger.d.ts +50 -0
- package/dist/engine/trigger.d.ts.map +1 -0
- package/dist/engine/utils.d.ts +90 -0
- package/dist/engine/utils.d.ts.map +1 -0
- package/dist/hooks/addContext.d.ts +2 -0
- package/dist/hooks/addContext.d.ts.map +1 -0
- package/dist/hooks/useProps.d.ts +42 -0
- package/dist/hooks/useProps.d.ts.map +1 -0
- package/dist/hooks/useRef.d.ts +5 -0
- package/dist/hooks/useRef.d.ts.map +1 -0
- package/dist/index-gb763Hyx.js +12560 -0
- package/dist/index-gb763Hyx.js.map +1 -0
- package/dist/index.d.ts +15 -1083
- package/dist/index.d.ts.map +1 -0
- package/dist/index.global.js +29 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +81 -3041
- package/dist/index.js.map +1 -1
- package/dist/utils/Ease.d.ts +17 -0
- package/dist/utils/Ease.d.ts.map +1 -0
- package/dist/utils/GlobalAssetLoader.d.ts +141 -0
- package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
- package/dist/utils/RadialGradient.d.ts +58 -0
- package/dist/utils/RadialGradient.d.ts.map +1 -0
- package/dist/utils/functions.d.ts +2 -0
- package/dist/utils/functions.d.ts.map +1 -0
- package/package.json +13 -7
- package/src/components/Button.ts +396 -0
- package/src/components/Canvas.ts +61 -45
- package/src/components/Container.ts +21 -2
- package/src/components/DOMContainer.ts +123 -0
- package/src/components/DOMElement.ts +421 -0
- package/src/components/DisplayObject.ts +350 -197
- package/src/components/Graphic.ts +200 -34
- package/src/components/Joystick.ts +361 -0
- package/src/components/Mesh.ts +222 -0
- package/src/components/NineSliceSprite.ts +4 -1
- package/src/components/ParticleEmitter.ts +12 -8
- package/src/components/Sprite.ts +306 -30
- package/src/components/Text.ts +125 -18
- package/src/components/Video.ts +110 -0
- package/src/components/Viewport.ts +59 -43
- package/src/components/index.ts +8 -2
- package/src/components/types/DisplayObject.ts +34 -0
- package/src/components/types/Spritesheet.ts +0 -118
- package/src/directives/Controls.ts +254 -0
- package/src/directives/ControlsBase.ts +266 -0
- package/src/directives/Drag.ts +357 -52
- package/src/directives/Flash.ts +409 -0
- package/src/directives/GamepadControls.ts +537 -0
- package/src/directives/JoystickControls.ts +396 -0
- package/src/directives/KeyboardControls.ts +66 -424
- package/src/directives/Shake.ts +282 -0
- package/src/directives/Sound.ts +94 -31
- package/src/directives/ViewportFollow.ts +35 -7
- package/src/directives/index.ts +12 -6
- package/src/engine/animation.ts +175 -21
- package/src/engine/bootstrap.ts +23 -3
- package/src/engine/directive.ts +2 -2
- package/src/engine/reactive.ts +780 -177
- package/src/engine/signal.ts +35 -4
- package/src/engine/trigger.ts +21 -4
- package/src/engine/utils.ts +19 -3
- package/src/hooks/useProps.ts +1 -1
- package/src/index.ts +4 -2
- package/src/utils/GlobalAssetLoader.ts +257 -0
- package/src/utils/functions.ts +7 -0
- package/testing/index.ts +12 -0
- package/tsconfig.json +17 -0
- package/vite.config.ts +39 -0
package/src/components/Sprite.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
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
|
|
|
@@ -73,15 +79,127 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
73
79
|
private subscriptionSheet: Subscription[] = [];
|
|
74
80
|
private sheetParams: any = {};
|
|
75
81
|
private sheetCurrentAnimation: string = StandardAnimation.Stand;
|
|
82
|
+
private app: Application | null = null;
|
|
76
83
|
onFinish: () => void;
|
|
84
|
+
private globalLoader: GlobalAssetLoader | null = null;
|
|
85
|
+
private trackedAssetIds: Set<string> = new Set();
|
|
86
|
+
|
|
87
|
+
get renderer() {
|
|
88
|
+
return this.app?.renderer;
|
|
89
|
+
}
|
|
77
90
|
|
|
78
91
|
private currentAnimationContainer: Container | null = null;
|
|
79
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Auto-detects image dimensions by loading the image and reading its natural size
|
|
95
|
+
* This is used when width/height are not explicitly provided in the spritesheet definition
|
|
96
|
+
*
|
|
97
|
+
* @param imagePath - Path to the image file
|
|
98
|
+
* @returns Object containing the detected width and height of the image
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* const { width, height } = await sprite.detectImageDimensions('path/to/image.png');
|
|
103
|
+
* // width: 256, height: 128
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
private async detectImageDimensions(imagePath: string): Promise<{ width: number; height: number }> {
|
|
107
|
+
if (!imagePath || typeof imagePath !== 'string' || imagePath.trim() === '') {
|
|
108
|
+
throw new Error(`Invalid image path provided to detectImageDimensions: ${imagePath}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Register asset in global loader if available
|
|
112
|
+
let assetId: string | null = null;
|
|
113
|
+
if (this.globalLoader) {
|
|
114
|
+
assetId = this.globalLoader.registerAsset(imagePath);
|
|
115
|
+
this.trackedAssetIds.add(assetId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const texture = await Assets.load(imagePath, (progress) => {
|
|
119
|
+
if (this.globalLoader && assetId) {
|
|
120
|
+
this.globalLoader.updateProgress(assetId, progress);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Mark as complete
|
|
125
|
+
if (this.globalLoader && assetId) {
|
|
126
|
+
this.globalLoader.completeAsset(assetId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
width: texture.width,
|
|
131
|
+
height: texture.height,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Creates textures from a spritesheet image by cutting it into frames
|
|
137
|
+
* Automatically detects image dimensions if width/height are not provided
|
|
138
|
+
*
|
|
139
|
+
* @param options - Texture options containing image path, dimensions, and frame configuration
|
|
140
|
+
* @returns A 2D array of textures organized by rows and columns
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* // With explicit dimensions
|
|
145
|
+
* const textures = await sprite.createTextures({
|
|
146
|
+
* image: 'path/to/image.png',
|
|
147
|
+
* width: 256,
|
|
148
|
+
* height: 128,
|
|
149
|
+
* framesWidth: 4,
|
|
150
|
+
* framesHeight: 2,
|
|
151
|
+
* spriteWidth: 64,
|
|
152
|
+
* spriteHeight: 64
|
|
153
|
+
* });
|
|
154
|
+
*
|
|
155
|
+
* // Without dimensions (automatically detected)
|
|
156
|
+
* const textures = await sprite.createTextures({
|
|
157
|
+
* image: 'path/to/image.png',
|
|
158
|
+
* framesWidth: 4,
|
|
159
|
+
* framesHeight: 2,
|
|
160
|
+
* spriteWidth: 64,
|
|
161
|
+
* spriteHeight: 64
|
|
162
|
+
* });
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
80
165
|
private async createTextures(
|
|
81
166
|
options: Required<TextureOptionsMerging>
|
|
82
167
|
): Promise<Texture[][]> {
|
|
83
|
-
|
|
84
|
-
|
|
168
|
+
let { width, height, framesHeight, framesWidth, image, offset } = options;
|
|
169
|
+
|
|
170
|
+
if (!image || typeof image !== 'string' || image.trim() === '') {
|
|
171
|
+
console.warn('Invalid image path provided to createTextures:', image);
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Register asset in global loader if available
|
|
176
|
+
let assetId: string | null = null;
|
|
177
|
+
if (this.globalLoader) {
|
|
178
|
+
assetId = this.globalLoader.registerAsset(image);
|
|
179
|
+
this.trackedAssetIds.add(assetId);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const texture = await Assets.load(image, (progress) => {
|
|
183
|
+
if (this.globalLoader && assetId) {
|
|
184
|
+
this.globalLoader.updateProgress(assetId, progress);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Mark as complete
|
|
189
|
+
if (this.globalLoader && assetId) {
|
|
190
|
+
this.globalLoader.completeAsset(assetId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Auto-detect width and height from the image if not provided
|
|
194
|
+
if (!width || width <= 0) {
|
|
195
|
+
width = texture.width;
|
|
196
|
+
options.width = width;
|
|
197
|
+
}
|
|
198
|
+
if (!height || height <= 0) {
|
|
199
|
+
height = texture.height;
|
|
200
|
+
options.height = height;
|
|
201
|
+
}
|
|
202
|
+
|
|
85
203
|
const spriteWidth = options.spriteWidth;
|
|
86
204
|
const spriteHeight = options.spriteHeight;
|
|
87
205
|
const frames: Texture[][] = [];
|
|
@@ -140,12 +258,30 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
140
258
|
} as any;
|
|
141
259
|
const {
|
|
142
260
|
rectWidth,
|
|
143
|
-
width = 0,
|
|
261
|
+
width: widthOption = 0,
|
|
144
262
|
framesWidth = 1,
|
|
145
263
|
rectHeight,
|
|
146
|
-
height = 0,
|
|
264
|
+
height: heightOption = 0,
|
|
147
265
|
framesHeight = 1,
|
|
266
|
+
image,
|
|
148
267
|
} = optionsTextures;
|
|
268
|
+
|
|
269
|
+
// Auto-detect width and height from the image if not provided
|
|
270
|
+
let width = widthOption;
|
|
271
|
+
let height = heightOption;
|
|
272
|
+
|
|
273
|
+
if (image && ((!width || width <= 0) || (!height || height <= 0))) {
|
|
274
|
+
const dimensions = await this.detectImageDimensions(image);
|
|
275
|
+
if (!width || width <= 0) {
|
|
276
|
+
width = dimensions.width;
|
|
277
|
+
optionsTextures.width = width;
|
|
278
|
+
}
|
|
279
|
+
if (!height || height <= 0) {
|
|
280
|
+
height = dimensions.height;
|
|
281
|
+
optionsTextures.height = height;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
149
285
|
optionsTextures.spriteWidth = rectWidth ? rectWidth : width / framesWidth;
|
|
150
286
|
optionsTextures.spriteHeight = rectHeight
|
|
151
287
|
? rectHeight
|
|
@@ -164,17 +300,26 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
164
300
|
}
|
|
165
301
|
|
|
166
302
|
async onMount(params: Element<CanvasSprite>) {
|
|
303
|
+
// Set #element manually for freeze checking before calling super.onMount
|
|
304
|
+
// We need to set it early so update() can check freeze state
|
|
305
|
+
(this as any)['#element'] = params;
|
|
306
|
+
|
|
167
307
|
const { props, propObservables } = params;
|
|
168
308
|
const tick: Signal = props.context.tick;
|
|
169
309
|
const sheet = props.sheet ?? {};
|
|
310
|
+
const definition = props.sheet?.definition ?? {};
|
|
311
|
+
this.app = props.context.app();
|
|
312
|
+
// Get global loader from context if available
|
|
313
|
+
this.globalLoader = props.context?.globalLoader || null;
|
|
170
314
|
if (sheet?.onFinish) {
|
|
171
315
|
this.onFinish = sheet.onFinish;
|
|
172
316
|
}
|
|
173
317
|
this.subscriptionTick = tick.observable.subscribe((value) => {
|
|
318
|
+
if (this.destroyed) return
|
|
174
319
|
this.update(value);
|
|
175
320
|
});
|
|
176
|
-
if (
|
|
177
|
-
this.spritesheet =
|
|
321
|
+
if (definition) {
|
|
322
|
+
this.spritesheet = definition.value ?? definition;
|
|
178
323
|
await this.createAnimations();
|
|
179
324
|
}
|
|
180
325
|
if (sheet.params) {
|
|
@@ -218,49 +363,123 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
218
363
|
this.sheetCurrentAnimation = StandardAnimation.Stand;
|
|
219
364
|
}
|
|
220
365
|
|
|
221
|
-
this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
366
|
+
if (this.spritesheet && this.has(this.sheetCurrentAnimation)) this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
222
367
|
});
|
|
223
|
-
|
|
224
368
|
super.onMount(params);
|
|
225
369
|
}
|
|
226
370
|
|
|
227
371
|
async onUpdate(props) {
|
|
372
|
+
if (this.destroyed) return
|
|
228
373
|
super.onUpdate(props);
|
|
229
374
|
|
|
230
|
-
|
|
375
|
+
// Initialize globalLoader from context if not already set
|
|
376
|
+
if (!this.globalLoader && props.context?.globalLoader) {
|
|
377
|
+
this.globalLoader = props.context.globalLoader;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const setTexture = async (image: string) => {
|
|
381
|
+
if (!image || typeof image !== 'string' || image.trim() === '') {
|
|
382
|
+
console.warn('Invalid image path provided to setTexture:', image);
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Register asset in global loader if available
|
|
387
|
+
let assetId: string | null = null;
|
|
388
|
+
if (this.globalLoader) {
|
|
389
|
+
assetId = this.globalLoader.registerAsset(image);
|
|
390
|
+
this.trackedAssetIds.add(assetId);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const onProgress = this.fullProps.loader?.onProgress;
|
|
394
|
+
const texture = await Assets.load(image, (progress) => {
|
|
395
|
+
// Update global loader progress
|
|
396
|
+
if (this.globalLoader && assetId) {
|
|
397
|
+
this.globalLoader.updateProgress(assetId, progress);
|
|
398
|
+
}
|
|
399
|
+
// Call local loader callback if provided
|
|
400
|
+
if (onProgress) onProgress(progress);
|
|
401
|
+
if (progress == 1) {
|
|
402
|
+
// Mark as complete in global loader
|
|
403
|
+
if (this.globalLoader && assetId) {
|
|
404
|
+
this.globalLoader.completeAsset(assetId);
|
|
405
|
+
}
|
|
406
|
+
const onComplete = this.fullProps.loader?.onComplete;
|
|
407
|
+
if (onComplete) {
|
|
408
|
+
// hack to memoize the texture
|
|
409
|
+
setTimeout(() => {
|
|
410
|
+
onComplete(texture);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
return texture
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const sheet = props.sheet
|
|
420
|
+
const definition = props.sheet?.definition ?? {};
|
|
421
|
+
|
|
422
|
+
if (definition?.type === 'reset') {
|
|
423
|
+
this.spritesheet = definition.value ?? definition;
|
|
424
|
+
await this.resetAnimations();
|
|
425
|
+
}
|
|
426
|
+
|
|
231
427
|
if (sheet?.params) this.sheetParams = sheet?.params;
|
|
232
428
|
|
|
233
|
-
if (sheet?.playing && this.isMounted) {
|
|
429
|
+
if (sheet?.playing && this.isMounted && this.spritesheet && this.animations.size > 0) {
|
|
234
430
|
this.sheetCurrentAnimation = sheet?.playing;
|
|
235
431
|
this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
236
432
|
}
|
|
237
433
|
|
|
238
|
-
if (props.hitbox) this.hitbox = props.hitbox;
|
|
434
|
+
if (props.hitbox) this.hitbox = props.hitbox.value ?? props.hitbox;
|
|
239
435
|
|
|
240
436
|
if (props.scaleMode) this.baseTexture.scaleMode = props.scaleMode;
|
|
241
437
|
else if (props.image && this.fullProps.rectangle === undefined) {
|
|
242
|
-
|
|
438
|
+
const texture = await setTexture(this.fullProps.image);
|
|
439
|
+
if (texture) {
|
|
440
|
+
this.texture = texture;
|
|
441
|
+
}
|
|
243
442
|
} else if (props.texture) {
|
|
244
|
-
|
|
443
|
+
if (isElement(props.texture)) {
|
|
444
|
+
const textureInstance = props.texture.componentInstance;
|
|
445
|
+
textureInstance.subjectInit
|
|
446
|
+
.subscribe()
|
|
447
|
+
this.texture = this.renderer?.generateTexture(props.texture.componentInstance);
|
|
448
|
+
} else {
|
|
449
|
+
this.texture = props.texture;
|
|
450
|
+
}
|
|
245
451
|
}
|
|
246
|
-
|
|
247
452
|
if (props.rectangle !== undefined) {
|
|
248
453
|
const { x, y, width, height } = props.rectangle?.value ?? props.rectangle;
|
|
249
|
-
const texture = await
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
454
|
+
const texture = await setTexture(this.fullProps.image);
|
|
455
|
+
if (texture) {
|
|
456
|
+
this.texture = new Texture({
|
|
457
|
+
source: texture.source,
|
|
458
|
+
frame: new Rectangle(x, y, width, height),
|
|
459
|
+
});
|
|
460
|
+
}
|
|
254
461
|
}
|
|
255
462
|
}
|
|
256
463
|
|
|
257
|
-
onDestroy(): void {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
464
|
+
async onDestroy(parent: Element, afterDestroy: () => void): Promise<void> {
|
|
465
|
+
const _afterDestroy = async () => {
|
|
466
|
+
// Clean up tracked assets from global loader
|
|
467
|
+
if (this.globalLoader) {
|
|
468
|
+
this.trackedAssetIds.forEach((assetId) => {
|
|
469
|
+
this.globalLoader!.removeAsset(assetId);
|
|
470
|
+
});
|
|
471
|
+
this.trackedAssetIds.clear();
|
|
472
|
+
}
|
|
473
|
+
this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
|
|
474
|
+
this.subscriptionTick.unsubscribe();
|
|
475
|
+
if (this.currentAnimationContainer && this.parent instanceof Container) {
|
|
476
|
+
this.parent.removeChild(this.currentAnimationContainer);
|
|
477
|
+
}
|
|
478
|
+
if (afterDestroy) {
|
|
479
|
+
afterDestroy();
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
await super.onDestroy(parent, _afterDestroy);
|
|
264
483
|
}
|
|
265
484
|
|
|
266
485
|
has(name: string): boolean {
|
|
@@ -279,7 +498,6 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
279
498
|
|
|
280
499
|
stop() {
|
|
281
500
|
this.currentAnimation = null;
|
|
282
|
-
this.destroy();
|
|
283
501
|
}
|
|
284
502
|
|
|
285
503
|
play(name: string, params: any[] = []) {
|
|
@@ -321,7 +539,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
321
539
|
const sound = this.currentAnimation.data.sound;
|
|
322
540
|
|
|
323
541
|
if (sound) {
|
|
324
|
-
|
|
542
|
+
new Howl({
|
|
543
|
+
src: sound,
|
|
544
|
+
autoplay: true,
|
|
545
|
+
loop: false,
|
|
546
|
+
volume: 1,
|
|
547
|
+
})
|
|
325
548
|
}
|
|
326
549
|
|
|
327
550
|
// Updates immediately to avoid flickering
|
|
@@ -330,7 +553,51 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
330
553
|
});
|
|
331
554
|
}
|
|
332
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Resets the sprite by destroying and recreating all animations
|
|
558
|
+
* This method clears the current animation state, destroys existing textures,
|
|
559
|
+
* and recreates all animations from the spritesheet
|
|
560
|
+
*
|
|
561
|
+
* @example
|
|
562
|
+
* ```typescript
|
|
563
|
+
* // Reset all animations to their initial state
|
|
564
|
+
* sprite.resetAnimations();
|
|
565
|
+
*
|
|
566
|
+
* // Reset and then play a specific animation
|
|
567
|
+
* await sprite.resetAnimations();
|
|
568
|
+
* sprite.play('walk');
|
|
569
|
+
* ```
|
|
570
|
+
*/
|
|
571
|
+
async resetAnimations(): Promise<void> {
|
|
572
|
+
// Stop current animation
|
|
573
|
+
this.stop();
|
|
574
|
+
|
|
575
|
+
// Clear all animations and textures
|
|
576
|
+
this.animations.clear();
|
|
577
|
+
|
|
578
|
+
// Reset animation state
|
|
579
|
+
this.currentAnimation = null;
|
|
580
|
+
this.currentAnimationContainer = null;
|
|
581
|
+
this.time = 0;
|
|
582
|
+
this.frameIndex = 0;
|
|
583
|
+
|
|
584
|
+
// Clear children
|
|
585
|
+
this.removeChildren();
|
|
586
|
+
|
|
587
|
+
// Recreate animations from spritesheet
|
|
588
|
+
if (this.spritesheet) {
|
|
589
|
+
await this.createAnimations();
|
|
590
|
+
this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
333
594
|
update({ deltaRatio }) {
|
|
595
|
+
// Block animation update if element is frozen
|
|
596
|
+
const element = this.getElement();
|
|
597
|
+
if (element && isElementFrozen(element)) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
334
601
|
if (
|
|
335
602
|
!this.isPlaying() ||
|
|
336
603
|
!this.currentAnimation ||
|
|
@@ -396,6 +663,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
396
663
|
const widthOfSprite =
|
|
397
664
|
typeof realSize == "number" ? realSize : realSize?.width;
|
|
398
665
|
|
|
666
|
+
|
|
399
667
|
const applyAnchorBySize = () => {
|
|
400
668
|
if (heightOfSprite && this.hitbox) {
|
|
401
669
|
const { spriteWidth, spriteHeight } = data;
|
|
@@ -440,7 +708,9 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
440
708
|
}
|
|
441
709
|
}
|
|
442
710
|
|
|
443
|
-
export interface CanvasSprite extends PixiSprite {
|
|
711
|
+
export interface CanvasSprite extends PixiSprite {
|
|
712
|
+
layout: Layout | null;
|
|
713
|
+
}
|
|
444
714
|
|
|
445
715
|
registerComponent("Sprite", CanvasSprite);
|
|
446
716
|
|
|
@@ -483,11 +753,17 @@ export interface SpritePropsWithSheet
|
|
|
483
753
|
params?: any;
|
|
484
754
|
onFinish?: () => void;
|
|
485
755
|
};
|
|
756
|
+
loader?: {
|
|
757
|
+
onProgress?: (progress: number) => void;
|
|
758
|
+
onComplete?: (texture: Texture) => void;
|
|
759
|
+
};
|
|
486
760
|
}
|
|
487
761
|
|
|
488
762
|
export type SpritePropTypes = SpritePropsWithImage | SpritePropsWithSheet;
|
|
489
763
|
|
|
490
764
|
// Update the Sprite function to use the props interface
|
|
491
765
|
export const Sprite: ComponentFunction<SpritePropTypes> = (props) => {
|
|
766
|
+
// Ensure component is registered in test environments where module cache may differ
|
|
767
|
+
registerComponent("Sprite", CanvasSprite);
|
|
492
768
|
return createComponent("Sprite", props);
|
|
493
769
|
};
|
package/src/components/Text.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { Text as PixiText, TextStyle } from "pixi.js";
|
|
2
|
-
import { createComponent, registerComponent } from "../engine/reactive";
|
|
3
|
-
import { DisplayObject } from "./DisplayObject";
|
|
2
|
+
import { createComponent, registerComponent, Element, Props } from "../engine/reactive";
|
|
3
|
+
import { DisplayObject, ComponentInstance } from "./DisplayObject";
|
|
4
4
|
import { DisplayObjectProps } from "./types/DisplayObject";
|
|
5
5
|
import { Signal } from "@signe/reactive";
|
|
6
|
-
import { on } from "../engine/trigger";
|
|
6
|
+
import { on, isTrigger } from "../engine/trigger";
|
|
7
|
+
import { Howl } from "howler";
|
|
7
8
|
|
|
8
9
|
enum TextEffect {
|
|
9
10
|
Typewriter = "typewriter",
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
interface TextProps extends DisplayObjectProps {
|
|
13
|
+
export interface TextProps extends DisplayObjectProps {
|
|
13
14
|
text?: string;
|
|
14
15
|
style?: Partial<TextStyle>;
|
|
15
16
|
color?: string;
|
|
@@ -20,7 +21,13 @@ interface TextProps extends DisplayObjectProps {
|
|
|
20
21
|
start?: () => void;
|
|
21
22
|
onComplete?: () => void;
|
|
22
23
|
skip?: () => void;
|
|
24
|
+
sound?: {
|
|
25
|
+
src: string;
|
|
26
|
+
volume?: number;
|
|
27
|
+
rate?: number;
|
|
28
|
+
};
|
|
23
29
|
};
|
|
30
|
+
context?: any; // Ensure context is available, ideally typed from a base prop or injected
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
class CanvasText extends DisplayObject(PixiText) {
|
|
@@ -31,10 +38,19 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
31
38
|
private _wordWrapWidth: number = 0;
|
|
32
39
|
private typewriterOptions: any = {};
|
|
33
40
|
private skipSignal?: () => void;
|
|
41
|
+
private typewriterSound?: Howl;
|
|
42
|
+
private lastSoundTime: number = 0;
|
|
43
|
+
private soundDuration: number = 0; // Duration of the sound in milliseconds
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Called when the component is mounted to the scene graph.
|
|
47
|
+
* Initializes the typewriter effect if configured.
|
|
48
|
+
* @param {Element<CanvasText>} element - The element being mounted with parent and props.
|
|
49
|
+
* @param {number} [index] - The index of the component among its siblings.
|
|
50
|
+
*/
|
|
51
|
+
async onMount(element: Element<CanvasText>, index?: number): Promise<void> {
|
|
52
|
+
const { props } = element;
|
|
53
|
+
await super.onMount(element, index);
|
|
38
54
|
const tick: Signal = props.context.tick;
|
|
39
55
|
|
|
40
56
|
if (props.text && props.typewriter) {
|
|
@@ -44,12 +60,18 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
44
60
|
// Set typewriter options
|
|
45
61
|
if (props.typewriter) {
|
|
46
62
|
this.typewriterOptions = props.typewriter;
|
|
47
|
-
if (this.typewriterOptions.skip) {
|
|
63
|
+
if (this.typewriterOptions.skip && isTrigger(this.typewriterOptions.skip)) {
|
|
48
64
|
on(this.typewriterOptions.skip, () => {
|
|
49
65
|
this.skipTypewriter();
|
|
50
66
|
});
|
|
51
67
|
}
|
|
68
|
+
// Initialize typewriter sound if configured
|
|
69
|
+
if (this.typewriterOptions.sound) {
|
|
70
|
+
this.initializeTypewriterSound();
|
|
71
|
+
}
|
|
52
72
|
}
|
|
73
|
+
// Update layout after initializing typewriter
|
|
74
|
+
this.updateLayout();
|
|
53
75
|
}
|
|
54
76
|
this.subscriptionTick = tick.observable.subscribe(() => {
|
|
55
77
|
if (props.typewriter) {
|
|
@@ -63,15 +85,21 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
63
85
|
if (props.typewriter) {
|
|
64
86
|
if (props.typewriter) {
|
|
65
87
|
this.typewriterOptions = props.typewriter;
|
|
88
|
+
// Reinitialize sound if sound configuration changed
|
|
89
|
+
if (props.typewriter.sound) {
|
|
90
|
+
this.initializeTypewriterSound();
|
|
91
|
+
}
|
|
66
92
|
}
|
|
67
93
|
}
|
|
68
|
-
if (props.text) {
|
|
69
|
-
this.text = props.text;
|
|
94
|
+
if (props.text !== undefined) {
|
|
95
|
+
this.text = ''+props.text;
|
|
70
96
|
}
|
|
71
97
|
if (props.text !== undefined && props.text !== this.fullText && this.fullProps.typewriter) {
|
|
72
98
|
this.text = "";
|
|
73
99
|
this.currentIndex = 0;
|
|
74
100
|
this.fullText = props.text;
|
|
101
|
+
// Update layout after resetting typewriter
|
|
102
|
+
this.updateLayout();
|
|
75
103
|
}
|
|
76
104
|
if (props.style) {
|
|
77
105
|
for (const key in props.style) {
|
|
@@ -90,6 +118,59 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
90
118
|
if (props.fontFamily) {
|
|
91
119
|
this.style.fontFamily = props.fontFamily;
|
|
92
120
|
}
|
|
121
|
+
|
|
122
|
+
// Use the centralized layout update method
|
|
123
|
+
this.updateLayout();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get onCompleteCallback() {
|
|
127
|
+
return this.typewriterOptions.onComplete;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Initializes the typewriter sound effect using Howler.
|
|
132
|
+
* Creates a Howl instance with the configured sound settings.
|
|
133
|
+
* Calculates the sound duration to prevent overlapping sounds.
|
|
134
|
+
*/
|
|
135
|
+
private initializeTypewriterSound() {
|
|
136
|
+
if (!this.typewriterOptions.sound?.src) return;
|
|
137
|
+
|
|
138
|
+
this.typewriterSound = new Howl({
|
|
139
|
+
src: [this.typewriterOptions.sound.src],
|
|
140
|
+
volume: this.typewriterOptions.sound.volume ?? 0.5,
|
|
141
|
+
rate: this.typewriterOptions.sound.rate ?? 1.0,
|
|
142
|
+
preload: true,
|
|
143
|
+
onload: () => {
|
|
144
|
+
// Calculate sound duration in milliseconds
|
|
145
|
+
if (this.typewriterSound) {
|
|
146
|
+
const duration = this.typewriterSound.duration();
|
|
147
|
+
const rate = this.typewriterOptions.sound?.rate ?? 1.0;
|
|
148
|
+
this.soundDuration = (duration / rate) * 1000;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Plays the typewriter sound with duration-based cooldown to prevent overlapping sounds.
|
|
156
|
+
* @param {number} currentTime - The current timestamp to check against sound duration.
|
|
157
|
+
*/
|
|
158
|
+
private playTypewriterSound(currentTime: number) {
|
|
159
|
+
if (!this.typewriterSound || !this.typewriterOptions.sound) return;
|
|
160
|
+
|
|
161
|
+
// Check if enough time has passed since the last sound play
|
|
162
|
+
// Use the actual sound duration to prevent overlap
|
|
163
|
+
if (this.soundDuration > 0 && currentTime - this.lastSoundTime < this.soundDuration) return;
|
|
164
|
+
|
|
165
|
+
this.typewriterSound.play();
|
|
166
|
+
this.lastSoundTime = currentTime;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Updates the layout properties of the text component.
|
|
171
|
+
* This method ensures consistent width, height and word wrap behavior.
|
|
172
|
+
*/
|
|
173
|
+
private updateLayout() {
|
|
93
174
|
if (this._wordWrapWidth) {
|
|
94
175
|
this.setWidth(this._wordWrapWidth);
|
|
95
176
|
} else {
|
|
@@ -98,10 +179,6 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
98
179
|
this.setHeight(this.height);
|
|
99
180
|
}
|
|
100
181
|
|
|
101
|
-
get onCompleteCallback() {
|
|
102
|
-
return this.typewriterOptions.onComplete;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
182
|
private typewriterEffect() {
|
|
106
183
|
if (this.currentIndex < this.fullText.length) {
|
|
107
184
|
const nextIndex = Math.min(
|
|
@@ -111,6 +188,14 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
111
188
|
this.text = this.fullText.slice(0, nextIndex);
|
|
112
189
|
this.currentIndex = nextIndex;
|
|
113
190
|
|
|
191
|
+
// Play typewriter sound if configured
|
|
192
|
+
if (this.typewriterOptions.sound) {
|
|
193
|
+
this.playTypewriterSound(Date.now());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Update layout after text change to maintain proper word wrap and dimensions
|
|
197
|
+
this.updateLayout();
|
|
198
|
+
|
|
114
199
|
// Check if typewriter effect is complete
|
|
115
200
|
if (
|
|
116
201
|
this.currentIndex === this.fullText.length &&
|
|
@@ -128,15 +213,37 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
128
213
|
}
|
|
129
214
|
this.text = this.fullText;
|
|
130
215
|
this.currentIndex = this.fullText.length;
|
|
216
|
+
|
|
217
|
+
// Update layout after setting full text to maintain proper word wrap and dimensions
|
|
218
|
+
this.updateLayout();
|
|
131
219
|
}
|
|
132
220
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Called when the component is about to be destroyed.
|
|
223
|
+
* Unsubscribes from the tick observable and cleans up sound resources.
|
|
224
|
+
* @param {Element<any>} parent - The parent element.
|
|
225
|
+
* @param {() => void} [afterDestroy] - An optional callback function to be executed after the component's own destruction logic.
|
|
226
|
+
*/
|
|
227
|
+
async onDestroy(parent: Element<any>, afterDestroy?: () => void): Promise<void> {
|
|
228
|
+
const _afterDestroy = async () => {
|
|
229
|
+
if (this.subscriptionTick) {
|
|
230
|
+
this.subscriptionTick.unsubscribe();
|
|
231
|
+
}
|
|
232
|
+
// Clean up typewriter sound
|
|
233
|
+
if (this.typewriterSound) {
|
|
234
|
+
this.typewriterSound.stop();
|
|
235
|
+
this.typewriterSound.unload();
|
|
236
|
+
this.typewriterSound = undefined;
|
|
237
|
+
}
|
|
238
|
+
if (afterDestroy) {
|
|
239
|
+
afterDestroy();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
await super.onDestroy(parent, _afterDestroy);
|
|
136
243
|
}
|
|
137
244
|
}
|
|
138
245
|
|
|
139
|
-
interface CanvasText extends PixiText {}
|
|
246
|
+
// interface CanvasText extends PixiText {} // Removed as it's redundant and causes type conflicts
|
|
140
247
|
|
|
141
248
|
registerComponent("Text", CanvasText);
|
|
142
249
|
|