canvasengine 2.0.1-beta.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/Container.d.ts +25 -0
- package/dist/components/Container.d.ts.map +1 -0
- package/dist/components/DOMContainer.d.ts +28 -0
- package/dist/components/DOMContainer.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/DisplayObject.d.ts +18 -0
- package/dist/components/DisplayObject.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 +37 -0
- package/dist/components/Joystick.d.ts.map +1 -0
- package/dist/components/Mesh.d.ts +138 -0
- package/dist/components/Mesh.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/Sprite.d.ts +182 -0
- package/dist/components/Sprite.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/Viewport.d.ts +52 -0
- package/dist/components/Viewport.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 +127 -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/SpriteEffects.d.ts +70 -0
- package/dist/directives/SpriteEffects.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 +15 -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 +139 -0
- package/dist/engine/reactive.d.ts.map +1 -0
- package/dist/engine/signal.d.ts +74 -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 +53 -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 -844
- package/dist/index.d.ts.map +1 -0
- package/dist/index.global.js +189 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +15156 -3287
- 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/index.d.ts +1 -3
- package/package.json +19 -52
- package/src/components/Button.ts +406 -0
- package/src/components/Canvas.ts +82 -51
- package/src/components/Container.ts +31 -4
- package/src/components/DOMContainer.ts +387 -0
- package/src/components/DOMElement.ts +556 -0
- package/src/components/DOMSprite.ts +1044 -0
- package/src/components/DisplayObject.ts +423 -202
- package/src/components/FocusContainer.ts +368 -0
- package/src/components/Graphic.ts +255 -68
- package/src/components/Joystick.ts +269 -0
- package/src/components/Mesh.ts +239 -0
- package/src/components/NineSliceSprite.ts +4 -1
- package/src/components/ParticleEmitter.ts +12 -8
- package/src/components/Sprite.ts +441 -54
- package/src/components/Text.ts +270 -26
- package/src/components/Video.ts +110 -0
- package/src/components/Viewport.ts +122 -63
- package/src/components/index.ts +12 -5
- package/src/components/types/DisplayObject.ts +64 -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 +86 -431
- package/src/directives/Scheduler.ts +21 -16
- package/src/directives/Shake.ts +298 -0
- package/src/directives/Sound.ts +94 -31
- package/src/directives/SpriteEffects.ts +461 -0
- package/src/directives/ViewportFollow.ts +40 -9
- package/src/directives/index.ts +14 -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 +1266 -167
- package/src/engine/signal.ts +335 -47
- package/src/engine/trigger.ts +95 -12
- package/src/engine/utils.ts +101 -9
- package/src/hooks/useFocus.ts +91 -0
- package/src/hooks/useProps.ts +92 -7
- package/src/index.ts +13 -6
- package/src/types/pixi-cull.d.ts +7 -0
- package/src/utils/GlobalAssetLoader.ts +257 -0
- package/src/utils/RadialGradient.ts +29 -0
- package/src/utils/functions.ts +7 -0
- package/src/utils/tabindex.ts +70 -0
- package/testing/index.ts +42 -0
- package/tsconfig.json +15 -25
- package/vite.config.ts +39 -0
- package/.cursorrules +0 -0
- package/.github/workflows/ci.yml +0 -29
- package/README.md +0 -79
- package/dist/compiler/vite.js +0 -119
- package/dist/compiler/vite.js.map +0 -1
- package/logo.png +0 -0
- package/src/compiler/grammar.pegjs +0 -180
- package/src/compiler/vite.ts +0 -166
- package/src/components/DrawMap/index.ts +0 -65
- package/src/components/Tilemap/Tile.ts +0 -79
- package/src/components/Tilemap/TileGroup.ts +0 -207
- package/src/components/Tilemap/TileLayer.ts +0 -163
- package/src/components/Tilemap/TileSet.ts +0 -41
- package/src/components/Tilemap/index.ts +0 -80
- package/src/presets/Bar.ts +0 -89
- package/src/presets/Button.ts +0 -0
- package/src/presets/Joystick.ts +0 -286
- package/src/presets/NightAmbiant.ts +0 -122
- package/src/presets/Particle.ts +0 -53
- package/starter/assets/logo.png +0 -0
- package/starter/components/app.ce +0 -18
- package/starter/components/hello.ce +0 -35
- package/starter/index.html +0 -21
- package/starter/main.ts +0 -6
- package/starter/package.json +0 -20
- package/starter/tsconfig.json +0 -32
- package/starter/vite.config.ts +0 -12
- package/tsconfig.node.json +0 -10
- package/tsup.config.ts +0 -28
- package/vitest.config.ts +0 -12
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,57 +375,136 @@ 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
|
|
|
230
|
-
|
|
391
|
+
// Initialize globalLoader from context if not already set
|
|
392
|
+
if (!this.globalLoader && props.context?.globalLoader) {
|
|
393
|
+
this.globalLoader = props.context.globalLoader;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const setTexture = async (image: string) => {
|
|
397
|
+
if (!image || typeof image !== 'string' || image.trim() === '') {
|
|
398
|
+
console.warn('Invalid image path provided to setTexture:', image);
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Register asset in global loader if available
|
|
403
|
+
let assetId: string | null = null;
|
|
404
|
+
if (this.globalLoader) {
|
|
405
|
+
assetId = this.globalLoader.registerAsset(image);
|
|
406
|
+
this.trackedAssetIds.add(assetId);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const onProgress = this.fullProps.loader?.onProgress;
|
|
410
|
+
const texture = await Assets.load(image, (progress) => {
|
|
411
|
+
// Update global loader progress
|
|
412
|
+
if (this.globalLoader && assetId) {
|
|
413
|
+
this.globalLoader.updateProgress(assetId, progress);
|
|
414
|
+
}
|
|
415
|
+
// Call local loader callback if provided
|
|
416
|
+
if (onProgress) onProgress(progress);
|
|
417
|
+
if (progress == 1) {
|
|
418
|
+
// Mark as complete in global loader
|
|
419
|
+
if (this.globalLoader && assetId) {
|
|
420
|
+
this.globalLoader.completeAsset(assetId);
|
|
421
|
+
}
|
|
422
|
+
const onComplete = this.fullProps.loader?.onComplete;
|
|
423
|
+
if (onComplete) {
|
|
424
|
+
// hack to memoize the texture
|
|
425
|
+
setTimeout(() => {
|
|
426
|
+
onComplete(texture);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
return texture
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const sheet = props.sheet
|
|
436
|
+
const definition = props.sheet?.definition ?? {};
|
|
437
|
+
|
|
438
|
+
if (definition?.type === 'reset') {
|
|
439
|
+
const resolvedValue = definition.value instanceof Promise ? await definition.value : definition.value;
|
|
440
|
+
this.spritesheet = resolvedValue ?? definition;
|
|
441
|
+
await this.resetAnimations();
|
|
442
|
+
}
|
|
443
|
+
|
|
231
444
|
if (sheet?.params) this.sheetParams = sheet?.params;
|
|
232
445
|
|
|
233
|
-
if (sheet?.playing && this.isMounted) {
|
|
446
|
+
if (sheet?.playing && this.isMounted && this.spritesheet && this.animations.size > 0) {
|
|
234
447
|
this.sheetCurrentAnimation = sheet?.playing;
|
|
235
448
|
this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
236
449
|
}
|
|
237
450
|
|
|
238
|
-
if (props.hitbox
|
|
451
|
+
if (props.hitbox !== undefined) {
|
|
452
|
+
this.hitbox = this.normalizeHitbox(props.hitbox);
|
|
453
|
+
}
|
|
239
454
|
|
|
240
455
|
if (props.scaleMode) this.baseTexture.scaleMode = props.scaleMode;
|
|
241
456
|
else if (props.image && this.fullProps.rectangle === undefined) {
|
|
242
|
-
|
|
457
|
+
const texture = await setTexture(this.fullProps.image);
|
|
458
|
+
if (texture) {
|
|
459
|
+
this.texture = texture;
|
|
460
|
+
}
|
|
243
461
|
} else if (props.texture) {
|
|
244
|
-
|
|
462
|
+
if (isElement(props.texture)) {
|
|
463
|
+
const textureInstance = props.texture.componentInstance;
|
|
464
|
+
textureInstance.subjectInit
|
|
465
|
+
.subscribe()
|
|
466
|
+
this.texture = this.renderer?.generateTexture(props.texture.componentInstance);
|
|
467
|
+
} else {
|
|
468
|
+
this.texture = props.texture;
|
|
469
|
+
}
|
|
245
470
|
}
|
|
246
|
-
|
|
247
471
|
if (props.rectangle !== undefined) {
|
|
248
472
|
const { x, y, width, height } = props.rectangle?.value ?? props.rectangle;
|
|
249
|
-
const texture = await
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
473
|
+
const texture = await setTexture(this.fullProps.image);
|
|
474
|
+
if (texture) {
|
|
475
|
+
this.texture = new Texture({
|
|
476
|
+
source: texture.source,
|
|
477
|
+
frame: new Rectangle(x, y, width, height),
|
|
478
|
+
});
|
|
479
|
+
}
|
|
254
480
|
}
|
|
255
|
-
}
|
|
256
481
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
|
|
260
|
-
this.subscriptionTick.unsubscribe();
|
|
261
|
-
if (this.currentAnimationContainer && this.parent instanceof Container) {
|
|
262
|
-
this.parent.removeChild(this.currentAnimationContainer);
|
|
482
|
+
if (this.hitbox && !this.spritesheet) {
|
|
483
|
+
this.applyHitboxAnchor(this.texture.width, this.texture.height);
|
|
263
484
|
}
|
|
264
485
|
}
|
|
265
486
|
|
|
487
|
+
async onDestroy(parent: Element, afterDestroy: () => void): Promise<void> {
|
|
488
|
+
const _afterDestroy = async () => {
|
|
489
|
+
// Clean up tracked assets from global loader
|
|
490
|
+
if (this.globalLoader) {
|
|
491
|
+
this.trackedAssetIds.forEach((assetId) => {
|
|
492
|
+
this.globalLoader!.removeAsset(assetId);
|
|
493
|
+
});
|
|
494
|
+
this.trackedAssetIds.clear();
|
|
495
|
+
}
|
|
496
|
+
this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
|
|
497
|
+
this.subscriptionTick.unsubscribe();
|
|
498
|
+
if (this.currentAnimationContainer && this.parent instanceof Container) {
|
|
499
|
+
this.parent.removeChild(this.currentAnimationContainer);
|
|
500
|
+
}
|
|
501
|
+
if (afterDestroy) {
|
|
502
|
+
afterDestroy();
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
await super.onDestroy(parent, _afterDestroy);
|
|
506
|
+
}
|
|
507
|
+
|
|
266
508
|
has(name: string): boolean {
|
|
267
509
|
return this.animations.has(name);
|
|
268
510
|
}
|
|
@@ -277,9 +519,37 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
277
519
|
return this.currentAnimation.name == name;
|
|
278
520
|
}
|
|
279
521
|
|
|
522
|
+
private getFirstAnimationName(): string | undefined {
|
|
523
|
+
return this.animations.keys().next().value;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private getPlayableAnimationName(preferred?: string): string | undefined {
|
|
527
|
+
if (preferred && this.has(preferred)) {
|
|
528
|
+
return preferred;
|
|
529
|
+
}
|
|
530
|
+
return this.getFirstAnimationName();
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
private getMovementAnimationName(isMoving: boolean): string | undefined {
|
|
534
|
+
const standardAnimation = isMoving
|
|
535
|
+
? StandardAnimation.Walk
|
|
536
|
+
: StandardAnimation.Stand;
|
|
537
|
+
|
|
538
|
+
if (this.has(standardAnimation)) {
|
|
539
|
+
return standardAnimation;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (this.sheetCurrentAnimation && this.has(this.sheetCurrentAnimation)) {
|
|
543
|
+
return this.sheetCurrentAnimation;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (this.currentAnimation?.name && this.has(this.currentAnimation.name)) {
|
|
547
|
+
return this.currentAnimation.name;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
280
551
|
stop() {
|
|
281
552
|
this.currentAnimation = null;
|
|
282
|
-
this.destroy();
|
|
283
553
|
}
|
|
284
554
|
|
|
285
555
|
play(name: string, params: any[] = []) {
|
|
@@ -321,7 +591,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
321
591
|
const sound = this.currentAnimation.data.sound;
|
|
322
592
|
|
|
323
593
|
if (sound) {
|
|
324
|
-
|
|
594
|
+
new Howl({
|
|
595
|
+
src: sound,
|
|
596
|
+
autoplay: true,
|
|
597
|
+
loop: false,
|
|
598
|
+
volume: 1,
|
|
599
|
+
})
|
|
325
600
|
}
|
|
326
601
|
|
|
327
602
|
// Updates immediately to avoid flickering
|
|
@@ -330,7 +605,55 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
330
605
|
});
|
|
331
606
|
}
|
|
332
607
|
|
|
608
|
+
/**
|
|
609
|
+
* Resets the sprite by destroying and recreating all animations
|
|
610
|
+
* This method clears the current animation state, destroys existing textures,
|
|
611
|
+
* and recreates all animations from the spritesheet
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* ```typescript
|
|
615
|
+
* // Reset all animations to their initial state
|
|
616
|
+
* sprite.resetAnimations();
|
|
617
|
+
*
|
|
618
|
+
* // Reset and then play a specific animation
|
|
619
|
+
* await sprite.resetAnimations();
|
|
620
|
+
* sprite.play('walk');
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
623
|
+
async resetAnimations(): Promise<void> {
|
|
624
|
+
// Stop current animation
|
|
625
|
+
this.stop();
|
|
626
|
+
|
|
627
|
+
// Clear all animations and textures
|
|
628
|
+
this.animations.clear();
|
|
629
|
+
|
|
630
|
+
// Reset animation state
|
|
631
|
+
this.currentAnimation = null;
|
|
632
|
+
this.currentAnimationContainer = null;
|
|
633
|
+
this.time = 0;
|
|
634
|
+
this.frameIndex = 0;
|
|
635
|
+
|
|
636
|
+
// Clear children
|
|
637
|
+
this.removeChildren();
|
|
638
|
+
|
|
639
|
+
// Recreate animations from spritesheet
|
|
640
|
+
if (this.spritesheet) {
|
|
641
|
+
await this.createAnimations();
|
|
642
|
+
const animationName = this.getPlayableAnimationName(this.sheetCurrentAnimation);
|
|
643
|
+
if (animationName) {
|
|
644
|
+
this.sheetCurrentAnimation = animationName;
|
|
645
|
+
this.play(animationName, [this.sheetParams]);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
333
650
|
update({ deltaRatio }) {
|
|
651
|
+
// Block animation update if element is frozen
|
|
652
|
+
const element = this.getElement();
|
|
653
|
+
if (element && isElementFrozen(element)) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
334
657
|
if (
|
|
335
658
|
!this.isPlaying() ||
|
|
336
659
|
!this.currentAnimation ||
|
|
@@ -391,26 +714,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
391
714
|
}
|
|
392
715
|
|
|
393
716
|
const realSize = getVal<"spriteRealSize">("spriteRealSize");
|
|
394
|
-
const heightOfSprite =
|
|
395
|
-
typeof realSize == "number" ? realSize : realSize?.height;
|
|
396
|
-
const widthOfSprite =
|
|
397
|
-
typeof realSize == "number" ? realSize : realSize?.width;
|
|
398
|
-
|
|
399
|
-
const applyAnchorBySize = () => {
|
|
400
|
-
if (heightOfSprite && this.hitbox) {
|
|
401
|
-
const { spriteWidth, spriteHeight } = data;
|
|
402
|
-
const w = (spriteWidth - this.hitbox.w) / 2 / spriteWidth;
|
|
403
|
-
const gap = (spriteHeight - heightOfSprite) / 2;
|
|
404
|
-
const h = (spriteHeight - this.hitbox.h - gap) / spriteHeight;
|
|
405
|
-
this.anchor.set(w, h);
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
717
|
|
|
409
718
|
if (frame.sound) {
|
|
410
719
|
//RpgSound.get(frame.sound).play()
|
|
411
720
|
}
|
|
412
721
|
|
|
413
|
-
|
|
722
|
+
this.applyHitboxAnchor(data.spriteWidth, data.spriteHeight, realSize);
|
|
414
723
|
|
|
415
724
|
applyTransform("anchor");
|
|
416
725
|
applyTransform("scale");
|
|
@@ -438,9 +747,80 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
438
747
|
this.frameIndex++;
|
|
439
748
|
}
|
|
440
749
|
}
|
|
750
|
+
|
|
751
|
+
private applyHitboxAnchor(
|
|
752
|
+
spriteWidth: number,
|
|
753
|
+
spriteHeight: number,
|
|
754
|
+
realSize?: TextureOptionsMerging["spriteRealSize"]
|
|
755
|
+
) {
|
|
756
|
+
if (!this.hitbox || !spriteWidth || !spriteHeight || !this.anchor) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const heightOfSprite =
|
|
761
|
+
typeof realSize === "number" ? realSize : realSize?.height;
|
|
762
|
+
const resolvedHeight = heightOfSprite ?? spriteHeight;
|
|
763
|
+
const gap = Math.max(0, (spriteHeight - resolvedHeight) / 2);
|
|
764
|
+
const hitboxTopLeftX = this.clamp(
|
|
765
|
+
(spriteWidth - this.hitbox.w) / 2 / spriteWidth
|
|
766
|
+
);
|
|
767
|
+
const hitboxTopLeftY = this.clamp(
|
|
768
|
+
(spriteHeight - this.hitbox.h - gap) / spriteHeight
|
|
769
|
+
);
|
|
770
|
+
const hitboxCenterX = this.clamp(
|
|
771
|
+
hitboxTopLeftX + this.hitbox.w / 2 / spriteWidth
|
|
772
|
+
);
|
|
773
|
+
const hitboxCenterY = this.clamp(
|
|
774
|
+
hitboxTopLeftY + this.hitbox.h / 2 / spriteHeight
|
|
775
|
+
);
|
|
776
|
+
const footY = this.clamp((spriteHeight - gap) / spriteHeight);
|
|
777
|
+
|
|
778
|
+
let anchorX = hitboxTopLeftX;
|
|
779
|
+
let anchorY = hitboxTopLeftY;
|
|
780
|
+
|
|
781
|
+
switch (this.hitbox.anchorMode ?? "top-left") {
|
|
782
|
+
case "center":
|
|
783
|
+
anchorX = hitboxCenterX;
|
|
784
|
+
anchorY = hitboxCenterY;
|
|
785
|
+
break;
|
|
786
|
+
case "foot":
|
|
787
|
+
anchorX = hitboxCenterX;
|
|
788
|
+
anchorY = footY;
|
|
789
|
+
break;
|
|
790
|
+
case "top-left":
|
|
791
|
+
default:
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
this.anchor.set(anchorX, anchorY);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
private normalizeHitbox(hitbox: unknown): Hitbox | null {
|
|
799
|
+
const resolvedHitbox = (hitbox as any)?.value ?? hitbox;
|
|
800
|
+
if (!resolvedHitbox || typeof resolvedHitbox !== "object") {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const { w, h, anchorMode } = resolvedHitbox as Partial<Hitbox>;
|
|
805
|
+
if (typeof w !== "number" || typeof h !== "number") {
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
return {
|
|
810
|
+
w,
|
|
811
|
+
h,
|
|
812
|
+
anchorMode,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
private clamp(value: number) {
|
|
817
|
+
return Math.min(1, Math.max(0, value));
|
|
818
|
+
}
|
|
441
819
|
}
|
|
442
820
|
|
|
443
|
-
export interface CanvasSprite extends PixiSprite {
|
|
821
|
+
export interface CanvasSprite extends PixiSprite {
|
|
822
|
+
layout: Layout | null;
|
|
823
|
+
}
|
|
444
824
|
|
|
445
825
|
registerComponent("Sprite", CanvasSprite);
|
|
446
826
|
|
|
@@ -452,6 +832,7 @@ export interface SpriteProps extends DisplayObjectProps {
|
|
|
452
832
|
params?: any;
|
|
453
833
|
onFinish?: () => void;
|
|
454
834
|
};
|
|
835
|
+
hitbox?: Hitbox;
|
|
455
836
|
scaleMode?: number;
|
|
456
837
|
image?: string;
|
|
457
838
|
rectangle?: {
|
|
@@ -483,11 +864,17 @@ export interface SpritePropsWithSheet
|
|
|
483
864
|
params?: any;
|
|
484
865
|
onFinish?: () => void;
|
|
485
866
|
};
|
|
867
|
+
loader?: {
|
|
868
|
+
onProgress?: (progress: number) => void;
|
|
869
|
+
onComplete?: (texture: Texture) => void;
|
|
870
|
+
};
|
|
486
871
|
}
|
|
487
872
|
|
|
488
873
|
export type SpritePropTypes = SpritePropsWithImage | SpritePropsWithSheet;
|
|
489
874
|
|
|
490
875
|
// Update the Sprite function to use the props interface
|
|
491
876
|
export const Sprite: ComponentFunction<SpritePropTypes> = (props) => {
|
|
877
|
+
// Ensure component is registered in test environments where module cache may differ
|
|
878
|
+
registerComponent("Sprite", CanvasSprite);
|
|
492
879
|
return createComponent("Sprite", props);
|
|
493
880
|
};
|