@rpgjs/client 5.0.0-alpha.8 → 5.0.0-alpha.9

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.
@@ -8,9 +8,9 @@ import { load } from "@signe/sync";
8
8
  import { RpgClientMap } from "./Game/Map"
9
9
  import { RpgGui } from "./Gui/Gui";
10
10
  import { EffectManager } from "./Game/EffectManager";
11
- import { lastValueFrom } from "rxjs";
11
+ import { lastValueFrom, Observable } from "rxjs";
12
12
  import { GlobalConfigToken } from "./module";
13
- import { ClientIo } from "@signe/room";
13
+ import * as PIXI from "pixi.js";
14
14
 
15
15
  export class RpgClientEngine<T = any> {
16
16
  private guiService: RpgGui;
@@ -22,7 +22,6 @@ export class RpgClientEngine<T = any> {
22
22
  public globalConfig: T;
23
23
  public sceneComponent: any;
24
24
  stopProcessingInput = false;
25
-
26
25
  width = signal("100%");
27
26
  height = signal("100%");
28
27
  spritesheets: Map<string, any> = new Map();
@@ -33,6 +32,11 @@ export class RpgClientEngine<T = any> {
33
32
  } = {
34
33
  emitters: []
35
34
  }
35
+ renderer: PIXI.Renderer;
36
+ tick: Observable<number>;
37
+ playerIdSignal = signal<string | null>(null);
38
+ spriteComponentsBehind = signal<any[]>([]);
39
+ spriteComponentsInFront = signal<any[]>([]);
36
40
 
37
41
  constructor(public context: Context) {
38
42
  this.webSocket = inject(context, WebSocketToken);
@@ -45,15 +49,27 @@ export class RpgClientEngine<T = any> {
45
49
  async start() {
46
50
  this.selector = document.body.querySelector("#rpg") as HTMLElement;
47
51
 
48
- await bootstrapCanvas(this.selector, Canvas);
52
+ const { app, canvasElement } = await bootstrapCanvas(this.selector, Canvas);
53
+ this.renderer = app.renderer as PIXI.Renderer;
54
+ this.tick = canvasElement?.propObservables?.context['tick'].observable
49
55
 
50
56
  await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
51
57
 
58
+ // wondow is resize
59
+ window.addEventListener('resize', () => {
60
+ this.hooks.callHooks("client-engine-onWindowResize", this).subscribe();
61
+ })
62
+
63
+ this.tick.subscribe((tick) => {
64
+ this.hooks.callHooks("client-engine-onStep", this, tick).subscribe();
65
+ })
66
+
52
67
  this.hooks.callHooks("client-spritesheets-load", this).subscribe();
53
68
  this.hooks.callHooks("client-sounds-load", this).subscribe();
54
69
  this.hooks.callHooks("client-gui-load", this).subscribe();
55
70
  this.hooks.callHooks("client-particles-load", this).subscribe();
56
71
  this.hooks.callHooks("client-effects-load", this).subscribe();
72
+ this.hooks.callHooks("client-sprite-load", this).subscribe();
57
73
 
58
74
 
59
75
  await this.webSocket.connection(() => {
@@ -64,6 +80,8 @@ export class RpgClientEngine<T = any> {
64
80
 
65
81
  private initListeners() {
66
82
  this.webSocket.on("sync", (data) => {
83
+ if (data.pId) this.playerIdSignal.set(data.pId)
84
+ this.hooks.callHooks("client-sceneMap-onChanges", this.sceneMap, { partial: data }).subscribe();
67
85
  load(this.sceneMap, data, true);
68
86
  });
69
87
 
@@ -79,6 +97,18 @@ export class RpgClientEngine<T = any> {
79
97
  const player = this.sceneMap.getObjectById(object);
80
98
  this.getEffect(id).displayEffect(params, player)
81
99
  });
100
+
101
+ this.webSocket.on('open', () => {
102
+ this.hooks.callHooks("client-engine-onConnected", this, this.socket).subscribe();
103
+ })
104
+
105
+ this.webSocket.on('close', () => {
106
+ this.hooks.callHooks("client-engine-onDisconnected", this, this.socket).subscribe();
107
+ })
108
+
109
+ this.webSocket.on('error', (error) => {
110
+ this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket).subscribe();
111
+ })
82
112
  }
83
113
 
84
114
  private async loadScene(mapId: string) {
@@ -108,6 +138,42 @@ export class RpgClientEngine<T = any> {
108
138
  return particle;
109
139
  }
110
140
 
141
+ /**
142
+ * Add a component to render behind sprites
143
+ * Components added with this method will be displayed with a lower z-index than the sprite
144
+ *
145
+ * @param component - The component to add behind sprites
146
+ * @returns The added component
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * // Add a shadow component behind all sprites
151
+ * engine.addSpriteComponentBehind(ShadowComponent);
152
+ * ```
153
+ */
154
+ addSpriteComponentBehind(component: any) {
155
+ this.spriteComponentsBehind.update((components: any[]) => [...components, component])
156
+ return component
157
+ }
158
+
159
+ /**
160
+ * Add a component to render in front of sprites
161
+ * Components added with this method will be displayed with a higher z-index than the sprite
162
+ *
163
+ * @param component - The component to add in front of sprites
164
+ * @returns The added component
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * // Add a health bar component in front of all sprites
169
+ * engine.addSpriteComponentInFront(HealthBarComponent);
170
+ * ```
171
+ */
172
+ addSpriteComponentInFront(component: any) {
173
+ this.spriteComponentsInFront.update((components: any[]) => [...components, component])
174
+ return component
175
+ }
176
+
111
177
  addEffect(effect: {
112
178
  component: any,
113
179
  id: string
@@ -131,11 +197,25 @@ export class RpgClientEngine<T = any> {
131
197
  }
132
198
 
133
199
  processInput({ input }: { input: number }) {
200
+ this.hooks.callHooks("client-engine-onInput", this, { input, playerId: this.playerId }).subscribe();
134
201
  this.webSocket.emit('move', { input })
135
202
  }
136
203
 
137
204
  processAction({ action }: { action: number }) {
138
205
  if (this.stopProcessingInput) return;
206
+ this.hooks.callHooks("client-engine-onInput", this, { input: 'action', playerId: this.playerId }).subscribe();
139
207
  this.webSocket.emit('action', { action })
140
208
  }
209
+
210
+ get PIXI() {
211
+ return PIXI
212
+ }
213
+
214
+ get socket() {
215
+ return this.webSocket
216
+ }
217
+
218
+ get playerId() {
219
+ return this.playerIdSignal()
220
+ }
141
221
  }
@@ -1,8 +1,20 @@
1
1
  <Container x y zIndex={y} viewportFollow={isMe} controls>
2
+ @for (component of componentsBehind) {
3
+ <Container>
4
+ <component object />
5
+ </Container>
6
+ }
2
7
  <Particle emit={@emitParticleTrigger} settings={@particleSettings} zIndex={1000} name={particleName} />
3
- @for (graphicId of graphics) {
4
- <Sprite sheet={@sheet(@graphicId)} direction tint />
5
- }
8
+ <Container>
9
+ @for (graphicId of graphics) {
10
+ <Sprite sheet={@sheet(@graphicId)} direction tint />
11
+ }
12
+ </Container>
13
+ @for (component of componentsInFront) {
14
+ <Container>
15
+ <component object />
16
+ </Container>
17
+ }
6
18
  <!-- <Ellipse
7
19
  x={shadow.@x}
8
20
  y={shadow.@y}
@@ -15,19 +27,24 @@
15
27
  </Container>
16
28
 
17
29
  <script>
18
- import { signal, effect, mount, computed } from "canvasengine";
30
+ import { signal, effect, mount, computed, tick } from "canvasengine";
19
31
  import { Particle } from "@canvasengine/presets";
20
- import { GameEngineToken } from "@rpgjs/common";
32
+ import { GameEngineToken, ModulesToken } from "@rpgjs/common";
21
33
  import { RpgClientEngine } from "../RpgClientEngine";
22
34
  import { inject } from "../core/inject";
23
35
  import { Direction } from "@rpgjs/common";
24
36
  import Hit from "./effects/hit.ce";
25
37
 
26
- const { object, id, isMe } = defineProps();
27
-
38
+ const { object, id } = defineProps();
39
+
28
40
  const client = inject(RpgClientEngine);
41
+ const hooks = inject(ModulesToken);
29
42
 
30
43
  const spritesheets = client.spritesheets;
44
+ const playerId = client.playerId;
45
+ const componentsBehind = client.spriteComponentsBehind;
46
+ const componentsInFront = client.spriteComponentsInFront;
47
+ const isMe = computed(() => id() === playerId);
31
48
 
32
49
  const x = object.x;
33
50
  const y = object.y;
@@ -84,7 +101,7 @@
84
101
  keyDown() {
85
102
  if (canControls()) {
86
103
  client.processAction({ action: 'action' })
87
- // particleName.set('hit')
104
+ // particleName.set('hit')
88
105
  // emitParticleTrigger.start()
89
106
  // object.flash('red')
90
107
  }
@@ -101,4 +118,18 @@
101
118
  },
102
119
  };
103
120
  }
121
+
122
+ mount((element) => {
123
+ hooks.callHooks("client-sprite-onInit", element.componentInstance)
124
+ hooks.callHooks("client-sceneMap-onAddSprite", client.sceneMap, element.componentInstance)
125
+
126
+ return () => {
127
+ hooks.callHooks("client-sprite-onDestroy", element.componentInstance)
128
+ hooks.callHooks("client-sceneMap-onRemoveSprite", client.sceneMap, element.componentInstance)
129
+ }
130
+ })
131
+
132
+ tick(() => {
133
+ hooks.callHooks("client-sprite-onUpdate")
134
+ })
104
135
  </script>
@@ -1,10 +1,10 @@
1
1
  <Container sortableChildren={true}>
2
2
  @for ((event,id) of events) {
3
- <Character id={id} object={event} isMe={false} />
3
+ <Character id={id} object={event} />
4
4
  }
5
5
 
6
6
  @for ((player,id) of players) {
7
- <Character id={id} object={player} isMe={true} />
7
+ <Character id={id} object={player} />
8
8
  }
9
9
  </Container>
10
10
 
package/src/module.ts CHANGED
@@ -63,6 +63,24 @@ export function provideClientModules(modules: RpgClient[]) {
63
63
  },
64
64
  };
65
65
  }
66
+ if (module.sprite) {
67
+ const sprite = {...module.sprite};
68
+ module.sprite = {
69
+ ...sprite,
70
+ load: (engine: RpgClientEngine) => {
71
+ if (sprite.componentsBehind) {
72
+ sprite.componentsBehind.forEach((component) => {
73
+ engine.addSpriteComponentBehind(component);
74
+ });
75
+ }
76
+ if (sprite.componentsInFront) {
77
+ sprite.componentsInFront.forEach((component) => {
78
+ engine.addSpriteComponentInFront(component);
79
+ });
80
+ }
81
+ },
82
+ };
83
+ }
66
84
  return module;
67
85
  });
68
86
  return modules
package/tsconfig.json CHANGED
@@ -30,6 +30,6 @@
30
30
  ],
31
31
  "typeRoots": [
32
32
  "node_modules/@types",
33
- "src/types"
33
+ "node_modules/@rpgjs/client"
34
34
  ],
35
35
  }