canvasengine 2.0.0-beta.37 → 2.0.0-beta.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DebugRenderer-CTWPthRt.js → DebugRenderer-Rrw9FlTd.js} +2 -2
- package/dist/{DebugRenderer-CTWPthRt.js.map → DebugRenderer-Rrw9FlTd.js.map} +1 -1
- package/dist/components/Button.d.ts +50 -3
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Canvas.d.ts.map +1 -1
- package/dist/components/Joystick.d.ts +36 -0
- package/dist/components/Joystick.d.ts.map +1 -0
- package/dist/components/Sprite.d.ts +2 -0
- package/dist/components/Sprite.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/types/Spritesheet.d.ts +0 -118
- package/dist/components/types/Spritesheet.d.ts.map +1 -1
- package/dist/directives/Controls.d.ts +16 -7
- package/dist/directives/Controls.d.ts.map +1 -1
- package/dist/directives/GamepadControls.d.ts +3 -1
- package/dist/directives/GamepadControls.d.ts.map +1 -1
- package/dist/directives/JoystickControls.d.ts +172 -0
- package/dist/directives/JoystickControls.d.ts.map +1 -0
- package/dist/directives/index.d.ts +1 -0
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/engine/signal.d.ts.map +1 -1
- package/dist/{index-BqwprEPH.js → index-BQ99FClW.js} +6057 -5433
- package/dist/index-BQ99FClW.js.map +1 -0
- package/dist/index.global.js +7 -7
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +59 -57
- package/dist/utils/GlobalAssetLoader.d.ts +141 -0
- package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Button.ts +168 -41
- package/src/components/Canvas.ts +3 -0
- package/src/components/Joystick.ts +361 -0
- package/src/components/Sprite.ts +82 -16
- package/src/components/index.ts +2 -1
- package/src/components/types/Spritesheet.ts +0 -118
- package/src/directives/Controls.ts +42 -8
- package/src/directives/GamepadControls.ts +40 -18
- package/src/directives/JoystickControls.ts +396 -0
- package/src/directives/index.ts +1 -0
- package/src/engine/reactive.ts +362 -242
- package/src/engine/signal.ts +8 -2
- package/src/utils/GlobalAssetLoader.ts +257 -0
- package/dist/index-BqwprEPH.js.map +0 -1
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Joystick
|
|
3
|
+
*
|
|
4
|
+
* Inspired by https://github.com/endel/pixi-virtual-joystick
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as PIXI from "pixi.js";
|
|
8
|
+
import { Container, Graphics, Sprite, h, signal, isSignal } from "../";
|
|
9
|
+
|
|
10
|
+
export interface JoystickChangeEvent {
|
|
11
|
+
angle: number;
|
|
12
|
+
direction: Direction;
|
|
13
|
+
power: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export enum Direction {
|
|
17
|
+
LEFT = "left",
|
|
18
|
+
TOP = "top",
|
|
19
|
+
BOTTOM = "bottom",
|
|
20
|
+
RIGHT = "right",
|
|
21
|
+
TOP_LEFT = "top_left",
|
|
22
|
+
TOP_RIGHT = "top_right",
|
|
23
|
+
BOTTOM_LEFT = "bottom_left",
|
|
24
|
+
BOTTOM_RIGHT = "bottom_right",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface JoystickSettings {
|
|
28
|
+
outer?: string;
|
|
29
|
+
inner?: string;
|
|
30
|
+
outerScale?: { x: number; y: number };
|
|
31
|
+
innerScale?: { x: number; y: number };
|
|
32
|
+
innerColor?: string;
|
|
33
|
+
outerColor?: string;
|
|
34
|
+
onChange?: (data: JoystickChangeEvent) => void;
|
|
35
|
+
onStart?: () => void;
|
|
36
|
+
onEnd?: () => void;
|
|
37
|
+
/** Controls instance to automatically apply joystick events to (e.g., JoystickControls or ControlsDirective) */
|
|
38
|
+
controls?: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function Joystick(opts: JoystickSettings = {}) {
|
|
42
|
+
const settings = Object.assign(
|
|
43
|
+
{
|
|
44
|
+
outerScale: { x: 1, y: 1 },
|
|
45
|
+
innerScale: { x: 1, y: 1 },
|
|
46
|
+
innerColor: "black",
|
|
47
|
+
outerColor: "black",
|
|
48
|
+
},
|
|
49
|
+
opts
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Unwrap controls if it's a signal
|
|
53
|
+
const getControls = () => {
|
|
54
|
+
if (isSignal(settings.controls)) {
|
|
55
|
+
return settings.controls();
|
|
56
|
+
}
|
|
57
|
+
return settings.controls;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
let outerRadius = 70;
|
|
61
|
+
let innerRadius = 10;
|
|
62
|
+
const innerAlphaStandby = 0.5;
|
|
63
|
+
|
|
64
|
+
let dragging = false;
|
|
65
|
+
let startPosition: PIXI.PointData | null = null;
|
|
66
|
+
let power = 0;
|
|
67
|
+
|
|
68
|
+
const innerPositionX = signal(0);
|
|
69
|
+
const innerPositionY = signal(0);
|
|
70
|
+
const innerAlpha = signal(innerAlphaStandby);
|
|
71
|
+
|
|
72
|
+
function getPower(centerPoint: PIXI.Point) {
|
|
73
|
+
const a = centerPoint.x - 0;
|
|
74
|
+
const b = centerPoint.y - 0;
|
|
75
|
+
return Math.min(1, Math.sqrt(a * a + b * b) / outerRadius);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getDirection(center: PIXI.Point) {
|
|
79
|
+
let rad = Math.atan2(center.y, center.x); // [-PI, PI]
|
|
80
|
+
if ((rad >= -Math.PI / 8 && rad < 0) || (rad >= 0 && rad < Math.PI / 8)) {
|
|
81
|
+
return Direction.RIGHT;
|
|
82
|
+
} else if (rad >= Math.PI / 8 && rad < (3 * Math.PI) / 8) {
|
|
83
|
+
return Direction.BOTTOM_RIGHT;
|
|
84
|
+
} else if (rad >= (3 * Math.PI) / 8 && rad < (5 * Math.PI) / 8) {
|
|
85
|
+
return Direction.BOTTOM;
|
|
86
|
+
} else if (rad >= (5 * Math.PI) / 8 && rad < (7 * Math.PI) / 8) {
|
|
87
|
+
return Direction.BOTTOM_LEFT;
|
|
88
|
+
} else if (
|
|
89
|
+
(rad >= (7 * Math.PI) / 8 && rad < Math.PI) ||
|
|
90
|
+
(rad >= -Math.PI && rad < (-7 * Math.PI) / 8)
|
|
91
|
+
) {
|
|
92
|
+
return Direction.LEFT;
|
|
93
|
+
} else if (rad >= (-7 * Math.PI) / 8 && rad < (-5 * Math.PI) / 8) {
|
|
94
|
+
return Direction.TOP_LEFT;
|
|
95
|
+
} else if (rad >= (-5 * Math.PI) / 8 && rad < (-3 * Math.PI) / 8) {
|
|
96
|
+
return Direction.TOP;
|
|
97
|
+
} else {
|
|
98
|
+
return Direction.TOP_RIGHT;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function handleDragStart(event: any) {
|
|
103
|
+
startPosition = event.getLocalPosition(this);
|
|
104
|
+
dragging = true;
|
|
105
|
+
innerAlpha.set(1);
|
|
106
|
+
settings.onStart?.();
|
|
107
|
+
|
|
108
|
+
// Notify controls if provided
|
|
109
|
+
const controls = getControls();
|
|
110
|
+
if (controls) {
|
|
111
|
+
// Check if it's JoystickControls instance
|
|
112
|
+
if (controls.handleJoystickStart) {
|
|
113
|
+
controls.handleJoystickStart();
|
|
114
|
+
}
|
|
115
|
+
// Check if it's ControlsDirective with joystick getter
|
|
116
|
+
else if (controls.joystick && controls.joystick.handleJoystickStart) {
|
|
117
|
+
controls.joystick.handleJoystickStart();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function handleDragEnd() {
|
|
123
|
+
if (!dragging) return;
|
|
124
|
+
innerPositionX.set(0);
|
|
125
|
+
innerPositionY.set(0);
|
|
126
|
+
dragging = false;
|
|
127
|
+
innerAlpha.set(innerAlphaStandby);
|
|
128
|
+
settings.onEnd?.();
|
|
129
|
+
|
|
130
|
+
// Notify controls if provided
|
|
131
|
+
const controls = getControls();
|
|
132
|
+
if (controls) {
|
|
133
|
+
// Check if it's JoystickControls instance
|
|
134
|
+
if (controls.handleJoystickEnd) {
|
|
135
|
+
controls.handleJoystickEnd();
|
|
136
|
+
}
|
|
137
|
+
// Check if it's ControlsDirective with joystick getter
|
|
138
|
+
else if (controls.joystick && controls.joystick.handleJoystickEnd) {
|
|
139
|
+
controls.joystick.handleJoystickEnd();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function handleDragMove(event: any) {
|
|
145
|
+
if (dragging == false) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let newPosition = event.getLocalPosition(this);
|
|
150
|
+
|
|
151
|
+
let sideX = newPosition.x - (startPosition?.x ?? 0);
|
|
152
|
+
let sideY = newPosition.y - (startPosition?.y ?? 0);
|
|
153
|
+
|
|
154
|
+
let centerPoint = new PIXI.Point(0, 0);
|
|
155
|
+
let angle = 0;
|
|
156
|
+
|
|
157
|
+
if (sideX == 0 && sideY == 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let calRadius = 0;
|
|
162
|
+
|
|
163
|
+
if (sideX * sideX + sideY * sideY >= outerRadius * outerRadius) {
|
|
164
|
+
calRadius = outerRadius;
|
|
165
|
+
} else {
|
|
166
|
+
calRadius = outerRadius - innerRadius;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* x: -1 <-> 1
|
|
171
|
+
* y: -1 <-> 1
|
|
172
|
+
* Y
|
|
173
|
+
* ^
|
|
174
|
+
* |
|
|
175
|
+
* 180 | 90
|
|
176
|
+
* ------------> X
|
|
177
|
+
* 270 | 360
|
|
178
|
+
* |
|
|
179
|
+
* |
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
let direction = Direction.LEFT;
|
|
183
|
+
|
|
184
|
+
if (sideX == 0) {
|
|
185
|
+
if (sideY > 0) {
|
|
186
|
+
centerPoint.set(0, sideY > outerRadius ? outerRadius : sideY);
|
|
187
|
+
angle = 270;
|
|
188
|
+
direction = Direction.BOTTOM;
|
|
189
|
+
} else {
|
|
190
|
+
centerPoint.set(
|
|
191
|
+
0,
|
|
192
|
+
-(Math.abs(sideY) > outerRadius ? outerRadius : Math.abs(sideY))
|
|
193
|
+
);
|
|
194
|
+
angle = 90;
|
|
195
|
+
direction = Direction.TOP;
|
|
196
|
+
}
|
|
197
|
+
innerPositionX.set(centerPoint.x);
|
|
198
|
+
innerPositionY.set(centerPoint.y);
|
|
199
|
+
power = getPower(centerPoint);
|
|
200
|
+
const changeEvent = { angle, direction, power };
|
|
201
|
+
settings.onChange?.(changeEvent);
|
|
202
|
+
|
|
203
|
+
// Notify controls if provided
|
|
204
|
+
const controls = getControls();
|
|
205
|
+
if (controls) {
|
|
206
|
+
// Check if it's JoystickControls instance
|
|
207
|
+
if (controls.handleJoystickChange) {
|
|
208
|
+
controls.handleJoystickChange(changeEvent);
|
|
209
|
+
}
|
|
210
|
+
// Check if it's ControlsDirective with joystick getter
|
|
211
|
+
else if (controls.joystick && controls.joystick.handleJoystickChange) {
|
|
212
|
+
controls.joystick.handleJoystickChange(changeEvent);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (sideY == 0) {
|
|
219
|
+
if (sideX > 0) {
|
|
220
|
+
centerPoint.set(
|
|
221
|
+
Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX),
|
|
222
|
+
0
|
|
223
|
+
);
|
|
224
|
+
angle = 0;
|
|
225
|
+
direction = Direction.RIGHT;
|
|
226
|
+
} else {
|
|
227
|
+
centerPoint.set(
|
|
228
|
+
-(Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX)),
|
|
229
|
+
0
|
|
230
|
+
);
|
|
231
|
+
angle = 180;
|
|
232
|
+
direction = Direction.LEFT;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
innerPositionX.set(centerPoint.x);
|
|
236
|
+
innerPositionY.set(centerPoint.y);
|
|
237
|
+
power = getPower(centerPoint);
|
|
238
|
+
const changeEvent = { angle, direction, power };
|
|
239
|
+
settings.onChange?.(changeEvent);
|
|
240
|
+
|
|
241
|
+
// Notify controls if provided
|
|
242
|
+
const controls = getControls();
|
|
243
|
+
if (controls) {
|
|
244
|
+
// Check if it's JoystickControls instance
|
|
245
|
+
if (controls.handleJoystickChange) {
|
|
246
|
+
controls.handleJoystickChange(changeEvent);
|
|
247
|
+
}
|
|
248
|
+
// Check if it's ControlsDirective with joystick getter
|
|
249
|
+
else if (controls.joystick && controls.joystick.handleJoystickChange) {
|
|
250
|
+
controls.joystick.handleJoystickChange(changeEvent);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let tanVal = Math.abs(sideY / sideX);
|
|
257
|
+
let radian = Math.atan(tanVal);
|
|
258
|
+
angle = (radian * 180) / Math.PI;
|
|
259
|
+
|
|
260
|
+
let centerX = 0;
|
|
261
|
+
let centerY = 0;
|
|
262
|
+
|
|
263
|
+
if (sideX * sideX + sideY * sideY >= outerRadius * outerRadius) {
|
|
264
|
+
centerX = outerRadius * Math.cos(radian);
|
|
265
|
+
centerY = outerRadius * Math.sin(radian);
|
|
266
|
+
} else {
|
|
267
|
+
centerX = Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX);
|
|
268
|
+
centerY = Math.abs(sideY) > outerRadius ? outerRadius : Math.abs(sideY);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (sideY < 0) {
|
|
272
|
+
centerY = -Math.abs(centerY);
|
|
273
|
+
}
|
|
274
|
+
if (sideX < 0) {
|
|
275
|
+
centerX = -Math.abs(centerX);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (sideX > 0 && sideY < 0) {
|
|
279
|
+
// < 90
|
|
280
|
+
} else if (sideX < 0 && sideY < 0) {
|
|
281
|
+
// 90 ~ 180
|
|
282
|
+
angle = 180 - angle;
|
|
283
|
+
} else if (sideX < 0 && sideY > 0) {
|
|
284
|
+
// 180 ~ 270
|
|
285
|
+
angle = angle + 180;
|
|
286
|
+
} else if (sideX > 0 && sideY > 0) {
|
|
287
|
+
// 270 ~ 369
|
|
288
|
+
angle = 360 - angle;
|
|
289
|
+
}
|
|
290
|
+
centerPoint.set(centerX, centerY);
|
|
291
|
+
power = getPower(centerPoint);
|
|
292
|
+
|
|
293
|
+
direction = getDirection(centerPoint);
|
|
294
|
+
innerPositionX.set(centerPoint.x);
|
|
295
|
+
innerPositionY.set(centerPoint.y);
|
|
296
|
+
const changeEvent = { angle, direction, power };
|
|
297
|
+
settings.onChange?.(changeEvent);
|
|
298
|
+
|
|
299
|
+
// Notify controls if provided
|
|
300
|
+
const controls = getControls();
|
|
301
|
+
if (controls) {
|
|
302
|
+
// Check if it's JoystickControls instance
|
|
303
|
+
if (controls.handleJoystickChange) {
|
|
304
|
+
controls.handleJoystickChange(changeEvent);
|
|
305
|
+
}
|
|
306
|
+
// Check if it's ControlsDirective with joystick getter
|
|
307
|
+
else if (controls.joystick && controls.joystick.handleJoystickChange) {
|
|
308
|
+
controls.joystick.handleJoystickChange(changeEvent);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let innerElement;
|
|
314
|
+
let outerElement;
|
|
315
|
+
|
|
316
|
+
if (!settings.outer) {
|
|
317
|
+
outerElement = h(Graphics, {
|
|
318
|
+
draw: (g) => {
|
|
319
|
+
g.circle(0, 0, outerRadius).fill(settings.outerColor);
|
|
320
|
+
},
|
|
321
|
+
alpha: 0.5,
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
outerElement = h(Sprite, {
|
|
325
|
+
image: settings.outer,
|
|
326
|
+
anchor: { x: 0.5, y: 0.5 },
|
|
327
|
+
scale: settings.outerScale,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const innerOptions: any = {
|
|
332
|
+
scale: settings.innerScale,
|
|
333
|
+
x: innerPositionX,
|
|
334
|
+
y: innerPositionY,
|
|
335
|
+
alpha: innerAlpha,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
if (!settings.inner) {
|
|
339
|
+
innerElement = h(Graphics, {
|
|
340
|
+
draw: (g) => {
|
|
341
|
+
g.circle(0, 0, innerRadius * 2.5).fill(settings.innerColor);
|
|
342
|
+
},
|
|
343
|
+
...innerOptions,
|
|
344
|
+
});
|
|
345
|
+
} else {
|
|
346
|
+
innerElement = settings.inner
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return h(
|
|
350
|
+
Container,
|
|
351
|
+
{
|
|
352
|
+
...opts,
|
|
353
|
+
pointerdown: handleDragStart,
|
|
354
|
+
pointerup: handleDragEnd,
|
|
355
|
+
pointerupoutside: handleDragEnd,
|
|
356
|
+
pointermove: handleDragMove,
|
|
357
|
+
},
|
|
358
|
+
outerElement,
|
|
359
|
+
innerElement,
|
|
360
|
+
);
|
|
361
|
+
}
|
package/src/components/Sprite.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { ComponentFunction } from "../engine/signal";
|
|
|
28
28
|
import { DisplayObjectProps } from "./types/DisplayObject";
|
|
29
29
|
import { AnimatedSignal, isAnimatedSignal } from "../engine/animation";
|
|
30
30
|
import { Layout } from '@pixi/layout';
|
|
31
|
+
import { GlobalAssetLoader } from "../utils/GlobalAssetLoader";
|
|
31
32
|
|
|
32
33
|
const log = console.log;
|
|
33
34
|
|
|
@@ -79,6 +80,8 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
79
80
|
private sheetCurrentAnimation: string = StandardAnimation.Stand;
|
|
80
81
|
private app: Application | null = null;
|
|
81
82
|
onFinish: () => void;
|
|
83
|
+
private globalLoader: GlobalAssetLoader | null = null;
|
|
84
|
+
private trackedAssetIds: Set<string> = new Set();
|
|
82
85
|
|
|
83
86
|
get renderer() {
|
|
84
87
|
return this.app?.renderer;
|
|
@@ -103,8 +106,25 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
103
106
|
if (!imagePath || typeof imagePath !== 'string' || imagePath.trim() === '') {
|
|
104
107
|
throw new Error(`Invalid image path provided to detectImageDimensions: ${imagePath}`);
|
|
105
108
|
}
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
|
|
110
|
+
// Register asset in global loader if available
|
|
111
|
+
let assetId: string | null = null;
|
|
112
|
+
if (this.globalLoader) {
|
|
113
|
+
assetId = this.globalLoader.registerAsset(imagePath);
|
|
114
|
+
this.trackedAssetIds.add(assetId);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const texture = await Assets.load(imagePath, (progress) => {
|
|
118
|
+
if (this.globalLoader && assetId) {
|
|
119
|
+
this.globalLoader.updateProgress(assetId, progress);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Mark as complete
|
|
124
|
+
if (this.globalLoader && assetId) {
|
|
125
|
+
this.globalLoader.completeAsset(assetId);
|
|
126
|
+
}
|
|
127
|
+
|
|
108
128
|
return {
|
|
109
129
|
width: texture.width,
|
|
110
130
|
height: texture.height,
|
|
@@ -145,14 +165,30 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
145
165
|
options: Required<TextureOptionsMerging>
|
|
146
166
|
): Promise<Texture[][]> {
|
|
147
167
|
let { width, height, framesHeight, framesWidth, image, offset } = options;
|
|
148
|
-
|
|
168
|
+
|
|
149
169
|
if (!image || typeof image !== 'string' || image.trim() === '') {
|
|
150
170
|
console.warn('Invalid image path provided to createTextures:', image);
|
|
151
171
|
return [];
|
|
152
172
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
173
|
+
|
|
174
|
+
// Register asset in global loader if available
|
|
175
|
+
let assetId: string | null = null;
|
|
176
|
+
if (this.globalLoader) {
|
|
177
|
+
assetId = this.globalLoader.registerAsset(image);
|
|
178
|
+
this.trackedAssetIds.add(assetId);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const texture = await Assets.load(image, (progress) => {
|
|
182
|
+
if (this.globalLoader && assetId) {
|
|
183
|
+
this.globalLoader.updateProgress(assetId, progress);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Mark as complete
|
|
188
|
+
if (this.globalLoader && assetId) {
|
|
189
|
+
this.globalLoader.completeAsset(assetId);
|
|
190
|
+
}
|
|
191
|
+
|
|
156
192
|
// Auto-detect width and height from the image if not provided
|
|
157
193
|
if (!width || width <= 0) {
|
|
158
194
|
width = texture.width;
|
|
@@ -162,7 +198,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
162
198
|
height = texture.height;
|
|
163
199
|
options.height = height;
|
|
164
200
|
}
|
|
165
|
-
|
|
201
|
+
|
|
166
202
|
const spriteWidth = options.spriteWidth;
|
|
167
203
|
const spriteHeight = options.spriteHeight;
|
|
168
204
|
const frames: Texture[][] = [];
|
|
@@ -228,11 +264,11 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
228
264
|
framesHeight = 1,
|
|
229
265
|
image,
|
|
230
266
|
} = optionsTextures;
|
|
231
|
-
|
|
267
|
+
|
|
232
268
|
// Auto-detect width and height from the image if not provided
|
|
233
269
|
let width = widthOption;
|
|
234
270
|
let height = heightOption;
|
|
235
|
-
|
|
271
|
+
|
|
236
272
|
if (image && ((!width || width <= 0) || (!height || height <= 0))) {
|
|
237
273
|
const dimensions = await this.detectImageDimensions(image);
|
|
238
274
|
if (!width || width <= 0) {
|
|
@@ -244,7 +280,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
244
280
|
optionsTextures.height = height;
|
|
245
281
|
}
|
|
246
282
|
}
|
|
247
|
-
|
|
283
|
+
|
|
248
284
|
optionsTextures.spriteWidth = rectWidth ? rectWidth : width / framesWidth;
|
|
249
285
|
optionsTextures.spriteHeight = rectHeight
|
|
250
286
|
? rectHeight
|
|
@@ -268,6 +304,8 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
268
304
|
const sheet = props.sheet ?? {};
|
|
269
305
|
const definition = props.sheet?.definition ?? {};
|
|
270
306
|
this.app = props.context.app();
|
|
307
|
+
// Get global loader from context if available
|
|
308
|
+
this.globalLoader = props.context?.globalLoader || null;
|
|
271
309
|
if (sheet?.onFinish) {
|
|
272
310
|
this.onFinish = sheet.onFinish;
|
|
273
311
|
}
|
|
@@ -320,7 +358,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
320
358
|
this.sheetCurrentAnimation = StandardAnimation.Stand;
|
|
321
359
|
}
|
|
322
360
|
|
|
323
|
-
if (this.spritesheet) this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
361
|
+
if (this.spritesheet && this.has(this.sheetCurrentAnimation)) this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
324
362
|
});
|
|
325
363
|
super.onMount(params);
|
|
326
364
|
}
|
|
@@ -329,16 +367,37 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
329
367
|
if (this.destroyed) return
|
|
330
368
|
super.onUpdate(props);
|
|
331
369
|
|
|
370
|
+
// Initialize globalLoader from context if not already set
|
|
371
|
+
if (!this.globalLoader && props.context?.globalLoader) {
|
|
372
|
+
this.globalLoader = props.context.globalLoader;
|
|
373
|
+
}
|
|
374
|
+
|
|
332
375
|
const setTexture = async (image: string) => {
|
|
333
376
|
if (!image || typeof image !== 'string' || image.trim() === '') {
|
|
334
377
|
console.warn('Invalid image path provided to setTexture:', image);
|
|
335
378
|
return null;
|
|
336
379
|
}
|
|
337
|
-
|
|
380
|
+
|
|
381
|
+
// Register asset in global loader if available
|
|
382
|
+
let assetId: string | null = null;
|
|
383
|
+
if (this.globalLoader) {
|
|
384
|
+
assetId = this.globalLoader.registerAsset(image);
|
|
385
|
+
this.trackedAssetIds.add(assetId);
|
|
386
|
+
}
|
|
387
|
+
|
|
338
388
|
const onProgress = this.fullProps.loader?.onProgress;
|
|
339
389
|
const texture = await Assets.load(image, (progress) => {
|
|
390
|
+
// Update global loader progress
|
|
391
|
+
if (this.globalLoader && assetId) {
|
|
392
|
+
this.globalLoader.updateProgress(assetId, progress);
|
|
393
|
+
}
|
|
394
|
+
// Call local loader callback if provided
|
|
340
395
|
if (onProgress) onProgress(progress);
|
|
341
396
|
if (progress == 1) {
|
|
397
|
+
// Mark as complete in global loader
|
|
398
|
+
if (this.globalLoader && assetId) {
|
|
399
|
+
this.globalLoader.completeAsset(assetId);
|
|
400
|
+
}
|
|
342
401
|
const onComplete = this.fullProps.loader?.onComplete;
|
|
343
402
|
if (onComplete) {
|
|
344
403
|
// hack to memoize the texture
|
|
@@ -379,7 +438,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
379
438
|
if (isElement(props.texture)) {
|
|
380
439
|
const textureInstance = props.texture.componentInstance;
|
|
381
440
|
textureInstance.subjectInit
|
|
382
|
-
|
|
441
|
+
.subscribe()
|
|
383
442
|
this.texture = this.renderer?.generateTexture(props.texture.componentInstance);
|
|
384
443
|
} else {
|
|
385
444
|
this.texture = props.texture;
|
|
@@ -399,6 +458,13 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
399
458
|
|
|
400
459
|
async onDestroy(parent: Element, afterDestroy: () => void): Promise<void> {
|
|
401
460
|
const _afterDestroy = async () => {
|
|
461
|
+
// Clean up tracked assets from global loader
|
|
462
|
+
if (this.globalLoader) {
|
|
463
|
+
this.trackedAssetIds.forEach((assetId) => {
|
|
464
|
+
this.globalLoader!.removeAsset(assetId);
|
|
465
|
+
});
|
|
466
|
+
this.trackedAssetIds.clear();
|
|
467
|
+
}
|
|
402
468
|
this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
|
|
403
469
|
this.subscriptionTick.unsubscribe();
|
|
404
470
|
if (this.currentAnimationContainer && this.parent instanceof Container) {
|
|
@@ -500,16 +566,16 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
500
566
|
async resetAnimations(): Promise<void> {
|
|
501
567
|
// Stop current animation
|
|
502
568
|
this.stop();
|
|
503
|
-
|
|
569
|
+
|
|
504
570
|
// Clear all animations and textures
|
|
505
571
|
this.animations.clear();
|
|
506
|
-
|
|
572
|
+
|
|
507
573
|
// Reset animation state
|
|
508
574
|
this.currentAnimation = null;
|
|
509
575
|
this.currentAnimationContainer = null;
|
|
510
576
|
this.time = 0;
|
|
511
577
|
this.frameIndex = 0;
|
|
512
|
-
|
|
578
|
+
|
|
513
579
|
// Clear children
|
|
514
580
|
this.removeChildren();
|
|
515
581
|
|
package/src/components/index.ts
CHANGED
|
@@ -13,4 +13,5 @@ export { NineSliceSprite } from './NineSliceSprite'
|
|
|
13
13
|
export { type ComponentInstance } from './DisplayObject'
|
|
14
14
|
export { DOMContainer } from './DOMContainer'
|
|
15
15
|
export { DOMElement } from './DOMElement'
|
|
16
|
-
export { Button, ButtonState, type ButtonProps, type ButtonStyle } from './Button'
|
|
16
|
+
export { Button, ButtonState, type ButtonProps, type ButtonStyle } from './Button'
|
|
17
|
+
export { Joystick, type JoystickSettings, type JoystickChangeEvent } from './Joystick'
|
|
@@ -208,124 +208,6 @@ export interface TexturesOptions extends TextureOptions, TransformOptions {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
export interface SpritesheetOptions extends TransformOptions, TextureOptions {
|
|
211
|
-
/**
|
|
212
|
-
* Object containing all animations.
|
|
213
|
-
* The key to the object is the name of the animation. The value is a two-dimensional array
|
|
214
|
-
*
|
|
215
|
-
* ```ts
|
|
216
|
-
* textures: {
|
|
217
|
-
* myanim: {
|
|
218
|
-
* animations: [
|
|
219
|
-
* [ { time: 0, frameX: 0, frameY: 0 } ]
|
|
220
|
-
* ]
|
|
221
|
-
* }
|
|
222
|
-
* }
|
|
223
|
-
* ```
|
|
224
|
-
*
|
|
225
|
-
* The first array represents an animation group. You can put several of them together to create an animation cluster. For example, several explosions with the same spritesheet
|
|
226
|
-
* The second array represents the animation itself which will animate over time. The object indicates, over a period of time (in frame), which part of the spritesheet will be taken (`frameX`, `frameY`)
|
|
227
|
-
*
|
|
228
|
-
* Here are the properties:
|
|
229
|
-
*
|
|
230
|
-
* * `time`: Time in frame
|
|
231
|
-
* * `frameX`: Retrieve a frame from the spritesheet on the X-axis
|
|
232
|
-
* * `frameY`: Retrieve a frame from the spritesheet on the Y-axis
|
|
233
|
-
* * `opacity`
|
|
234
|
-
* * `pivot`
|
|
235
|
-
* * `anchor`
|
|
236
|
-
* * `rotation`
|
|
237
|
-
* * `angle`
|
|
238
|
-
* * `scale`
|
|
239
|
-
* * `skew`
|
|
240
|
-
* * `x`
|
|
241
|
-
* * `y`
|
|
242
|
-
* * `visible`
|
|
243
|
-
* * `sound`: The sound that will be played during the frame
|
|
244
|
-
*
|
|
245
|
-
* ---
|
|
246
|
-
* **Extract Animation of Spritesheet**
|
|
247
|
-
*
|
|
248
|
-
* Sometimes the animation is part of the image
|
|
249
|
-
*
|
|
250
|
-
* ```ts
|
|
251
|
-
* textures: {
|
|
252
|
-
* myanim: {
|
|
253
|
-
* rectWidth: 64,
|
|
254
|
-
* rectHeight: 64,
|
|
255
|
-
* framesWidth: 10,
|
|
256
|
-
* framesHeight: 2,
|
|
257
|
-
* offset: {x: 0, y: 230},
|
|
258
|
-
* sound: 'my-sound-id', // You can put a sound just for the animation
|
|
259
|
-
* animations: [
|
|
260
|
-
* [ { time: 0, frameX: 0, frameY: 0 } ]
|
|
261
|
-
* ]
|
|
262
|
-
* }
|
|
263
|
-
* }
|
|
264
|
-
* ```
|
|
265
|
-
*
|
|
266
|
-
* Above, we can specify which part we want to recover
|
|
267
|
-
*
|
|
268
|
-
* 1. We go to the position {0, 230} of the image (`offset`)
|
|
269
|
-
* 2. We recover cells of 64px (`rectWidth` and `rectHeight`)
|
|
270
|
-
* 3. And we get 20 cells (10 on the width, 2 on the height) (`frameX` and `frameY`)
|
|
271
|
-
*
|
|
272
|
-
* ---
|
|
273
|
-
*
|
|
274
|
-
* **Advanced**
|
|
275
|
-
*
|
|
276
|
-
* You can create an animation that will be linked to a data. For example, different animation according to a direction of the character.
|
|
277
|
-
*
|
|
278
|
-
* Full example:
|
|
279
|
-
*
|
|
280
|
-
* ```ts
|
|
281
|
-
* import { Spritesheet, Animation, Direction } from '@rpgjs/client'
|
|
282
|
-
*
|
|
283
|
-
* @Spritesheet({
|
|
284
|
-
* id: 'chest',
|
|
285
|
-
* image: require('./assets/chest.png'),
|
|
286
|
-
* width: 124,
|
|
287
|
-
* height: 61,
|
|
288
|
-
* framesHeight: 2,
|
|
289
|
-
* framesWidth: 4,
|
|
290
|
-
* textures: {
|
|
291
|
-
* [Animation.Stand]: {
|
|
292
|
-
* animations: direction => [[ {time: 0, frameX: 3, frameY: direction == Direction.Up ? 0 : 1 } ]]
|
|
293
|
-
* }
|
|
294
|
-
* })
|
|
295
|
-
* })
|
|
296
|
-
* export class Chest { }
|
|
297
|
-
* ```
|
|
298
|
-
*
|
|
299
|
-
* > It is important to know that `Animation.Stand` animation is called if it exists. it only works in the case of an event that doesn't move. The direction is then sent
|
|
300
|
-
*
|
|
301
|
-
* As you can see, the property contains a function that returns the array for the animation. Here, it is the direction but the parameters depend on the call of the animation. Example:
|
|
302
|
-
*
|
|
303
|
-
* ```ts
|
|
304
|
-
* import { Spritesheet, Animation, Direction, RpgSprite, ISpriteCharacter } from '@rpgjs/client'
|
|
305
|
-
*
|
|
306
|
-
* @Spritesheet({
|
|
307
|
-
* id: 'chest',
|
|
308
|
-
* image: require('./assets/chest.png'),
|
|
309
|
-
* width: 124,
|
|
310
|
-
* height: 61,
|
|
311
|
-
* framesHeight: 2,
|
|
312
|
-
* framesWidth: 4,
|
|
313
|
-
* textures: {
|
|
314
|
-
* [Animation.Stand]: {
|
|
315
|
-
* animations: str => [[ {time: 0, frameX: 3, frameY: str == 'hello' ? 0 : 1 } ]]
|
|
316
|
-
* }
|
|
317
|
-
* }
|
|
318
|
-
* })
|
|
319
|
-
* export class Chest implements ISpriteCharacter {
|
|
320
|
-
* onCharacterStand(sprite: RpgSprite) {
|
|
321
|
-
* sprite.animation.play(Animation.Stand, ['hello'])
|
|
322
|
-
* }
|
|
323
|
-
* }
|
|
324
|
-
* ```
|
|
325
|
-
*
|
|
326
|
-
* @prop { { [animName: string]: { animations: Array<Array<FrameOptions>> | Function, ...other } } } [textures]
|
|
327
|
-
* @memberof Spritesheet
|
|
328
|
-
* */
|
|
329
211
|
textures?: {
|
|
330
212
|
[animationName: string]: Partial<TexturesOptions> & Pick<TexturesOptions, 'animations'>
|
|
331
213
|
}
|