canvasengine 2.0.0-beta.6 → 2.0.0-beta.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DebugRenderer-DkjTAc48.js +1384 -0
- package/dist/DebugRenderer-DkjTAc48.js.map +1 -0
- package/dist/components/Button.d.ts +185 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Canvas.d.ts +17 -0
- package/dist/components/Canvas.d.ts.map +1 -0
- package/dist/components/DOMElement.d.ts +54 -0
- package/dist/components/DOMElement.d.ts.map +1 -0
- package/dist/components/DOMSprite.d.ts +127 -0
- package/dist/components/DOMSprite.d.ts.map +1 -0
- package/dist/components/FocusContainer.d.ts +129 -0
- package/dist/components/FocusContainer.d.ts.map +1 -0
- package/dist/components/Graphic.d.ts +64 -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 +16 -0
- package/dist/components/NineSliceSprite.d.ts.map +1 -0
- package/dist/components/ParticleEmitter.d.ts +4 -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 +24 -0
- package/dist/components/Text.d.ts.map +1 -0
- package/dist/components/TilingSprite.d.ts +17 -0
- package/dist/components/TilingSprite.d.ts.map +1 -0
- package/dist/components/Video.d.ts +14 -0
- package/dist/components/Video.d.ts.map +1 -0
- package/dist/components/index.d.ts +20 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/types/DisplayObject.d.ts +118 -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 +4 -0
- package/dist/components/types/index.d.ts.map +1 -0
- package/dist/directives/Controls.d.ts +112 -0
- package/dist/directives/Controls.d.ts.map +1 -0
- package/dist/directives/ControlsBase.d.ts +199 -0
- package/dist/directives/ControlsBase.d.ts.map +1 -0
- package/dist/directives/Drag.d.ts +69 -0
- package/dist/directives/Drag.d.ts.map +1 -0
- package/dist/directives/Flash.d.ts +116 -0
- package/dist/directives/Flash.d.ts.map +1 -0
- package/dist/directives/FocusNavigation.d.ts +52 -0
- package/dist/directives/FocusNavigation.d.ts.map +1 -0
- package/dist/directives/FogVisibility.d.ts +47 -0
- package/dist/directives/FogVisibility.d.ts.map +1 -0
- package/dist/directives/GamepadControls.d.ts +224 -0
- package/dist/directives/GamepadControls.d.ts.map +1 -0
- package/dist/directives/JoystickControls.d.ts +171 -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 +25 -0
- package/dist/directives/Sound.d.ts.map +1 -0
- package/dist/directives/Transition.d.ts +10 -0
- package/dist/directives/Transition.d.ts.map +1 -0
- package/dist/directives/ViewportCull.d.ts +11 -0
- package/dist/directives/ViewportCull.d.ts.map +1 -0
- package/dist/directives/ViewportFollow.d.ts +18 -0
- package/dist/directives/ViewportFollow.d.ts.map +1 -0
- package/dist/directives/index.d.ts +14 -0
- package/dist/directives/index.d.ts.map +1 -0
- package/dist/dist-BOOc43Qm.js +778 -0
- package/dist/dist-BOOc43Qm.js.map +1 -0
- package/dist/engine/FocusManager.d.ts +174 -0
- package/dist/engine/FocusManager.d.ts.map +1 -0
- package/dist/engine/animation.d.ts +72 -0
- package/dist/engine/animation.d.ts.map +1 -0
- package/dist/engine/bootstrap.d.ts +52 -0
- package/dist/engine/bootstrap.d.ts.map +1 -0
- package/dist/engine/directive.d.ts +13 -0
- package/dist/engine/directive.d.ts.map +1 -0
- package/dist/engine/reactive.d.ts +135 -0
- package/dist/engine/reactive.d.ts.map +1 -0
- package/dist/engine/signal.d.ts +73 -0
- package/dist/engine/signal.d.ts.map +1 -0
- package/dist/engine/trigger.d.ts +54 -0
- package/dist/engine/trigger.d.ts.map +1 -0
- package/dist/engine/utils.d.ts +89 -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/useFocus.d.ts +60 -0
- package/dist/hooks/useFocus.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 +4 -0
- package/dist/hooks/useRef.d.ts.map +1 -0
- package/dist/index.d.ts +19 -1107
- package/dist/index.d.ts.map +1 -0
- package/dist/index.global.js +8 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +14708 -3135
- 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 +57 -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/dist/utils/tabindex.d.ts +16 -0
- package/dist/utils/tabindex.d.ts.map +1 -0
- package/package.json +16 -9
- package/src/components/Button.ts +399 -0
- package/src/components/Canvas.ts +82 -51
- package/src/components/Container.ts +21 -2
- package/src/components/DOMContainer.ts +379 -0
- package/src/components/DOMElement.ts +556 -0
- package/src/components/DOMSprite.ts +1040 -0
- package/src/components/DisplayObject.ts +422 -201
- package/src/components/FocusContainer.ts +368 -0
- package/src/components/Graphic.ts +239 -73
- package/src/components/Joystick.ts +363 -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 +418 -52
- package/src/components/Text.ts +270 -26
- package/src/components/Viewport.ts +122 -63
- package/src/components/index.ts +9 -2
- package/src/components/types/DisplayObject.ts +53 -5
- package/src/components/types/Spritesheet.ts +0 -118
- package/src/directives/Controls.ts +254 -0
- package/src/directives/ControlsBase.ts +267 -0
- package/src/directives/Drag.ts +357 -52
- package/src/directives/Flash.ts +419 -0
- package/src/directives/FocusNavigation.ts +113 -0
- package/src/directives/FogVisibility.ts +273 -0
- package/src/directives/GamepadControls.ts +537 -0
- package/src/directives/JoystickControls.ts +396 -0
- package/src/directives/KeyboardControls.ts +85 -430
- package/src/directives/Scheduler.ts +21 -5
- package/src/directives/Shake.ts +298 -0
- package/src/directives/Sound.ts +94 -31
- package/src/directives/ViewportFollow.ts +40 -9
- package/src/directives/index.ts +13 -6
- package/src/engine/FocusManager.ts +510 -0
- package/src/engine/animation.ts +175 -21
- package/src/engine/bootstrap.ts +140 -6
- package/src/engine/directive.ts +4 -4
- package/src/engine/reactive.ts +980 -177
- package/src/engine/signal.ts +241 -47
- package/src/engine/trigger.ts +34 -7
- package/src/engine/utils.ts +19 -3
- package/src/hooks/useFocus.ts +91 -0
- package/src/hooks/useProps.ts +1 -1
- package/src/index.ts +8 -2
- package/src/types/pixi-cull.d.ts +7 -0
- package/src/utils/GlobalAssetLoader.ts +257 -0
- package/src/utils/functions.ts +7 -0
- package/src/utils/tabindex.ts +70 -0
- package/testing/index.ts +35 -4
- package/tsconfig.json +18 -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
|
|
|
@@ -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:
|
|
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
|
-
|
|
84
|
-
|
|
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<
|
|
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 (
|
|
177
|
-
|
|
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.
|
|
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.
|
|
353
|
+
if (!this.has(this.sheetCurrentAnimation)) continue;
|
|
354
|
+
this.play(this.sheetCurrentAnimation, [{ ...this.sheetParams, [key]: value }]);
|
|
192
355
|
}
|
|
193
356
|
}
|
|
194
357
|
}
|
|
@@ -212,26 +375,50 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
212
375
|
|
|
213
376
|
if (!this.isMounted) return;
|
|
214
377
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
} else {
|
|
218
|
-
this.sheetCurrentAnimation = StandardAnimation.Stand;
|
|
219
|
-
}
|
|
378
|
+
const animationName = this.getMovementAnimationName(_isMoving);
|
|
379
|
+
if (!animationName) return;
|
|
220
380
|
|
|
221
|
-
this.
|
|
381
|
+
this.sheetCurrentAnimation = animationName;
|
|
382
|
+
if (this.spritesheet) this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
222
383
|
});
|
|
223
|
-
|
|
224
384
|
super.onMount(params);
|
|
225
385
|
}
|
|
226
386
|
|
|
227
387
|
async onUpdate(props) {
|
|
388
|
+
if (this.destroyed) return
|
|
228
389
|
super.onUpdate(props);
|
|
229
390
|
|
|
391
|
+
// Initialize globalLoader from context if not already set
|
|
392
|
+
if (!this.globalLoader && props.context?.globalLoader) {
|
|
393
|
+
this.globalLoader = props.context.globalLoader;
|
|
394
|
+
}
|
|
395
|
+
|
|
230
396
|
const setTexture = async (image: string) => {
|
|
397
|
+
if (!image || typeof image !== 'string' || image.trim() === '') {
|
|
398
|
+
console.warn('Invalid image path provided to setTexture:', image);
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Register asset in global loader if available
|
|
403
|
+
let assetId: string | null = null;
|
|
404
|
+
if (this.globalLoader) {
|
|
405
|
+
assetId = this.globalLoader.registerAsset(image);
|
|
406
|
+
this.trackedAssetIds.add(assetId);
|
|
407
|
+
}
|
|
408
|
+
|
|
231
409
|
const onProgress = this.fullProps.loader?.onProgress;
|
|
232
410
|
const texture = await Assets.load(image, (progress) => {
|
|
411
|
+
// Update global loader progress
|
|
412
|
+
if (this.globalLoader && assetId) {
|
|
413
|
+
this.globalLoader.updateProgress(assetId, progress);
|
|
414
|
+
}
|
|
415
|
+
// Call local loader callback if provided
|
|
233
416
|
if (onProgress) onProgress(progress);
|
|
234
417
|
if (progress == 1) {
|
|
418
|
+
// Mark as complete in global loader
|
|
419
|
+
if (this.globalLoader && assetId) {
|
|
420
|
+
this.globalLoader.completeAsset(assetId);
|
|
421
|
+
}
|
|
235
422
|
const onComplete = this.fullProps.loader?.onComplete;
|
|
236
423
|
if (onComplete) {
|
|
237
424
|
// hack to memoize the texture
|
|
@@ -245,41 +432,79 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
245
432
|
return texture
|
|
246
433
|
}
|
|
247
434
|
|
|
248
|
-
const sheet = props.sheet
|
|
435
|
+
const sheet = props.sheet
|
|
436
|
+
const definition = props.sheet?.definition ?? {};
|
|
437
|
+
|
|
438
|
+
if (definition?.type === 'reset') {
|
|
439
|
+
const resolvedValue = definition.value instanceof Promise ? await definition.value : definition.value;
|
|
440
|
+
this.spritesheet = resolvedValue ?? definition;
|
|
441
|
+
await this.resetAnimations();
|
|
442
|
+
}
|
|
443
|
+
|
|
249
444
|
if (sheet?.params) this.sheetParams = sheet?.params;
|
|
250
445
|
|
|
251
|
-
if (sheet?.playing && this.isMounted) {
|
|
446
|
+
if (sheet?.playing && this.isMounted && this.spritesheet && this.animations.size > 0) {
|
|
252
447
|
this.sheetCurrentAnimation = sheet?.playing;
|
|
253
448
|
this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
254
449
|
}
|
|
255
450
|
|
|
256
|
-
if (props.hitbox
|
|
451
|
+
if (props.hitbox !== undefined) {
|
|
452
|
+
this.hitbox = this.normalizeHitbox(props.hitbox);
|
|
453
|
+
}
|
|
257
454
|
|
|
258
455
|
if (props.scaleMode) this.baseTexture.scaleMode = props.scaleMode;
|
|
259
456
|
else if (props.image && this.fullProps.rectangle === undefined) {
|
|
260
|
-
|
|
457
|
+
const texture = await setTexture(this.fullProps.image);
|
|
458
|
+
if (texture) {
|
|
459
|
+
this.texture = texture;
|
|
460
|
+
}
|
|
261
461
|
} else if (props.texture) {
|
|
262
|
-
|
|
462
|
+
if (isElement(props.texture)) {
|
|
463
|
+
const textureInstance = props.texture.componentInstance;
|
|
464
|
+
textureInstance.subjectInit
|
|
465
|
+
.subscribe()
|
|
466
|
+
this.texture = this.renderer?.generateTexture(props.texture.componentInstance);
|
|
467
|
+
} else {
|
|
468
|
+
this.texture = props.texture;
|
|
469
|
+
}
|
|
263
470
|
}
|
|
264
471
|
if (props.rectangle !== undefined) {
|
|
265
472
|
const { x, y, width, height } = props.rectangle?.value ?? props.rectangle;
|
|
266
473
|
const texture = await setTexture(this.fullProps.image);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
474
|
+
if (texture) {
|
|
475
|
+
this.texture = new Texture({
|
|
476
|
+
source: texture.source,
|
|
477
|
+
frame: new Rectangle(x, y, width, height),
|
|
478
|
+
});
|
|
479
|
+
}
|
|
271
480
|
}
|
|
272
|
-
}
|
|
273
481
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
|
|
277
|
-
this.subscriptionTick.unsubscribe();
|
|
278
|
-
if (this.currentAnimationContainer && this.parent instanceof Container) {
|
|
279
|
-
this.parent.removeChild(this.currentAnimationContainer);
|
|
482
|
+
if (this.hitbox && !this.spritesheet) {
|
|
483
|
+
this.applyHitboxAnchor(this.texture.width, this.texture.height);
|
|
280
484
|
}
|
|
281
485
|
}
|
|
282
486
|
|
|
487
|
+
async onDestroy(parent: Element, afterDestroy: () => void): Promise<void> {
|
|
488
|
+
const _afterDestroy = async () => {
|
|
489
|
+
// Clean up tracked assets from global loader
|
|
490
|
+
if (this.globalLoader) {
|
|
491
|
+
this.trackedAssetIds.forEach((assetId) => {
|
|
492
|
+
this.globalLoader!.removeAsset(assetId);
|
|
493
|
+
});
|
|
494
|
+
this.trackedAssetIds.clear();
|
|
495
|
+
}
|
|
496
|
+
this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
|
|
497
|
+
this.subscriptionTick.unsubscribe();
|
|
498
|
+
if (this.currentAnimationContainer && this.parent instanceof Container) {
|
|
499
|
+
this.parent.removeChild(this.currentAnimationContainer);
|
|
500
|
+
}
|
|
501
|
+
if (afterDestroy) {
|
|
502
|
+
afterDestroy();
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
await super.onDestroy(parent, _afterDestroy);
|
|
506
|
+
}
|
|
507
|
+
|
|
283
508
|
has(name: string): boolean {
|
|
284
509
|
return this.animations.has(name);
|
|
285
510
|
}
|
|
@@ -294,9 +519,37 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
294
519
|
return this.currentAnimation.name == name;
|
|
295
520
|
}
|
|
296
521
|
|
|
522
|
+
private getFirstAnimationName(): string | undefined {
|
|
523
|
+
return this.animations.keys().next().value;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private getPlayableAnimationName(preferred?: string): string | undefined {
|
|
527
|
+
if (preferred && this.has(preferred)) {
|
|
528
|
+
return preferred;
|
|
529
|
+
}
|
|
530
|
+
return this.getFirstAnimationName();
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
private getMovementAnimationName(isMoving: boolean): string | undefined {
|
|
534
|
+
const standardAnimation = isMoving
|
|
535
|
+
? StandardAnimation.Walk
|
|
536
|
+
: StandardAnimation.Stand;
|
|
537
|
+
|
|
538
|
+
if (this.has(standardAnimation)) {
|
|
539
|
+
return standardAnimation;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (this.sheetCurrentAnimation && this.has(this.sheetCurrentAnimation)) {
|
|
543
|
+
return this.sheetCurrentAnimation;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (this.currentAnimation?.name && this.has(this.currentAnimation.name)) {
|
|
547
|
+
return this.currentAnimation.name;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
297
551
|
stop() {
|
|
298
552
|
this.currentAnimation = null;
|
|
299
|
-
this.destroy();
|
|
300
553
|
}
|
|
301
554
|
|
|
302
555
|
play(name: string, params: any[] = []) {
|
|
@@ -338,7 +591,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
338
591
|
const sound = this.currentAnimation.data.sound;
|
|
339
592
|
|
|
340
593
|
if (sound) {
|
|
341
|
-
|
|
594
|
+
new Howl({
|
|
595
|
+
src: sound,
|
|
596
|
+
autoplay: true,
|
|
597
|
+
loop: false,
|
|
598
|
+
volume: 1,
|
|
599
|
+
})
|
|
342
600
|
}
|
|
343
601
|
|
|
344
602
|
// Updates immediately to avoid flickering
|
|
@@ -347,7 +605,55 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
347
605
|
});
|
|
348
606
|
}
|
|
349
607
|
|
|
608
|
+
/**
|
|
609
|
+
* Resets the sprite by destroying and recreating all animations
|
|
610
|
+
* This method clears the current animation state, destroys existing textures,
|
|
611
|
+
* and recreates all animations from the spritesheet
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* ```typescript
|
|
615
|
+
* // Reset all animations to their initial state
|
|
616
|
+
* sprite.resetAnimations();
|
|
617
|
+
*
|
|
618
|
+
* // Reset and then play a specific animation
|
|
619
|
+
* await sprite.resetAnimations();
|
|
620
|
+
* sprite.play('walk');
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
623
|
+
async resetAnimations(): Promise<void> {
|
|
624
|
+
// Stop current animation
|
|
625
|
+
this.stop();
|
|
626
|
+
|
|
627
|
+
// Clear all animations and textures
|
|
628
|
+
this.animations.clear();
|
|
629
|
+
|
|
630
|
+
// Reset animation state
|
|
631
|
+
this.currentAnimation = null;
|
|
632
|
+
this.currentAnimationContainer = null;
|
|
633
|
+
this.time = 0;
|
|
634
|
+
this.frameIndex = 0;
|
|
635
|
+
|
|
636
|
+
// Clear children
|
|
637
|
+
this.removeChildren();
|
|
638
|
+
|
|
639
|
+
// Recreate animations from spritesheet
|
|
640
|
+
if (this.spritesheet) {
|
|
641
|
+
await this.createAnimations();
|
|
642
|
+
const animationName = this.getPlayableAnimationName(this.sheetCurrentAnimation);
|
|
643
|
+
if (animationName) {
|
|
644
|
+
this.sheetCurrentAnimation = animationName;
|
|
645
|
+
this.play(animationName, [this.sheetParams]);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
350
650
|
update({ deltaRatio }) {
|
|
651
|
+
// Block animation update if element is frozen
|
|
652
|
+
const element = this.getElement();
|
|
653
|
+
if (element && isElementFrozen(element)) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
351
657
|
if (
|
|
352
658
|
!this.isPlaying() ||
|
|
353
659
|
!this.currentAnimation ||
|
|
@@ -408,26 +714,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
408
714
|
}
|
|
409
715
|
|
|
410
716
|
const realSize = getVal<"spriteRealSize">("spriteRealSize");
|
|
411
|
-
const heightOfSprite =
|
|
412
|
-
typeof realSize == "number" ? realSize : realSize?.height;
|
|
413
|
-
const widthOfSprite =
|
|
414
|
-
typeof realSize == "number" ? realSize : realSize?.width;
|
|
415
|
-
|
|
416
|
-
const applyAnchorBySize = () => {
|
|
417
|
-
if (heightOfSprite && this.hitbox) {
|
|
418
|
-
const { spriteWidth, spriteHeight } = data;
|
|
419
|
-
const w = (spriteWidth - this.hitbox.w) / 2 / spriteWidth;
|
|
420
|
-
const gap = (spriteHeight - heightOfSprite) / 2;
|
|
421
|
-
const h = (spriteHeight - this.hitbox.h - gap) / spriteHeight;
|
|
422
|
-
this.anchor.set(w, h);
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
717
|
|
|
426
718
|
if (frame.sound) {
|
|
427
719
|
//RpgSound.get(frame.sound).play()
|
|
428
720
|
}
|
|
429
721
|
|
|
430
|
-
|
|
722
|
+
this.applyHitboxAnchor(data.spriteWidth, data.spriteHeight, realSize);
|
|
431
723
|
|
|
432
724
|
applyTransform("anchor");
|
|
433
725
|
applyTransform("scale");
|
|
@@ -455,9 +747,80 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
455
747
|
this.frameIndex++;
|
|
456
748
|
}
|
|
457
749
|
}
|
|
750
|
+
|
|
751
|
+
private applyHitboxAnchor(
|
|
752
|
+
spriteWidth: number,
|
|
753
|
+
spriteHeight: number,
|
|
754
|
+
realSize?: TextureOptionsMerging["spriteRealSize"]
|
|
755
|
+
) {
|
|
756
|
+
if (!this.hitbox || !spriteWidth || !spriteHeight) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const heightOfSprite =
|
|
761
|
+
typeof realSize === "number" ? realSize : realSize?.height;
|
|
762
|
+
const resolvedHeight = heightOfSprite ?? spriteHeight;
|
|
763
|
+
const gap = Math.max(0, (spriteHeight - resolvedHeight) / 2);
|
|
764
|
+
const hitboxTopLeftX = this.clamp(
|
|
765
|
+
(spriteWidth - this.hitbox.w) / 2 / spriteWidth
|
|
766
|
+
);
|
|
767
|
+
const hitboxTopLeftY = this.clamp(
|
|
768
|
+
(spriteHeight - this.hitbox.h - gap) / spriteHeight
|
|
769
|
+
);
|
|
770
|
+
const hitboxCenterX = this.clamp(
|
|
771
|
+
hitboxTopLeftX + this.hitbox.w / 2 / spriteWidth
|
|
772
|
+
);
|
|
773
|
+
const hitboxCenterY = this.clamp(
|
|
774
|
+
hitboxTopLeftY + this.hitbox.h / 2 / spriteHeight
|
|
775
|
+
);
|
|
776
|
+
const footY = this.clamp((spriteHeight - gap) / spriteHeight);
|
|
777
|
+
|
|
778
|
+
let anchorX = hitboxTopLeftX;
|
|
779
|
+
let anchorY = hitboxTopLeftY;
|
|
780
|
+
|
|
781
|
+
switch (this.hitbox.anchorMode ?? "top-left") {
|
|
782
|
+
case "center":
|
|
783
|
+
anchorX = hitboxCenterX;
|
|
784
|
+
anchorY = hitboxCenterY;
|
|
785
|
+
break;
|
|
786
|
+
case "foot":
|
|
787
|
+
anchorX = hitboxCenterX;
|
|
788
|
+
anchorY = footY;
|
|
789
|
+
break;
|
|
790
|
+
case "top-left":
|
|
791
|
+
default:
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
this.anchor.set(anchorX, anchorY);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
private normalizeHitbox(hitbox: unknown): Hitbox | null {
|
|
799
|
+
const resolvedHitbox = (hitbox as any)?.value ?? hitbox;
|
|
800
|
+
if (!resolvedHitbox || typeof resolvedHitbox !== "object") {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const { w, h, anchorMode } = resolvedHitbox as Partial<Hitbox>;
|
|
805
|
+
if (typeof w !== "number" || typeof h !== "number") {
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
return {
|
|
810
|
+
w,
|
|
811
|
+
h,
|
|
812
|
+
anchorMode,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
private clamp(value: number) {
|
|
817
|
+
return Math.min(1, Math.max(0, value));
|
|
818
|
+
}
|
|
458
819
|
}
|
|
459
820
|
|
|
460
|
-
export interface CanvasSprite extends PixiSprite {
|
|
821
|
+
export interface CanvasSprite extends PixiSprite {
|
|
822
|
+
layout: Layout | null;
|
|
823
|
+
}
|
|
461
824
|
|
|
462
825
|
registerComponent("Sprite", CanvasSprite);
|
|
463
826
|
|
|
@@ -469,6 +832,7 @@ export interface SpriteProps extends DisplayObjectProps {
|
|
|
469
832
|
params?: any;
|
|
470
833
|
onFinish?: () => void;
|
|
471
834
|
};
|
|
835
|
+
hitbox?: Hitbox;
|
|
472
836
|
scaleMode?: number;
|
|
473
837
|
image?: string;
|
|
474
838
|
rectangle?: {
|
|
@@ -510,5 +874,7 @@ export type SpritePropTypes = SpritePropsWithImage | SpritePropsWithSheet;
|
|
|
510
874
|
|
|
511
875
|
// Update the Sprite function to use the props interface
|
|
512
876
|
export const Sprite: ComponentFunction<SpritePropTypes> = (props) => {
|
|
877
|
+
// Ensure component is registered in test environments where module cache may differ
|
|
878
|
+
registerComponent("Sprite", CanvasSprite);
|
|
513
879
|
return createComponent("Sprite", props);
|
|
514
880
|
};
|