@rpgjs/client 5.0.0-alpha.21 → 5.0.0-alpha.23
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/Game/Object.d.ts +111 -0
- package/dist/Game/TransitionManager.d.ts +56 -0
- package/dist/RpgClientEngine.d.ts +306 -9
- package/dist/components/gui/mobile/index.d.ts +8 -0
- package/dist/components/prebuilt/index.d.ts +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +14 -8
- package/dist/index.js.map +1 -1
- package/dist/index10.js +1 -1
- package/dist/index11.js +6 -5
- package/dist/index11.js.map +1 -1
- package/dist/index12.js +2 -2
- package/dist/index13.js +102 -10
- package/dist/index13.js.map +1 -1
- package/dist/index14.js +68 -9
- package/dist/index14.js.map +1 -1
- package/dist/index15.js +10 -224
- package/dist/index15.js.map +1 -1
- package/dist/index16.js +9 -97
- package/dist/index16.js.map +1 -1
- package/dist/index17.js +300 -89
- package/dist/index17.js.map +1 -1
- package/dist/index18.js +63 -80
- package/dist/index18.js.map +1 -1
- package/dist/index19.js +96 -348
- package/dist/index19.js.map +1 -1
- package/dist/index2.js +387 -21
- package/dist/index2.js.map +1 -1
- package/dist/index20.js +361 -5
- package/dist/index20.js.map +1 -1
- package/dist/index21.js +19 -50
- package/dist/index21.js.map +1 -1
- package/dist/index22.js +212 -5
- package/dist/index22.js.map +1 -1
- package/dist/index23.js +6 -395
- package/dist/index23.js.map +1 -1
- package/dist/index24.js +4 -39
- package/dist/index24.js.map +1 -1
- package/dist/index25.js +19 -20
- package/dist/index25.js.map +1 -1
- package/dist/index26.js +44 -2624
- package/dist/index26.js.map +1 -1
- package/dist/index27.js +5 -110
- package/dist/index27.js.map +1 -1
- package/dist/index28.js +394 -65
- package/dist/index28.js.map +1 -1
- package/dist/index29.js +40 -15
- package/dist/index29.js.map +1 -1
- package/dist/index3.js +3 -3
- package/dist/index30.js +21 -23
- package/dist/index30.js.map +1 -1
- package/dist/index31.js +49 -91
- package/dist/index31.js.map +1 -1
- package/dist/index32.js +2624 -32
- package/dist/index32.js.map +1 -1
- package/dist/index33.js +108 -18
- package/dist/index33.js.map +1 -1
- package/dist/index34.js +69 -3
- package/dist/index34.js.map +1 -1
- package/dist/index35.js +17 -331
- package/dist/index35.js.map +1 -1
- package/dist/index36.js +24 -24
- package/dist/index36.js.map +1 -1
- package/dist/index37.js +92 -8
- package/dist/index37.js.map +1 -1
- package/dist/index38.js +37 -7
- package/dist/index38.js.map +1 -1
- package/dist/index39.js +22 -10
- package/dist/index39.js.map +1 -1
- package/dist/index4.js +3 -3
- package/dist/index40.js +140 -6
- package/dist/index40.js.map +1 -1
- package/dist/index41.js +31 -3678
- package/dist/index41.js.map +1 -1
- package/dist/index42.js +3 -185
- package/dist/index42.js.map +1 -1
- package/dist/index43.js +172 -489
- package/dist/index43.js.map +1 -1
- package/dist/index44.js +498 -71
- package/dist/index44.js.map +1 -1
- package/dist/index45.js +331 -2
- package/dist/index45.js.map +1 -1
- package/dist/index46.js +25 -11
- package/dist/index46.js.map +1 -1
- package/dist/index47.js +70 -139
- package/dist/index47.js.map +1 -1
- package/dist/index48.js +9 -9
- package/dist/index48.js.map +1 -1
- package/dist/index49.js +6 -112
- package/dist/index49.js.map +1 -1
- package/dist/index5.js +1 -1
- package/dist/index50.js +3678 -124
- package/dist/index50.js.map +1 -1
- package/dist/index51.js +48 -131
- package/dist/index51.js.map +1 -1
- package/dist/index52.js +17 -109
- package/dist/index52.js.map +1 -1
- package/dist/index53.js +3 -138
- package/dist/index53.js.map +1 -1
- package/dist/index54.js +10 -7
- package/dist/index54.js.map +1 -1
- package/dist/index55.js +107 -48
- package/dist/index55.js.map +1 -1
- package/dist/index56.js +136 -0
- package/dist/index56.js.map +1 -0
- package/dist/index57.js +137 -0
- package/dist/index57.js.map +1 -0
- package/dist/index58.js +112 -0
- package/dist/index58.js.map +1 -0
- package/dist/index59.js +9 -0
- package/dist/index59.js.map +1 -0
- package/dist/index6.js +1 -1
- package/dist/index7.js +1 -1
- package/dist/index8.js +20 -2
- package/dist/index8.js.map +1 -1
- package/dist/index9.js +11 -27
- package/dist/index9.js.map +1 -1
- package/dist/module.d.ts +43 -4
- package/dist/services/keyboardControls.d.ts +11 -1
- package/package.json +11 -10
- package/src/Game/Object.ts +90 -8
- package/src/Game/TransitionManager.ts +75 -0
- package/src/Gui/Gui.ts +5 -31
- package/src/RpgClientEngine.ts +430 -16
- package/src/components/character.ce +212 -11
- package/src/components/gui/mobile/index.ts +24 -0
- package/src/components/gui/mobile/mobile.ce +95 -0
- package/src/components/prebuilt/index.ts +2 -0
- package/src/components/prebuilt/light-halo.ce +217 -0
- package/src/components/scenes/canvas.ce +12 -2
- package/src/components/scenes/draw-map.ce +12 -3
- package/src/components/scenes/transition.ce +60 -0
- package/src/index.ts +7 -1
- package/src/module.ts +66 -2
- package/src/services/keyboardControls.ts +14 -2
package/src/Gui/Gui.ts
CHANGED
|
@@ -195,14 +195,13 @@ export class RpgGui {
|
|
|
195
195
|
if (!guiId) {
|
|
196
196
|
throw new Error("GUI must have a name or id");
|
|
197
197
|
}
|
|
198
|
-
|
|
199
198
|
const guiInstance: GuiInstance = {
|
|
200
199
|
name: guiId,
|
|
201
200
|
component: gui.component,
|
|
202
201
|
display: signal(gui.display || false),
|
|
203
202
|
data: signal(gui.data || {}),
|
|
204
203
|
autoDisplay: gui.autoDisplay || false,
|
|
205
|
-
dependencies: gui.dependencies,
|
|
204
|
+
dependencies: gui.dependencies ? gui.dependencies() : [],
|
|
206
205
|
attachToSprite: gui.attachToSprite || false,
|
|
207
206
|
};
|
|
208
207
|
|
|
@@ -317,8 +316,9 @@ export class RpgGui {
|
|
|
317
316
|
// Handle Vue component display
|
|
318
317
|
this._handleVueComponentDisplay(id, data, dependencies, guiInstance);
|
|
319
318
|
} else {
|
|
320
|
-
|
|
321
|
-
|
|
319
|
+
guiInstance.data.set(data);
|
|
320
|
+
guiInstance.display.set(true);
|
|
321
|
+
console.log(guiInstance.dependencies)
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
|
|
@@ -371,33 +371,7 @@ export class RpgGui {
|
|
|
371
371
|
* @param guiInstance - GUI instance
|
|
372
372
|
*/
|
|
373
373
|
private _handleCanvasComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance) {
|
|
374
|
-
|
|
375
|
-
if (guiInstance.subscription) {
|
|
376
|
-
guiInstance.subscription.unsubscribe();
|
|
377
|
-
guiInstance.subscription = undefined;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Use runtime dependencies or config dependencies
|
|
381
|
-
const deps = dependencies.length > 0
|
|
382
|
-
? dependencies
|
|
383
|
-
: (guiInstance.dependencies ? guiInstance.dependencies() : []);
|
|
384
|
-
|
|
385
|
-
if (deps.length > 0) {
|
|
386
|
-
// Subscribe to dependencies
|
|
387
|
-
guiInstance.subscription = combineLatest(
|
|
388
|
-
deps.map(dependency => dependency.observable)
|
|
389
|
-
).subscribe((values) => {
|
|
390
|
-
if (values.every(value => value !== undefined)) {
|
|
391
|
-
guiInstance.data.set(data);
|
|
392
|
-
guiInstance.display.set(true);
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// No dependencies, display immediately
|
|
399
|
-
guiInstance.data.set(data);
|
|
400
|
-
guiInstance.display.set(true);
|
|
374
|
+
|
|
401
375
|
}
|
|
402
376
|
|
|
403
377
|
/**
|
package/src/RpgClientEngine.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Canvas from "./components/scenes/canvas.ce";
|
|
2
|
-
import {
|
|
3
|
-
import { signal, bootstrapCanvas,
|
|
2
|
+
import { inject } from './core/inject'
|
|
3
|
+
import { signal, bootstrapCanvas, KeyboardControls, Howl, trigger } from "canvasengine";
|
|
4
4
|
import { AbstractWebsocket, WebSocketToken } from "./services/AbstractSocket";
|
|
5
5
|
import { LoadMapService, LoadMapToken } from "./services/loadMap";
|
|
6
6
|
import { RpgSound } from "./Sound";
|
|
@@ -12,6 +12,7 @@ import { load } from "@signe/sync";
|
|
|
12
12
|
import { RpgClientMap } from "./Game/Map"
|
|
13
13
|
import { RpgGui } from "./Gui/Gui";
|
|
14
14
|
import { AnimationManager } from "./Game/AnimationManager";
|
|
15
|
+
import { TransitionManager } from "./Game/TransitionManager";
|
|
15
16
|
import { lastValueFrom, Observable } from "rxjs";
|
|
16
17
|
import { GlobalConfigToken } from "./module";
|
|
17
18
|
import * as PIXI from "pixi.js";
|
|
@@ -20,7 +21,6 @@ import {
|
|
|
20
21
|
PredictionController,
|
|
21
22
|
type PredictionState,
|
|
22
23
|
} from "@rpgjs/common";
|
|
23
|
-
import { KeyboardControls } from "./services/keyboardControls";
|
|
24
24
|
|
|
25
25
|
export class RpgClientEngine<T = any> {
|
|
26
26
|
private guiService: RpgGui;
|
|
@@ -37,6 +37,7 @@ export class RpgClientEngine<T = any> {
|
|
|
37
37
|
spritesheets: Map<string, any> = new Map();
|
|
38
38
|
sounds: Map<string, any> = new Map();
|
|
39
39
|
componentAnimations: any[] = [];
|
|
40
|
+
transitions: any[] = [];
|
|
40
41
|
private spritesheetResolver?: (id: string) => any | Promise<any>;
|
|
41
42
|
private soundResolver?: (id: string) => any | Promise<any>;
|
|
42
43
|
particleSettings: {
|
|
@@ -49,6 +50,10 @@ export class RpgClientEngine<T = any> {
|
|
|
49
50
|
playerIdSignal = signal<string | null>(null);
|
|
50
51
|
spriteComponentsBehind = signal<any[]>([]);
|
|
51
52
|
spriteComponentsInFront = signal<any[]>([]);
|
|
53
|
+
/** ID of the sprite that the camera should follow. null means follow the current player */
|
|
54
|
+
cameraFollowTargetId = signal<string | null>(null);
|
|
55
|
+
/** Trigger for map shake animation */
|
|
56
|
+
mapShakeTrigger = trigger();
|
|
52
57
|
|
|
53
58
|
private predictionEnabled = false;
|
|
54
59
|
private prediction?: PredictionController<Direction>;
|
|
@@ -61,12 +66,12 @@ export class RpgClientEngine<T = any> {
|
|
|
61
66
|
private readonly PING_INTERVAL_MS = 5000; // Send ping every 5 seconds
|
|
62
67
|
private lastInputTime = 0;
|
|
63
68
|
|
|
64
|
-
constructor(public context
|
|
65
|
-
this.webSocket = inject(
|
|
66
|
-
this.guiService = inject(
|
|
67
|
-
this.loadMapService = inject(
|
|
68
|
-
this.hooks = inject<Hooks>(
|
|
69
|
-
this.globalConfig = inject(
|
|
69
|
+
constructor(public context) {
|
|
70
|
+
this.webSocket = inject(WebSocketToken);
|
|
71
|
+
this.guiService = inject(RpgGui);
|
|
72
|
+
this.loadMapService = inject(LoadMapToken);
|
|
73
|
+
this.hooks = inject<Hooks>(ModulesToken);
|
|
74
|
+
this.globalConfig = inject(GlobalConfigToken)
|
|
70
75
|
|
|
71
76
|
if (!this.globalConfig) {
|
|
72
77
|
this.globalConfig = {} as T
|
|
@@ -124,8 +129,8 @@ export class RpgClientEngine<T = any> {
|
|
|
124
129
|
* ```
|
|
125
130
|
*/
|
|
126
131
|
setKeyboardControls(controlInstance: any) {
|
|
127
|
-
const currentValues = this.context.values['inject:' + KeyboardControls]
|
|
128
|
-
this.context.values['inject:' + KeyboardControls] = {
|
|
132
|
+
const currentValues = this.context.values['inject:' + 'KeyboardControls']
|
|
133
|
+
this.context.values['inject:' + 'KeyboardControls'] = {
|
|
129
134
|
...currentValues,
|
|
130
135
|
values: new Map([['__default__', controlInstance]])
|
|
131
136
|
}
|
|
@@ -158,6 +163,7 @@ export class RpgClientEngine<T = any> {
|
|
|
158
163
|
this.hooks.callHooks("client-gui-load", this).subscribe();
|
|
159
164
|
this.hooks.callHooks("client-particles-load", this).subscribe();
|
|
160
165
|
this.hooks.callHooks("client-componentAnimations-load", this).subscribe();
|
|
166
|
+
this.hooks.callHooks("client-transitions-load", this).subscribe();
|
|
161
167
|
this.hooks.callHooks("client-sprite-load", this).subscribe();
|
|
162
168
|
|
|
163
169
|
await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
|
|
@@ -214,6 +220,8 @@ export class RpgClientEngine<T = any> {
|
|
|
214
220
|
|
|
215
221
|
this.webSocket.on("changeMap", (data) => {
|
|
216
222
|
this.sceneMap.reset()
|
|
223
|
+
// Reset camera follow to default (follow current player) when changing maps
|
|
224
|
+
this.cameraFollowTargetId.set(null);
|
|
217
225
|
this.loadScene(data.mapId);
|
|
218
226
|
});
|
|
219
227
|
|
|
@@ -242,6 +250,33 @@ export class RpgClientEngine<T = any> {
|
|
|
242
250
|
this.stopSound(soundId);
|
|
243
251
|
});
|
|
244
252
|
|
|
253
|
+
this.webSocket.on("stopAllSounds", () => {
|
|
254
|
+
this.stopAllSounds();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
this.webSocket.on("cameraFollow", (data) => {
|
|
258
|
+
const { targetId, smoothMove } = data;
|
|
259
|
+
this.setCameraFollow(targetId, smoothMove);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
this.webSocket.on("flash", (data) => {
|
|
263
|
+
const { object, type, duration, cycles, alpha, tint } = data;
|
|
264
|
+
const sprite = object ? this.sceneMap.getObjectById(object) : undefined;
|
|
265
|
+
if (sprite && typeof sprite.flash === 'function') {
|
|
266
|
+
sprite.flash({ type, duration, cycles, alpha, tint });
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
this.webSocket.on("shakeMap", (data) => {
|
|
271
|
+
const { intensity, duration, frequency, direction } = data || {};
|
|
272
|
+
this.mapShakeTrigger.start({
|
|
273
|
+
intensity,
|
|
274
|
+
duration,
|
|
275
|
+
frequency,
|
|
276
|
+
direction
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
245
280
|
this.webSocket.on('open', () => {
|
|
246
281
|
this.hooks.callHooks("client-engine-onConnected", this, this.socket).subscribe();
|
|
247
282
|
// Start ping/pong for synchronization
|
|
@@ -673,6 +708,79 @@ export class RpgClientEngine<T = any> {
|
|
|
673
708
|
}
|
|
674
709
|
}
|
|
675
710
|
|
|
711
|
+
/**
|
|
712
|
+
* Stop all currently playing sounds
|
|
713
|
+
*
|
|
714
|
+
* This method stops all sounds that are currently playing.
|
|
715
|
+
* Useful when changing maps to prevent sound overlap.
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* ```ts
|
|
719
|
+
* // Stop all sounds
|
|
720
|
+
* engine.stopAllSounds();
|
|
721
|
+
* ```
|
|
722
|
+
*/
|
|
723
|
+
stopAllSounds(): void {
|
|
724
|
+
this.sounds.forEach((sound) => {
|
|
725
|
+
if (sound && sound.stop) {
|
|
726
|
+
sound.stop();
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Set the camera to follow a specific sprite
|
|
733
|
+
*
|
|
734
|
+
* This method changes which sprite the camera viewport should follow.
|
|
735
|
+
* The camera will smoothly animate to the target sprite if smoothMove options are provided.
|
|
736
|
+
*
|
|
737
|
+
* ## Design
|
|
738
|
+
*
|
|
739
|
+
* The camera follow target is stored in a signal that is read by sprite components.
|
|
740
|
+
* Each sprite checks if it should be followed by comparing its ID with the target ID.
|
|
741
|
+
* When smoothMove options are provided, the viewport animation is handled by CanvasEngine's
|
|
742
|
+
* viewport system.
|
|
743
|
+
*
|
|
744
|
+
* @param targetId - The ID of the sprite to follow. Set to null to follow the current player
|
|
745
|
+
* @param smoothMove - Animation options. Can be a boolean (default: true) or an object with time and ease
|
|
746
|
+
* @param smoothMove.time - Duration of the animation in milliseconds (optional)
|
|
747
|
+
* @param smoothMove.ease - Easing function name from https://easings.net (optional)
|
|
748
|
+
*
|
|
749
|
+
* @example
|
|
750
|
+
* ```ts
|
|
751
|
+
* // Follow another player with default smooth animation
|
|
752
|
+
* engine.setCameraFollow(otherPlayerId, true);
|
|
753
|
+
*
|
|
754
|
+
* // Follow an event with custom smooth animation
|
|
755
|
+
* engine.setCameraFollow(eventId, {
|
|
756
|
+
* time: 1000,
|
|
757
|
+
* ease: "easeInOutQuad"
|
|
758
|
+
* });
|
|
759
|
+
*
|
|
760
|
+
* // Follow without animation (instant)
|
|
761
|
+
* engine.setCameraFollow(targetId, false);
|
|
762
|
+
*
|
|
763
|
+
* // Return to following current player
|
|
764
|
+
* engine.setCameraFollow(null);
|
|
765
|
+
* ```
|
|
766
|
+
*/
|
|
767
|
+
setCameraFollow(
|
|
768
|
+
targetId: string | null,
|
|
769
|
+
smoothMove?: boolean | { time?: number; ease?: string }
|
|
770
|
+
): void {
|
|
771
|
+
// Store smoothMove options for potential future use with viewport animation
|
|
772
|
+
// For now, we just set the target ID and let CanvasEngine handle the viewport follow
|
|
773
|
+
// The smoothMove options could be used to configure viewport animation if CanvasEngine supports it
|
|
774
|
+
this.cameraFollowTargetId.set(targetId);
|
|
775
|
+
|
|
776
|
+
// If smoothMove is an object, we could store it for viewport configuration
|
|
777
|
+
// This would require integration with CanvasEngine's viewport animation system
|
|
778
|
+
if (typeof smoothMove === "object" && smoothMove !== null) {
|
|
779
|
+
// Future: Apply smoothMove.time and smoothMove.ease to viewport animation
|
|
780
|
+
// For now, CanvasEngine handles viewport following automatically
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
676
784
|
addParticle(particle: any) {
|
|
677
785
|
this.particleSettings.emitters.push(particle)
|
|
678
786
|
return particle;
|
|
@@ -682,13 +790,38 @@ export class RpgClientEngine<T = any> {
|
|
|
682
790
|
* Add a component to render behind sprites
|
|
683
791
|
* Components added with this method will be displayed with a lower z-index than the sprite
|
|
684
792
|
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
793
|
+
* Supports multiple formats:
|
|
794
|
+
* 1. Direct component: `ShadowComponent`
|
|
795
|
+
* 2. Configuration object: `{ component: LightHalo, props: {...} }`
|
|
796
|
+
* 3. With dynamic props: `{ component: LightHalo, props: (object) => {...} }`
|
|
797
|
+
* 4. With dependencies: `{ component: HealthBar, dependencies: (object) => [object.hp, object.param.maxHp] }`
|
|
798
|
+
*
|
|
799
|
+
* Components with dependencies will only be displayed when all dependencies are resolved (!= undefined).
|
|
800
|
+
* The object (sprite) is passed to the dependencies function to allow sprite-specific dependency resolution.
|
|
801
|
+
*
|
|
802
|
+
* @param component - The component to add behind sprites, or a configuration object
|
|
803
|
+
* @param component.component - The component function to render
|
|
804
|
+
* @param component.props - Static props object or function that receives the sprite object and returns props
|
|
805
|
+
* @param component.dependencies - Function that receives the sprite object and returns an array of Signals
|
|
806
|
+
* @returns The added component or configuration
|
|
687
807
|
*
|
|
688
808
|
* @example
|
|
689
809
|
* ```ts
|
|
690
810
|
* // Add a shadow component behind all sprites
|
|
691
811
|
* engine.addSpriteComponentBehind(ShadowComponent);
|
|
812
|
+
*
|
|
813
|
+
* // Add a component with static props
|
|
814
|
+
* engine.addSpriteComponentBehind({
|
|
815
|
+
* component: LightHalo,
|
|
816
|
+
* props: { radius: 30 }
|
|
817
|
+
* });
|
|
818
|
+
*
|
|
819
|
+
* // Add a component with dynamic props and dependencies
|
|
820
|
+
* engine.addSpriteComponentBehind({
|
|
821
|
+
* component: HealthBar,
|
|
822
|
+
* props: (object) => ({ hp: object.hp(), maxHp: object.param.maxHp() }),
|
|
823
|
+
* dependencies: (object) => [object.hp, object.param.maxHp]
|
|
824
|
+
* });
|
|
692
825
|
* ```
|
|
693
826
|
*/
|
|
694
827
|
addSpriteComponentBehind(component: any) {
|
|
@@ -700,16 +833,41 @@ export class RpgClientEngine<T = any> {
|
|
|
700
833
|
* Add a component to render in front of sprites
|
|
701
834
|
* Components added with this method will be displayed with a higher z-index than the sprite
|
|
702
835
|
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
836
|
+
* Supports multiple formats:
|
|
837
|
+
* 1. Direct component: `HealthBarComponent`
|
|
838
|
+
* 2. Configuration object: `{ component: StatusIndicator, props: {...} }`
|
|
839
|
+
* 3. With dynamic props: `{ component: HealthBar, props: (object) => {...} }`
|
|
840
|
+
* 4. With dependencies: `{ component: HealthBar, dependencies: (object) => [object.hp, object.param.maxHp] }`
|
|
841
|
+
*
|
|
842
|
+
* Components with dependencies will only be displayed when all dependencies are resolved (!= undefined).
|
|
843
|
+
* The object (sprite) is passed to the dependencies function to allow sprite-specific dependency resolution.
|
|
844
|
+
*
|
|
845
|
+
* @param component - The component to add in front of sprites, or a configuration object
|
|
846
|
+
* @param component.component - The component function to render
|
|
847
|
+
* @param component.props - Static props object or function that receives the sprite object and returns props
|
|
848
|
+
* @param component.dependencies - Function that receives the sprite object and returns an array of Signals
|
|
849
|
+
* @returns The added component or configuration
|
|
705
850
|
*
|
|
706
851
|
* @example
|
|
707
852
|
* ```ts
|
|
708
853
|
* // Add a health bar component in front of all sprites
|
|
709
854
|
* engine.addSpriteComponentInFront(HealthBarComponent);
|
|
855
|
+
*
|
|
856
|
+
* // Add a component with static props
|
|
857
|
+
* engine.addSpriteComponentInFront({
|
|
858
|
+
* component: StatusIndicator,
|
|
859
|
+
* props: { type: 'poison' }
|
|
860
|
+
* });
|
|
861
|
+
*
|
|
862
|
+
* // Add a component with dynamic props and dependencies
|
|
863
|
+
* engine.addSpriteComponentInFront({
|
|
864
|
+
* component: HealthBar,
|
|
865
|
+
* props: (object) => ({ hp: object.hp(), maxHp: object.param.maxHp() }),
|
|
866
|
+
* dependencies: (object) => [object.hp, object.param.maxHp]
|
|
867
|
+
* });
|
|
710
868
|
* ```
|
|
711
869
|
*/
|
|
712
|
-
addSpriteComponentInFront(component: any) {
|
|
870
|
+
addSpriteComponentInFront(component: any | { component: any, props: (object: any) => any, dependencies?: (object: any) => any[] }) {
|
|
713
871
|
this.spriteComponentsInFront.update((components: any[]) => [...components, component])
|
|
714
872
|
return component
|
|
715
873
|
}
|
|
@@ -779,6 +937,196 @@ export class RpgClientEngine<T = any> {
|
|
|
779
937
|
return componentAnimation.instance
|
|
780
938
|
}
|
|
781
939
|
|
|
940
|
+
/**
|
|
941
|
+
* Add a transition to the engine
|
|
942
|
+
*
|
|
943
|
+
* Transitions are screen effects that can be displayed during scene changes,
|
|
944
|
+
* map loading, or any other moment where a visual transition is needed.
|
|
945
|
+
* They are displayed on top of the entire canvas and can have custom props
|
|
946
|
+
* that can be functions (similar to ComponentAnimation).
|
|
947
|
+
*
|
|
948
|
+
* @param transition - The transition configuration
|
|
949
|
+
* @param transition.id - Unique identifier for the transition
|
|
950
|
+
* @param transition.component - The component function to render
|
|
951
|
+
* @param transition.props - Optional props to pass to the component (can be a function)
|
|
952
|
+
* @returns The added transition configuration
|
|
953
|
+
*
|
|
954
|
+
* @example
|
|
955
|
+
* ```ts
|
|
956
|
+
* // Add a fade transition
|
|
957
|
+
* engine.addTransition({
|
|
958
|
+
* id: 'fade',
|
|
959
|
+
* component: FadeComponent
|
|
960
|
+
* });
|
|
961
|
+
*
|
|
962
|
+
* // Add a transition with props
|
|
963
|
+
* engine.addTransition({
|
|
964
|
+
* id: 'slide',
|
|
965
|
+
* component: SlideComponent,
|
|
966
|
+
* props: { direction: 'left', duration: 500 }
|
|
967
|
+
* });
|
|
968
|
+
*
|
|
969
|
+
* // Add a transition with function props
|
|
970
|
+
* engine.addTransition({
|
|
971
|
+
* id: 'custom',
|
|
972
|
+
* component: CustomTransition,
|
|
973
|
+
* props: (engine) => ({ width: engine.width(), height: engine.height() })
|
|
974
|
+
* });
|
|
975
|
+
* ```
|
|
976
|
+
*/
|
|
977
|
+
addTransition(transition: {
|
|
978
|
+
component: any,
|
|
979
|
+
id: string,
|
|
980
|
+
props?: any | ((engine: RpgClientEngine) => any)
|
|
981
|
+
}) {
|
|
982
|
+
const instance = new TransitionManager()
|
|
983
|
+
this.transitions.push({
|
|
984
|
+
id: transition.id,
|
|
985
|
+
component: transition.component,
|
|
986
|
+
props: transition.props,
|
|
987
|
+
instance: instance,
|
|
988
|
+
current: instance.current
|
|
989
|
+
})
|
|
990
|
+
return transition;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Remove a transition from the engine
|
|
995
|
+
*
|
|
996
|
+
* Removes a transition by its ID. This will not affect any currently
|
|
997
|
+
* running transitions, only prevent new ones from being started.
|
|
998
|
+
*
|
|
999
|
+
* @param id - The unique identifier of the transition to remove
|
|
1000
|
+
* @returns true if the transition was found and removed, false otherwise
|
|
1001
|
+
*
|
|
1002
|
+
* @example
|
|
1003
|
+
* ```ts
|
|
1004
|
+
* // Remove a transition
|
|
1005
|
+
* engine.removeTransition('fade');
|
|
1006
|
+
* ```
|
|
1007
|
+
*/
|
|
1008
|
+
removeTransition(id: string): boolean {
|
|
1009
|
+
const index = this.transitions.findIndex((transition) => transition.id === id);
|
|
1010
|
+
if (index !== -1) {
|
|
1011
|
+
this.transitions.splice(index, 1);
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Modify an existing transition
|
|
1019
|
+
*
|
|
1020
|
+
* Updates the component or props of an existing transition. This will
|
|
1021
|
+
* not affect any currently running transitions, only future ones.
|
|
1022
|
+
*
|
|
1023
|
+
* @param id - The unique identifier of the transition to modify
|
|
1024
|
+
* @param updates - The updates to apply (component and/or props)
|
|
1025
|
+
* @returns true if the transition was found and modified, false otherwise
|
|
1026
|
+
*
|
|
1027
|
+
* @example
|
|
1028
|
+
* ```ts
|
|
1029
|
+
* // Update transition props
|
|
1030
|
+
* engine.modifyTransition('fade', {
|
|
1031
|
+
* props: { duration: 2000, color: 'white' }
|
|
1032
|
+
* });
|
|
1033
|
+
*
|
|
1034
|
+
* // Update transition component
|
|
1035
|
+
* engine.modifyTransition('fade', {
|
|
1036
|
+
* component: NewFadeComponent
|
|
1037
|
+
* });
|
|
1038
|
+
* ```
|
|
1039
|
+
*/
|
|
1040
|
+
modifyTransition(id: string, updates: {
|
|
1041
|
+
component?: any,
|
|
1042
|
+
props?: any | ((engine: RpgClientEngine) => any)
|
|
1043
|
+
}): boolean {
|
|
1044
|
+
const transition = this.transitions.find((transition) => transition.id === id);
|
|
1045
|
+
if (!transition) {
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
if (updates.component !== undefined) {
|
|
1049
|
+
transition.component = updates.component;
|
|
1050
|
+
}
|
|
1051
|
+
if (updates.props !== undefined) {
|
|
1052
|
+
transition.props = updates.props;
|
|
1053
|
+
}
|
|
1054
|
+
return true;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Get a transition by its ID
|
|
1059
|
+
*
|
|
1060
|
+
* Retrieves the TransitionManager instance for a specific transition,
|
|
1061
|
+
* which can be used to start the transition.
|
|
1062
|
+
*
|
|
1063
|
+
* @param id - The unique identifier of the transition
|
|
1064
|
+
* @returns The TransitionManager instance for the transition
|
|
1065
|
+
* @throws Error if the transition is not found
|
|
1066
|
+
*
|
|
1067
|
+
* @example
|
|
1068
|
+
* ```ts
|
|
1069
|
+
* // Get a transition and start it
|
|
1070
|
+
* const fadeTransition = engine.getTransition('fade');
|
|
1071
|
+
* fadeTransition.start({ duration: 1000 });
|
|
1072
|
+
* ```
|
|
1073
|
+
*/
|
|
1074
|
+
getTransition(id: string): TransitionManager {
|
|
1075
|
+
const transition = this.transitions.find((transition) => transition.id === id)
|
|
1076
|
+
if (!transition) {
|
|
1077
|
+
throw new Error(`Transition with id ${id} not found`)
|
|
1078
|
+
}
|
|
1079
|
+
return transition.instance
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* Start a transition
|
|
1084
|
+
*
|
|
1085
|
+
* Convenience method to start a transition by its ID. This combines
|
|
1086
|
+
* getTransition and start into a single call. The transition will
|
|
1087
|
+
* automatically receive an onFinish callback to remove itself when done.
|
|
1088
|
+
*
|
|
1089
|
+
* @param id - The unique identifier of the transition to start
|
|
1090
|
+
* @param props - Additional props to pass to the transition component
|
|
1091
|
+
* @returns The created transition object
|
|
1092
|
+
*
|
|
1093
|
+
* @example
|
|
1094
|
+
* ```ts
|
|
1095
|
+
* // Start a fade transition
|
|
1096
|
+
* engine.startTransition('fade', { duration: 1000, color: 'black' });
|
|
1097
|
+
*
|
|
1098
|
+
* // Start with onFinish callback
|
|
1099
|
+
* engine.startTransition('fade', {
|
|
1100
|
+
* duration: 1000,
|
|
1101
|
+
* onFinish: () => console.log('Fade complete')
|
|
1102
|
+
* });
|
|
1103
|
+
* ```
|
|
1104
|
+
*/
|
|
1105
|
+
startTransition(id: string, props: any = {}) {
|
|
1106
|
+
const transition = this.transitions.find((t) => t.id === id);
|
|
1107
|
+
if (!transition) {
|
|
1108
|
+
throw new Error(`Transition with id ${id} not found`);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// Get base props (can be a function or object)
|
|
1112
|
+
let baseProps = {};
|
|
1113
|
+
if (transition.props) {
|
|
1114
|
+
if (typeof transition.props === 'function') {
|
|
1115
|
+
baseProps = transition.props(this);
|
|
1116
|
+
} else {
|
|
1117
|
+
baseProps = transition.props;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Merge base props with provided props (provided props take precedence)
|
|
1122
|
+
const finalProps = {
|
|
1123
|
+
...baseProps,
|
|
1124
|
+
...props,
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
return transition.instance.start(finalProps);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
782
1130
|
async processInput({ input }: { input: Direction }) {
|
|
783
1131
|
const timestamp = Date.now();
|
|
784
1132
|
let frame: number;
|
|
@@ -900,6 +1248,72 @@ export class RpgClientEngine<T = any> {
|
|
|
900
1248
|
this.inputFrameCounter = 0;
|
|
901
1249
|
}
|
|
902
1250
|
|
|
1251
|
+
/**
|
|
1252
|
+
* Trigger a flash animation on a sprite
|
|
1253
|
+
*
|
|
1254
|
+
* This method allows you to trigger a flash effect on any sprite from client-side code.
|
|
1255
|
+
* The flash can be configured with various options including type (alpha, tint, or both),
|
|
1256
|
+
* duration, cycles, and color.
|
|
1257
|
+
*
|
|
1258
|
+
* ## Design
|
|
1259
|
+
*
|
|
1260
|
+
* The flash is applied directly to the sprite object using its flash trigger.
|
|
1261
|
+
* This is useful for client-side visual feedback, UI interactions, or local effects
|
|
1262
|
+
* that don't need to be synchronized with the server.
|
|
1263
|
+
*
|
|
1264
|
+
* @param spriteId - The ID of the sprite to flash. If not provided, flashes the current player
|
|
1265
|
+
* @param options - Flash configuration options
|
|
1266
|
+
* @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')
|
|
1267
|
+
* @param options.duration - Duration of the flash animation in milliseconds (default: 300)
|
|
1268
|
+
* @param options.cycles - Number of flash cycles (flash on/off) (default: 1)
|
|
1269
|
+
* @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)
|
|
1270
|
+
* @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)
|
|
1271
|
+
*
|
|
1272
|
+
* @example
|
|
1273
|
+
* ```ts
|
|
1274
|
+
* // Flash the current player with default settings
|
|
1275
|
+
* engine.flash();
|
|
1276
|
+
*
|
|
1277
|
+
* // Flash a specific sprite with red tint
|
|
1278
|
+
* engine.flash('sprite-id', { type: 'tint', tint: 0xff0000 });
|
|
1279
|
+
*
|
|
1280
|
+
* // Flash with both alpha and tint for dramatic effect
|
|
1281
|
+
* engine.flash(undefined, {
|
|
1282
|
+
* type: 'both',
|
|
1283
|
+
* alpha: 0.5,
|
|
1284
|
+
* tint: 0xff0000,
|
|
1285
|
+
* duration: 200,
|
|
1286
|
+
* cycles: 2
|
|
1287
|
+
* });
|
|
1288
|
+
*
|
|
1289
|
+
* // Quick damage flash on current player
|
|
1290
|
+
* engine.flash(undefined, {
|
|
1291
|
+
* type: 'tint',
|
|
1292
|
+
* tint: 'red',
|
|
1293
|
+
* duration: 150,
|
|
1294
|
+
* cycles: 1
|
|
1295
|
+
* });
|
|
1296
|
+
* ```
|
|
1297
|
+
*/
|
|
1298
|
+
flash(
|
|
1299
|
+
spriteId?: string,
|
|
1300
|
+
options?: {
|
|
1301
|
+
type?: 'alpha' | 'tint' | 'both';
|
|
1302
|
+
duration?: number;
|
|
1303
|
+
cycles?: number;
|
|
1304
|
+
alpha?: number;
|
|
1305
|
+
tint?: number | string;
|
|
1306
|
+
}
|
|
1307
|
+
): void {
|
|
1308
|
+
const targetId = spriteId || this.playerId;
|
|
1309
|
+
if (!targetId) return;
|
|
1310
|
+
|
|
1311
|
+
const sprite = this.sceneMap.getObjectById(targetId);
|
|
1312
|
+
if (sprite && typeof sprite.flash === 'function') {
|
|
1313
|
+
sprite.flash(options);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
903
1317
|
private applyServerAck(ack: { frame: number; serverTick?: number; x?: number; y?: number; direction?: Direction }) {
|
|
904
1318
|
if (this.predictionEnabled && this.prediction) {
|
|
905
1319
|
this.prediction.applyServerAck({
|