@rpgjs/client 5.0.0-alpha.21 → 5.0.0-alpha.22
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 +109 -0
- package/dist/RpgClientEngine.d.ts +111 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/index10.js +1 -1
- package/dist/index15.js +42 -3
- package/dist/index15.js.map +1 -1
- package/dist/index19.js.map +1 -1
- package/dist/index2.js +142 -2
- package/dist/index2.js.map +1 -1
- package/dist/index20.js +15 -2
- package/dist/index20.js.map +1 -1
- package/dist/index23.js.map +1 -1
- package/dist/index26.js +2 -2
- package/dist/index26.js.map +1 -1
- package/dist/index27.js +2 -2
- package/dist/index27.js.map +1 -1
- package/dist/index34.js.map +1 -1
- package/dist/index35.js.map +1 -1
- package/dist/index36.js +8 -1
- package/dist/index36.js.map +1 -1
- package/dist/index37.js +1 -1
- package/dist/index38.js +1 -1
- package/dist/index40.js +6 -6
- package/dist/index41.js +320 -3681
- package/dist/index41.js.map +1 -1
- package/dist/index42.js +3686 -183
- package/dist/index42.js.map +1 -1
- package/dist/index43.js +71 -498
- package/dist/index43.js.map +1 -1
- package/dist/index44.js +182 -72
- package/dist/index44.js.map +1 -1
- package/dist/index45.js +500 -2
- package/dist/index45.js.map +1 -1
- package/dist/index46.js +3 -17
- package/dist/index46.js.map +1 -1
- package/dist/index47.js +16 -142
- package/dist/index47.js.map +1 -1
- package/dist/index48.js +206 -8
- package/dist/index48.js.map +1 -1
- package/dist/index49.js +7 -108
- package/dist/index49.js.map +1 -1
- package/dist/index50.js +104 -127
- package/dist/index50.js.map +1 -1
- package/dist/index51.js +122 -123
- package/dist/index51.js.map +1 -1
- package/dist/index52.js +123 -98
- package/dist/index52.js.map +1 -1
- package/dist/index53.js +107 -136
- package/dist/index53.js.map +1 -1
- package/dist/index54.js +139 -7
- package/dist/index54.js.map +1 -1
- package/dist/index55.js +7 -52
- package/dist/index55.js.map +1 -1
- package/dist/index56.js +54 -0
- package/dist/index56.js.map +1 -0
- package/dist/index8.js +8 -0
- package/dist/index8.js.map +1 -1
- package/dist/module.d.ts +43 -4
- package/dist/services/keyboardControls.d.ts +13 -2
- package/dist/services/mmorpg.d.ts +1 -1
- package/dist/services/standalone.d.ts +1 -1
- package/package.json +11 -10
- package/src/Game/Object.ts +82 -8
- package/src/RpgClientEngine.ts +173 -2
- package/src/components/character.ce +67 -4
- package/src/components/prebuilt/index.ts +1 -0
- package/src/components/scenes/draw-map.ce +12 -3
- package/src/index.ts +2 -1
- package/src/module.ts +56 -2
- package/src/services/keyboardControls.ts +13 -1
package/src/Game/Object.ts
CHANGED
|
@@ -19,6 +19,7 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
19
19
|
frames: { x: number; y: number; ts: number }[] = [];
|
|
20
20
|
graphicsSignals = signal<any[]>([]);
|
|
21
21
|
_component = {} // temporary component memory
|
|
22
|
+
flashTrigger = trigger();
|
|
22
23
|
|
|
23
24
|
constructor() {
|
|
24
25
|
super();
|
|
@@ -86,14 +87,87 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
86
87
|
|
|
87
88
|
private animationSubscription?: Subscription;
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Trigger a flash animation on this sprite
|
|
92
|
+
*
|
|
93
|
+
* This method triggers a flash effect using CanvasEngine's flash directive.
|
|
94
|
+
* The flash can be configured with various options including type (alpha, tint, or both),
|
|
95
|
+
* duration, cycles, and color.
|
|
96
|
+
*
|
|
97
|
+
* ## Design
|
|
98
|
+
*
|
|
99
|
+
* The flash uses a trigger system that is connected to the flash directive in the
|
|
100
|
+
* character component. This allows for flexible configuration and can be triggered
|
|
101
|
+
* from both server events and client-side code.
|
|
102
|
+
*
|
|
103
|
+
* @param options - Flash configuration options
|
|
104
|
+
* @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')
|
|
105
|
+
* @param options.duration - Duration of the flash animation in milliseconds (default: 300)
|
|
106
|
+
* @param options.cycles - Number of flash cycles (flash on/off) (default: 1)
|
|
107
|
+
* @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)
|
|
108
|
+
* @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* // Simple flash with default settings (alpha flash)
|
|
113
|
+
* player.flash();
|
|
114
|
+
*
|
|
115
|
+
* // Flash with red tint
|
|
116
|
+
* player.flash({ type: 'tint', tint: 0xff0000 });
|
|
117
|
+
*
|
|
118
|
+
* // Flash with both alpha and tint
|
|
119
|
+
* player.flash({
|
|
120
|
+
* type: 'both',
|
|
121
|
+
* alpha: 0.5,
|
|
122
|
+
* tint: 0xff0000,
|
|
123
|
+
* duration: 200,
|
|
124
|
+
* cycles: 2
|
|
125
|
+
* });
|
|
126
|
+
*
|
|
127
|
+
* // Quick damage flash
|
|
128
|
+
* player.flash({
|
|
129
|
+
* type: 'tint',
|
|
130
|
+
* tint: 0xff0000,
|
|
131
|
+
* duration: 150,
|
|
132
|
+
* cycles: 1
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
flash(options?: {
|
|
137
|
+
type?: 'alpha' | 'tint' | 'both';
|
|
138
|
+
duration?: number;
|
|
139
|
+
cycles?: number;
|
|
140
|
+
alpha?: number;
|
|
141
|
+
tint?: number | string;
|
|
142
|
+
}): void {
|
|
143
|
+
const flashOptions = {
|
|
144
|
+
type: options?.type || 'alpha',
|
|
145
|
+
duration: options?.duration ?? 300,
|
|
146
|
+
cycles: options?.cycles ?? 1,
|
|
147
|
+
alpha: options?.alpha ?? 0.3,
|
|
148
|
+
tint: options?.tint ?? 0xffffff,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Convert color name to hex if needed
|
|
152
|
+
let tintValue = flashOptions.tint;
|
|
153
|
+
if (typeof tintValue === 'string') {
|
|
154
|
+
// Common color name to hex mapping
|
|
155
|
+
const colorMap: Record<string, number> = {
|
|
156
|
+
'white': 0xffffff,
|
|
157
|
+
'red': 0xff0000,
|
|
158
|
+
'green': 0x00ff00,
|
|
159
|
+
'blue': 0x0000ff,
|
|
160
|
+
'yellow': 0xffff00,
|
|
161
|
+
'cyan': 0x00ffff,
|
|
162
|
+
'magenta': 0xff00ff,
|
|
163
|
+
'black': 0x000000,
|
|
164
|
+
};
|
|
165
|
+
tintValue = colorMap[tintValue.toLowerCase()] ?? 0xffffff;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.flashTrigger.start({
|
|
169
|
+
...flashOptions,
|
|
170
|
+
tint: tintValue,
|
|
97
171
|
});
|
|
98
172
|
}
|
|
99
173
|
|
package/src/RpgClientEngine.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Canvas from "./components/scenes/canvas.ce";
|
|
2
2
|
import { Context, inject } from "@signe/di";
|
|
3
|
-
import { signal, bootstrapCanvas,
|
|
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";
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
PredictionController,
|
|
21
21
|
type PredictionState,
|
|
22
22
|
} from "@rpgjs/common";
|
|
23
|
-
import { KeyboardControls } from "./services/keyboardControls";
|
|
24
23
|
|
|
25
24
|
export class RpgClientEngine<T = any> {
|
|
26
25
|
private guiService: RpgGui;
|
|
@@ -49,6 +48,10 @@ export class RpgClientEngine<T = any> {
|
|
|
49
48
|
playerIdSignal = signal<string | null>(null);
|
|
50
49
|
spriteComponentsBehind = signal<any[]>([]);
|
|
51
50
|
spriteComponentsInFront = signal<any[]>([]);
|
|
51
|
+
/** ID of the sprite that the camera should follow. null means follow the current player */
|
|
52
|
+
cameraFollowTargetId = signal<string | null>(null);
|
|
53
|
+
/** Trigger for map shake animation */
|
|
54
|
+
mapShakeTrigger = trigger();
|
|
52
55
|
|
|
53
56
|
private predictionEnabled = false;
|
|
54
57
|
private prediction?: PredictionController<Direction>;
|
|
@@ -214,6 +217,8 @@ export class RpgClientEngine<T = any> {
|
|
|
214
217
|
|
|
215
218
|
this.webSocket.on("changeMap", (data) => {
|
|
216
219
|
this.sceneMap.reset()
|
|
220
|
+
// Reset camera follow to default (follow current player) when changing maps
|
|
221
|
+
this.cameraFollowTargetId.set(null);
|
|
217
222
|
this.loadScene(data.mapId);
|
|
218
223
|
});
|
|
219
224
|
|
|
@@ -242,6 +247,33 @@ export class RpgClientEngine<T = any> {
|
|
|
242
247
|
this.stopSound(soundId);
|
|
243
248
|
});
|
|
244
249
|
|
|
250
|
+
this.webSocket.on("stopAllSounds", () => {
|
|
251
|
+
this.stopAllSounds();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
this.webSocket.on("cameraFollow", (data) => {
|
|
255
|
+
const { targetId, smoothMove } = data;
|
|
256
|
+
this.setCameraFollow(targetId, smoothMove);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
this.webSocket.on("flash", (data) => {
|
|
260
|
+
const { object, type, duration, cycles, alpha, tint } = data;
|
|
261
|
+
const sprite = object ? this.sceneMap.getObjectById(object) : undefined;
|
|
262
|
+
if (sprite && typeof sprite.flash === 'function') {
|
|
263
|
+
sprite.flash({ type, duration, cycles, alpha, tint });
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
this.webSocket.on("shakeMap", (data) => {
|
|
268
|
+
const { intensity, duration, frequency, direction } = data || {};
|
|
269
|
+
this.mapShakeTrigger.start({
|
|
270
|
+
intensity,
|
|
271
|
+
duration,
|
|
272
|
+
frequency,
|
|
273
|
+
direction
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
245
277
|
this.webSocket.on('open', () => {
|
|
246
278
|
this.hooks.callHooks("client-engine-onConnected", this, this.socket).subscribe();
|
|
247
279
|
// Start ping/pong for synchronization
|
|
@@ -673,6 +705,79 @@ export class RpgClientEngine<T = any> {
|
|
|
673
705
|
}
|
|
674
706
|
}
|
|
675
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Stop all currently playing sounds
|
|
710
|
+
*
|
|
711
|
+
* This method stops all sounds that are currently playing.
|
|
712
|
+
* Useful when changing maps to prevent sound overlap.
|
|
713
|
+
*
|
|
714
|
+
* @example
|
|
715
|
+
* ```ts
|
|
716
|
+
* // Stop all sounds
|
|
717
|
+
* engine.stopAllSounds();
|
|
718
|
+
* ```
|
|
719
|
+
*/
|
|
720
|
+
stopAllSounds(): void {
|
|
721
|
+
this.sounds.forEach((sound) => {
|
|
722
|
+
if (sound && sound.stop) {
|
|
723
|
+
sound.stop();
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Set the camera to follow a specific sprite
|
|
730
|
+
*
|
|
731
|
+
* This method changes which sprite the camera viewport should follow.
|
|
732
|
+
* The camera will smoothly animate to the target sprite if smoothMove options are provided.
|
|
733
|
+
*
|
|
734
|
+
* ## Design
|
|
735
|
+
*
|
|
736
|
+
* The camera follow target is stored in a signal that is read by sprite components.
|
|
737
|
+
* Each sprite checks if it should be followed by comparing its ID with the target ID.
|
|
738
|
+
* When smoothMove options are provided, the viewport animation is handled by CanvasEngine's
|
|
739
|
+
* viewport system.
|
|
740
|
+
*
|
|
741
|
+
* @param targetId - The ID of the sprite to follow. Set to null to follow the current player
|
|
742
|
+
* @param smoothMove - Animation options. Can be a boolean (default: true) or an object with time and ease
|
|
743
|
+
* @param smoothMove.time - Duration of the animation in milliseconds (optional)
|
|
744
|
+
* @param smoothMove.ease - Easing function name from https://easings.net (optional)
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```ts
|
|
748
|
+
* // Follow another player with default smooth animation
|
|
749
|
+
* engine.setCameraFollow(otherPlayerId, true);
|
|
750
|
+
*
|
|
751
|
+
* // Follow an event with custom smooth animation
|
|
752
|
+
* engine.setCameraFollow(eventId, {
|
|
753
|
+
* time: 1000,
|
|
754
|
+
* ease: "easeInOutQuad"
|
|
755
|
+
* });
|
|
756
|
+
*
|
|
757
|
+
* // Follow without animation (instant)
|
|
758
|
+
* engine.setCameraFollow(targetId, false);
|
|
759
|
+
*
|
|
760
|
+
* // Return to following current player
|
|
761
|
+
* engine.setCameraFollow(null);
|
|
762
|
+
* ```
|
|
763
|
+
*/
|
|
764
|
+
setCameraFollow(
|
|
765
|
+
targetId: string | null,
|
|
766
|
+
smoothMove?: boolean | { time?: number; ease?: string }
|
|
767
|
+
): void {
|
|
768
|
+
// Store smoothMove options for potential future use with viewport animation
|
|
769
|
+
// For now, we just set the target ID and let CanvasEngine handle the viewport follow
|
|
770
|
+
// The smoothMove options could be used to configure viewport animation if CanvasEngine supports it
|
|
771
|
+
this.cameraFollowTargetId.set(targetId);
|
|
772
|
+
|
|
773
|
+
// If smoothMove is an object, we could store it for viewport configuration
|
|
774
|
+
// This would require integration with CanvasEngine's viewport animation system
|
|
775
|
+
if (typeof smoothMove === "object" && smoothMove !== null) {
|
|
776
|
+
// Future: Apply smoothMove.time and smoothMove.ease to viewport animation
|
|
777
|
+
// For now, CanvasEngine handles viewport following automatically
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
676
781
|
addParticle(particle: any) {
|
|
677
782
|
this.particleSettings.emitters.push(particle)
|
|
678
783
|
return particle;
|
|
@@ -900,6 +1005,72 @@ export class RpgClientEngine<T = any> {
|
|
|
900
1005
|
this.inputFrameCounter = 0;
|
|
901
1006
|
}
|
|
902
1007
|
|
|
1008
|
+
/**
|
|
1009
|
+
* Trigger a flash animation on a sprite
|
|
1010
|
+
*
|
|
1011
|
+
* This method allows you to trigger a flash effect on any sprite from client-side code.
|
|
1012
|
+
* The flash can be configured with various options including type (alpha, tint, or both),
|
|
1013
|
+
* duration, cycles, and color.
|
|
1014
|
+
*
|
|
1015
|
+
* ## Design
|
|
1016
|
+
*
|
|
1017
|
+
* The flash is applied directly to the sprite object using its flash trigger.
|
|
1018
|
+
* This is useful for client-side visual feedback, UI interactions, or local effects
|
|
1019
|
+
* that don't need to be synchronized with the server.
|
|
1020
|
+
*
|
|
1021
|
+
* @param spriteId - The ID of the sprite to flash. If not provided, flashes the current player
|
|
1022
|
+
* @param options - Flash configuration options
|
|
1023
|
+
* @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')
|
|
1024
|
+
* @param options.duration - Duration of the flash animation in milliseconds (default: 300)
|
|
1025
|
+
* @param options.cycles - Number of flash cycles (flash on/off) (default: 1)
|
|
1026
|
+
* @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)
|
|
1027
|
+
* @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)
|
|
1028
|
+
*
|
|
1029
|
+
* @example
|
|
1030
|
+
* ```ts
|
|
1031
|
+
* // Flash the current player with default settings
|
|
1032
|
+
* engine.flash();
|
|
1033
|
+
*
|
|
1034
|
+
* // Flash a specific sprite with red tint
|
|
1035
|
+
* engine.flash('sprite-id', { type: 'tint', tint: 0xff0000 });
|
|
1036
|
+
*
|
|
1037
|
+
* // Flash with both alpha and tint for dramatic effect
|
|
1038
|
+
* engine.flash(undefined, {
|
|
1039
|
+
* type: 'both',
|
|
1040
|
+
* alpha: 0.5,
|
|
1041
|
+
* tint: 0xff0000,
|
|
1042
|
+
* duration: 200,
|
|
1043
|
+
* cycles: 2
|
|
1044
|
+
* });
|
|
1045
|
+
*
|
|
1046
|
+
* // Quick damage flash on current player
|
|
1047
|
+
* engine.flash(undefined, {
|
|
1048
|
+
* type: 'tint',
|
|
1049
|
+
* tint: 'red',
|
|
1050
|
+
* duration: 150,
|
|
1051
|
+
* cycles: 1
|
|
1052
|
+
* });
|
|
1053
|
+
* ```
|
|
1054
|
+
*/
|
|
1055
|
+
flash(
|
|
1056
|
+
spriteId?: string,
|
|
1057
|
+
options?: {
|
|
1058
|
+
type?: 'alpha' | 'tint' | 'both';
|
|
1059
|
+
duration?: number;
|
|
1060
|
+
cycles?: number;
|
|
1061
|
+
alpha?: number;
|
|
1062
|
+
tint?: number | string;
|
|
1063
|
+
}
|
|
1064
|
+
): void {
|
|
1065
|
+
const targetId = spriteId || this.playerId;
|
|
1066
|
+
if (!targetId) return;
|
|
1067
|
+
|
|
1068
|
+
const sprite = this.sceneMap.getObjectById(targetId);
|
|
1069
|
+
if (sprite && typeof sprite.flash === 'function') {
|
|
1070
|
+
sprite.flash(options);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
903
1074
|
private applyServerAck(ack: { frame: number; serverTick?: number; x?: number; y?: number; direction?: Direction }) {
|
|
904
1075
|
if (this.predictionEnabled && this.prediction) {
|
|
905
1076
|
this.prediction.applyServerAck({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<Container x={smoothX} y={smoothY} zIndex={y} viewportFollow={
|
|
1
|
+
<Container x={smoothX} y={smoothY} zIndex={y} viewportFollow={shouldFollowCamera} controls onBeforeDestroy visible >
|
|
2
2
|
@for (component of componentsBehind) {
|
|
3
3
|
<Container>
|
|
4
4
|
<component object />
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<Particle emit={@emitParticleTrigger} settings={@particleSettings} zIndex={1000} name={particleName} />
|
|
8
8
|
<Container>
|
|
9
9
|
@for (graphicObj of graphicsSignals) {
|
|
10
|
-
<Sprite sheet={@sheet(@graphicObj)} direction tint hitbox />
|
|
10
|
+
<Sprite sheet={@sheet(@graphicObj)} direction tint hitbox flash={flashConfig} />
|
|
11
11
|
}
|
|
12
12
|
</Container>
|
|
13
13
|
@for (component of componentsInFront) {
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
</Container>
|
|
30
30
|
|
|
31
31
|
<script>
|
|
32
|
-
import { signal, effect, mount, computed, tick, animatedSignal } from "canvasengine";
|
|
32
|
+
import { signal, effect, mount, computed, tick, animatedSignal, on } from "canvasengine";
|
|
33
|
+
|
|
33
34
|
import { lastValueFrom, combineLatest, pairwise, filter, map, startWith } from "rxjs";
|
|
34
35
|
import { Particle } from "@canvasengine/presets";
|
|
35
36
|
import { GameEngineToken, ModulesToken } from "@rpgjs/common";
|
|
@@ -51,6 +52,23 @@
|
|
|
51
52
|
const componentsBehind = client.spriteComponentsBehind;
|
|
52
53
|
const componentsInFront = client.spriteComponentsInFront;
|
|
53
54
|
const isMe = computed(() => id() === playerId);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Determine if the camera should follow this sprite
|
|
58
|
+
*
|
|
59
|
+
* The camera follows this sprite if:
|
|
60
|
+
* - It's explicitly set as the camera follow target, OR
|
|
61
|
+
* - It's the current player and no explicit target is set (default behavior)
|
|
62
|
+
*/
|
|
63
|
+
const shouldFollowCamera = computed(() => {
|
|
64
|
+
const cameraTargetId = client.cameraFollowTargetId();
|
|
65
|
+
// If a target is explicitly set, only follow if this sprite is the target
|
|
66
|
+
if (cameraTargetId !== null) {
|
|
67
|
+
return id() === cameraTargetId;
|
|
68
|
+
}
|
|
69
|
+
// Otherwise, follow the current player (default behavior)
|
|
70
|
+
return isMe();
|
|
71
|
+
});
|
|
54
72
|
|
|
55
73
|
/**
|
|
56
74
|
* Get all attached GUI components that should be rendered on sprites
|
|
@@ -80,9 +98,51 @@
|
|
|
80
98
|
graphics,
|
|
81
99
|
hitbox,
|
|
82
100
|
isConnected,
|
|
83
|
-
graphicsSignals
|
|
101
|
+
graphicsSignals,
|
|
102
|
+
flashTrigger
|
|
84
103
|
} = object;
|
|
85
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Flash configuration signals for dynamic options
|
|
107
|
+
* These signals are updated when the flash trigger is activated with options
|
|
108
|
+
*/
|
|
109
|
+
const flashType = signal<'alpha' | 'tint' | 'both'>('alpha');
|
|
110
|
+
const flashDuration = signal(300);
|
|
111
|
+
const flashCycles = signal(1);
|
|
112
|
+
const flashAlpha = signal(0.3);
|
|
113
|
+
const flashTint = signal(0xffffff);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Listen to flash trigger to update configuration dynamically
|
|
117
|
+
* When flash is triggered with options, update the signals
|
|
118
|
+
*/
|
|
119
|
+
on(flashTrigger, (data) => {
|
|
120
|
+
if (data && typeof data === 'object') {
|
|
121
|
+
if (data.type !== undefined) flashType.set(data.type);
|
|
122
|
+
if (data.duration !== undefined) flashDuration.set(data.duration);
|
|
123
|
+
if (data.cycles !== undefined) flashCycles.set(data.cycles);
|
|
124
|
+
if (data.alpha !== undefined) flashAlpha.set(data.alpha);
|
|
125
|
+
if (data.tint !== undefined) flashTint.set(data.tint);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Flash configuration for the sprite
|
|
131
|
+
*
|
|
132
|
+
* This configuration is used by the flash directive to create visual feedback effects.
|
|
133
|
+
* The flash trigger is exposed through the object, allowing both server events and
|
|
134
|
+
* client-side code to trigger flash animations. Options can be passed dynamically
|
|
135
|
+
* through the trigger, which updates the reactive signals.
|
|
136
|
+
*/
|
|
137
|
+
const flashConfig = computed(() => ({
|
|
138
|
+
trigger: flashTrigger,
|
|
139
|
+
type: flashType(),
|
|
140
|
+
duration: flashDuration(),
|
|
141
|
+
cycles: flashCycles(),
|
|
142
|
+
alpha: flashAlpha(),
|
|
143
|
+
tint: flashTint(),
|
|
144
|
+
}));
|
|
145
|
+
|
|
86
146
|
const particleSettings = client.particleSettings;
|
|
87
147
|
|
|
88
148
|
const canControls = () => isMe() && object.canMove()
|
|
@@ -135,6 +195,9 @@
|
|
|
135
195
|
}
|
|
136
196
|
},
|
|
137
197
|
},
|
|
198
|
+
gamepad: {
|
|
199
|
+
enabled: true
|
|
200
|
+
}
|
|
138
201
|
});
|
|
139
202
|
|
|
140
203
|
const smoothX = animatedSignal(x(), {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<Container sound={backgroundMusic} >
|
|
1
|
+
<Container sound={backgroundMusic} shake={shakeConfig}>
|
|
2
2
|
<Container sound={backgroundAmbientSound} />
|
|
3
3
|
|
|
4
4
|
<sceneComponent() data={map.@data} params={map.@params} />
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
</Container>
|
|
14
14
|
|
|
15
15
|
<script>
|
|
16
|
-
import {
|
|
16
|
+
import { NoiseFilter } from 'pixi-filters';
|
|
17
|
+
import { effect, signal, computed, mount, on, tick } from 'canvasengine'
|
|
17
18
|
import { inject } from "../../core/inject";
|
|
18
19
|
import { RpgClientEngine } from "../../RpgClientEngine";
|
|
19
20
|
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
const animations = engine.sceneMap.animations
|
|
27
28
|
const backgroundMusic = { src: mapParams?.backgroundMusic, autoplay: true, loop: true }
|
|
28
29
|
const backgroundAmbientSound = { src: mapParams?.backgroundAmbientSound, autoplay: true, loop: true }
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
const data = signal(map().data)
|
|
31
32
|
|
|
32
33
|
const scale = mapParams?.scale || 1
|
|
@@ -36,4 +37,12 @@
|
|
|
36
37
|
const clamp = {
|
|
37
38
|
direction: "all"
|
|
38
39
|
}
|
|
40
|
+
|
|
41
|
+
const shakeConfig = {
|
|
42
|
+
trigger: engine.mapShakeTrigger,
|
|
43
|
+
intensity: 10,
|
|
44
|
+
duration: 500,
|
|
45
|
+
frequency: 10,
|
|
46
|
+
direction: 'both'
|
|
47
|
+
}
|
|
39
48
|
</script>
|
package/src/index.ts
CHANGED
|
@@ -15,4 +15,5 @@ export * from "./components/gui";
|
|
|
15
15
|
export * from "./Sound";
|
|
16
16
|
export * from "./Resource";
|
|
17
17
|
export { Context } from "@signe/di";
|
|
18
|
-
export
|
|
18
|
+
export { KeyboardControls, Input } from "canvasengine";
|
|
19
|
+
export { Control } from "./services/keyboardControls";
|
package/src/module.ts
CHANGED
|
@@ -1,15 +1,69 @@
|
|
|
1
1
|
import { findModules, provideModules } from "@rpgjs/common";
|
|
2
|
+
import { FactoryProvider } from "@signe/di";
|
|
2
3
|
import { RpgClientEngine } from "./RpgClientEngine";
|
|
3
4
|
import { RpgClient } from "./RpgClient";
|
|
4
5
|
import { inject } from "@signe/di";
|
|
5
6
|
import { RpgGui } from "./Gui/Gui";
|
|
6
7
|
import { getSoundMetadata } from "./Sound";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Type for client modules that can be either:
|
|
11
|
+
* - An object implementing RpgClient interface
|
|
12
|
+
* - A class decorated with @RpgModule decorator
|
|
13
|
+
*/
|
|
14
|
+
export type RpgClientModule = RpgClient | (new () => any);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Provides client modules configuration to Dependency Injection
|
|
18
|
+
*
|
|
19
|
+
* This function accepts an array of client modules that can be either:
|
|
20
|
+
* - Objects implementing the RpgClient interface
|
|
21
|
+
* - Classes decorated with the @RpgModule decorator (which will be instantiated)
|
|
22
|
+
*
|
|
23
|
+
* @param modules - Array of client modules (objects or classes)
|
|
24
|
+
* @returns FactoryProvider configuration for DI
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* // Using an object
|
|
28
|
+
* provideClientModules([
|
|
29
|
+
* {
|
|
30
|
+
* engine: {
|
|
31
|
+
* onConnected(engine) {
|
|
32
|
+
* console.log('Client connected')
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
* ])
|
|
37
|
+
*
|
|
38
|
+
* // Using a decorated class
|
|
39
|
+
* @RpgModule<RpgClient>({
|
|
40
|
+
* engine: {
|
|
41
|
+
* onStart(engine) {
|
|
42
|
+
* console.log('Client started')
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* })
|
|
46
|
+
* class MyClientModule {}
|
|
47
|
+
*
|
|
48
|
+
* provideClientModules([MyClientModule])
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function provideClientModules(modules: RpgClientModule[]): FactoryProvider {
|
|
9
52
|
return provideModules(modules, "client", (modules, context) => {
|
|
10
53
|
const mainModuleClient = findModules(context, 'Client')
|
|
11
54
|
modules = [...mainModuleClient, ...modules]
|
|
12
55
|
modules = modules.map((module) => {
|
|
56
|
+
// If module is a class (constructor function), instantiate it
|
|
57
|
+
// The RpgModule decorator adds properties to the prototype, which will be accessible via the instance
|
|
58
|
+
if (typeof module === 'function') {
|
|
59
|
+
const instance = new module() as any;
|
|
60
|
+
// Copy all enumerable properties (including from prototype) to a plain object
|
|
61
|
+
const moduleObj: any = {};
|
|
62
|
+
for (const key in instance) {
|
|
63
|
+
moduleObj[key] = instance[key];
|
|
64
|
+
}
|
|
65
|
+
module = moduleObj;
|
|
66
|
+
}
|
|
13
67
|
if ('client' in module) {
|
|
14
68
|
module = module.client as any;
|
|
15
69
|
}
|
|
@@ -84,7 +138,7 @@ export function provideClientModules(modules: RpgClient[]) {
|
|
|
84
138
|
const gui = [...module.gui];
|
|
85
139
|
module.gui = {
|
|
86
140
|
load: (engine: RpgClientEngine) => {
|
|
87
|
-
const guiService = inject(engine.context, RpgGui);
|
|
141
|
+
const guiService = inject(engine.context, RpgGui) as RpgGui;
|
|
88
142
|
gui.forEach((gui) => {
|
|
89
143
|
guiService.add(gui);
|
|
90
144
|
});
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import { KeyboardControls } from "canvasengine";
|
|
2
|
+
|
|
3
|
+
export enum Control {
|
|
4
|
+
Action = 'action',
|
|
5
|
+
Attack = 'attack',
|
|
6
|
+
Defense = 'defense',
|
|
7
|
+
Skill = 'skill',
|
|
8
|
+
Back = 'back',
|
|
9
|
+
Up = 1,
|
|
10
|
+
Down = 3,
|
|
11
|
+
Right = 2,
|
|
12
|
+
Left = 4
|
|
13
|
+
}
|
|
2
14
|
|
|
3
15
|
export function provideKeyboardControls() {
|
|
4
16
|
return {
|