@rpgjs/client 4.0.2 → 4.0.3

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/browser/React-e57feed9.js +31136 -0
  3. package/browser/manifest.json +5 -0
  4. package/browser/rpg.client.js +435 -362
  5. package/browser/rpg.client.umd.cjs +31582 -373
  6. package/lib/Components/Component.js +6 -0
  7. package/lib/Components/Component.js.map +1 -1
  8. package/lib/GameEngine.d.ts +1 -0
  9. package/lib/GameEngine.js +6 -2
  10. package/lib/GameEngine.js.map +1 -1
  11. package/lib/{RpgGui.d.ts → Gui/Gui.d.ts} +27 -20
  12. package/lib/Gui/Gui.js +497 -0
  13. package/lib/Gui/Gui.js.map +1 -0
  14. package/lib/Gui/React.d.ts +14 -0
  15. package/lib/Gui/React.js +89 -0
  16. package/lib/Gui/React.js.map +1 -0
  17. package/lib/Gui/Vue.d.ts +13 -0
  18. package/lib/{RpgGuiCompiled.js → Gui/Vue.js} +64 -11
  19. package/lib/Gui/Vue.js.map +1 -0
  20. package/lib/Renderer.js +6 -4
  21. package/lib/Renderer.js.map +1 -1
  22. package/lib/RpgClientEngine.js +1 -1
  23. package/lib/RpgClientEngine.js.map +1 -1
  24. package/lib/Scene/Scene.d.ts +26 -1
  25. package/lib/Scene/Scene.js +32 -3
  26. package/lib/Scene/Scene.js.map +1 -1
  27. package/lib/index.d.ts +2 -1
  28. package/lib/index.js +2 -1
  29. package/lib/index.js.map +1 -1
  30. package/package.json +21 -6
  31. package/src/Components/Component.ts +6 -0
  32. package/src/GameEngine.ts +7 -3
  33. package/src/Gui/Gui.ts +556 -0
  34. package/src/Gui/React.ts +116 -0
  35. package/src/Gui/Vue.ts +137 -0
  36. package/src/Renderer.ts +8 -4
  37. package/src/RpgClientEngine.ts +1 -1
  38. package/src/Scene/Scene.ts +35 -4
  39. package/src/index.ts +2 -1
  40. package/lib/RpgGui.js +0 -499
  41. package/lib/RpgGui.js.map +0 -1
  42. package/lib/RpgGuiCompiled.d.ts +0 -3
  43. package/lib/RpgGuiCompiled.js.map +0 -1
  44. package/src/RpgGui.ts +0 -553
  45. package/src/RpgGuiCompiled.ts +0 -43
package/src/Gui/Gui.ts ADDED
@@ -0,0 +1,556 @@
1
+ import { RpgCommonPlayer, Utils } from '@rpgjs/common'
2
+ import { RpgSound } from '../Sound/RpgSound'
3
+ import { RpgClientEngine, RpgResource } from '../index'
4
+ import { RpgRenderer } from '../Renderer'
5
+ import { GameEngineClient } from '../GameEngine'
6
+ import { SceneMap } from '../Scene/Map'
7
+ import { VueGui } from './Vue'
8
+ import { Scene } from '../Scene/Scene'
9
+ import { map, tap, combineLatest, Subject, filter, Observable } from 'rxjs';
10
+
11
+ const { elementToPositionAbsolute } = Utils
12
+
13
+ interface GuiOptions {
14
+ data: any,
15
+ attachToSprite: boolean
16
+ display: boolean,
17
+ name: string
18
+ isFunction: boolean,
19
+ gui: any
20
+ }
21
+
22
+ interface GuiList {
23
+ [guiName: string]: GuiOptions
24
+ }
25
+
26
+ const COMPONENT_LIBRARIES: any = [
27
+ VueGui
28
+ ]
29
+
30
+ export class Gui {
31
+ private renderer: RpgRenderer
32
+ private gameEngine: GameEngineClient
33
+ public clientEngine: RpgClientEngine
34
+ private socket
35
+ public gui: GuiList = {}
36
+ public currentScene: Scene | null = null
37
+ private librariesInstances: any[] = []
38
+
39
+ async _initialize(clientEngine: RpgClientEngine, guiEl: HTMLDivElement) {
40
+ this.clientEngine = clientEngine
41
+ this.renderer = clientEngine.renderer
42
+ this.gameEngine = clientEngine.gameEngine
43
+ const { gui } = this.renderer.options
44
+
45
+ for (let ui of gui) {
46
+ let name = ui.name
47
+ if (Utils.isFunction(ui)) {
48
+ name = Utils.camelToKebab(name)
49
+ }
50
+ this.gui[name] = {
51
+ data: ui.data,
52
+ attachToSprite: ui.rpgAttachToSprite,
53
+ display: false,
54
+ name: name,
55
+ isFunction: Utils.isFunction(ui),
56
+ gui: ui
57
+ }
58
+ }
59
+
60
+ if (this.clientEngine.envs?.['VITE_REACT']) {
61
+ console.warn('[RPGJS] React GUI is experimental feature. So, its use may change over time. Not yet in production')
62
+ COMPONENT_LIBRARIES.push(await import('./React').then(m => m.ReactGui))
63
+ }
64
+
65
+ const propagateEvents = (el: HTMLElement) => {
66
+ const events = ['click', 'mousedown', 'mouseup', 'mousemove', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout', 'contextmenu', 'pointerdown', 'pointerup', 'pointermove', 'pointerenter', 'pointerleave', 'pointerover', 'pointerout', 'pointerupoutside', 'pointercancel', 'touchstart', 'touchend', 'touchmove', 'touchcancel', 'wheel', 'keydown', 'keyup', 'keypress', 'keydownoutside', 'keyupoutside', 'keypressoutside']
67
+ for (let type of events) {
68
+ el.addEventListener(type, (e) => {
69
+ this.renderer.canvas.dispatchEvent(new MouseEvent(type, e))
70
+ })
71
+ }
72
+ }
73
+
74
+ for (let componentClass of COMPONENT_LIBRARIES) {
75
+ const el = document.createElement('div')
76
+ elementToPositionAbsolute(el)
77
+ el.style['pointer-events'] = 'auto'
78
+ propagateEvents(el)
79
+ guiEl.appendChild(el)
80
+ this.librariesInstances.push(new componentClass(el, this))
81
+ }
82
+
83
+ guiEl.style['pointer-events'] = 'none'
84
+ }
85
+
86
+ _setSceneReady(scene: Scene) {
87
+ this.currentScene = scene
88
+ this.librariesInstances.forEach(instance => {
89
+ if (instance._setSceneReady) instance._setSceneReady(scene)
90
+ })
91
+ }
92
+
93
+ getInjectObject(): any {
94
+ const self = this
95
+ return {
96
+ /**
97
+ * Recovery of the current scene
98
+ *
99
+ * ```js
100
+ * export default {
101
+ * inject: ['rpgScene'],
102
+ * mounted() {
103
+ * const scene = this.rpgScene()
104
+ * scene.stopInputs()
105
+ * }
106
+ * }
107
+ * ```
108
+ *
109
+ * @prop {Function returns RpgScene} [rpgScene]
110
+ * @memberof VueInject
111
+ * */
112
+ rpgScene: this.renderer.getScene.bind(this.renderer),
113
+
114
+ /**
115
+ * Retrieve the main container of the game
116
+ *
117
+ * ```js
118
+ * export default {
119
+ * inject: ['rpgStage'],
120
+ * mounted() {
121
+ * const blur = new PIXI.BlurFilter()
122
+ this.rpgStage.filters = [blur]
123
+ * }
124
+ * }
125
+ * ```
126
+ *
127
+ * @prop {PIXI.Container} [rpgStage]
128
+ * @memberof VueInject
129
+ * */
130
+ rpgStage: this.renderer.stage,
131
+
132
+ /**
133
+ * Listen to all the objects present in the room (events and players)
134
+ *
135
+ * ```js
136
+ * export default {
137
+ * inject: ['rpgObjects'],
138
+ * mounted() {
139
+ * this.obs = this.rpgObjects.subscribe((objects) => {
140
+ * for (let id in objects) {
141
+ * const obj = objects[id]
142
+ * console.log(obj.object, obj.paramsChanged)
143
+ * }
144
+ * })
145
+ * },
146
+ * unmounted() {
147
+ * this.obs.unsubscribe()
148
+ * }
149
+ * }
150
+ * ```
151
+ *
152
+ * > remember to unsubscribe for memory leaks
153
+ *
154
+ * It is an observable that returns an object:
155
+ *
156
+ * * the key is the object identifier
157
+ * * The value is an object comprising:
158
+ * * `object`: The entire object
159
+ * * `paramsChanged`: Only the representation of the properties that have been changed on this object
160
+ *
161
+ * @prop {Observable<{ [objectId]: { object: object, paramsChanged: object } }>} [rpgObjects]
162
+ * @memberof VueInject
163
+ * */
164
+ rpgObjects: this.clientEngine.objects,
165
+
166
+ /**
167
+ * Recovers and listens to the current player
168
+ *
169
+ * ```js
170
+ * export default {
171
+ * inject: ['rpgCurrentPlayer'],
172
+ * mounted() {
173
+ * this.obs = this.rpgCurrentPlayer.subscribe((obj) => {
174
+ * console.log(obj.object, obj.paramsChanged)
175
+ * })
176
+ * },
177
+ * unmounted() {
178
+ * this.obs.unsubscribe()
179
+ * }
180
+ * }
181
+ * ```
182
+ *
183
+ * * `object`: The whole player
184
+ * * `paramsChanged`: Only the representation of the properties that have been changed on this player
185
+ *
186
+ * @prop {Observable<{ object: object, paramsChanged: object }>} [rpgCurrentPlayer]
187
+ * @memberof VueInject
188
+ * */
189
+ rpgCurrentPlayer: this.clientEngine.objects
190
+ .pipe(
191
+ map((objects: any) => objects[this.gameEngine.playerId]),
192
+ filter(player => !!player)
193
+ ),
194
+ rpgGameEngine: this.gameEngine,
195
+
196
+ /**
197
+ * Tell the server to close the GUI.
198
+ *
199
+ * It is a function with 2 parameters:
200
+ * * `name`: The name of the component
201
+ * * `data`: The data you want to pass to the server
202
+ *
203
+ * ```js
204
+ * export default {
205
+ * inject: ['rpgGuiClose'],
206
+ * methods: {
207
+ * close() {
208
+ * this.rpgGuiClose('gui-name', {
209
+ * amount: 1000
210
+ * })
211
+ * }
212
+ * }
213
+ * }
214
+ * ```
215
+ *
216
+ * @prop {Function(name, data)} [rpgGuiClose]
217
+ * @memberof VueInject
218
+ * */
219
+ rpgGuiClose(name: string, data?) {
220
+ const guiId = name || this.$options?.name
221
+ self.socket.emit('gui.exit', {
222
+ guiId,
223
+ data
224
+ })
225
+ },
226
+
227
+ /**
228
+ * Perform an interaction with the open GUI
229
+ *
230
+ * It is a function with 2 parameters:
231
+ * * `guiId`: The name of the component/Gui
232
+ * * `name`: The name of the interaction (defined on the server side)
233
+ * * `data`: Data to be sent
234
+ *
235
+ * ```js
236
+ * export default {
237
+ * inject: ['rpgGuiInteraction'],
238
+ * methods: {
239
+ * changeGold() {
240
+ * this.rpgGuiInteraction('gui-name', 'change-gold', {
241
+ * amount: 100
242
+ * })
243
+ * }
244
+ * }
245
+ * }
246
+ * ```
247
+ *
248
+ * @prop {Function(guiId, name, data = {})} [rpgGuiInteraction]
249
+ * @memberof VueInject
250
+ * */
251
+ rpgGuiInteraction: (guiId: string, name: string, data: any = {}) => {
252
+ this.socket.emit('gui.interaction', {
253
+ guiId,
254
+ name,
255
+ data
256
+ })
257
+ },
258
+
259
+ /**
260
+ * Listen to the keys that are pressed on the keyboard
261
+ *
262
+ * ```js
263
+ * export default {
264
+ * inject: ['rpgKeypress'],
265
+ * mounted() {
266
+ * this.obs = this.rpgKeypress.subscribe(({ inputName, control }) => {
267
+ * console.log(inputName) // "escape"
268
+ * console.log(control.actionName) // "back"
269
+ * })
270
+ * },
271
+ * unmounted() {
272
+ * this.obs.unsubscribe()
273
+ * }
274
+ * }
275
+ * ```
276
+ *
277
+ * @prop {Observable<{ inputName: string, control: { actionName: string, options: any } }>} [rpgKeypress]
278
+ * @memberof VueInject
279
+ * */
280
+ rpgKeypress: this.clientEngine.keyChange
281
+ .pipe(
282
+ map(name => {
283
+ const control = this.clientEngine.controls.getControl(name)
284
+ return {
285
+ inputName: name,
286
+ control
287
+ }
288
+ })
289
+ ),
290
+
291
+ /**
292
+ * Recovers the socket.
293
+ *
294
+ * ```js
295
+ * export default {
296
+ * inject: ['rpgSocket'],
297
+ * mounted() {
298
+ * const socket = this.rpgSocket()
299
+ * socket.emit('foo', 'bar')
300
+ * }
301
+ * }
302
+ * ```
303
+ *
304
+ * @prop {Function returns RpgScene} [rpgSocket]
305
+ * @memberof VueInject
306
+ * */
307
+ rpgSocket: () => this.socket,
308
+
309
+ /**
310
+ * The RpgGui object to control GUIs
311
+ *
312
+ * ```js
313
+ * export default {
314
+ * inject: ['rpgGui'],
315
+ * mounted() {
316
+ * const guis = this.rpgGui.getAll()
317
+ * }
318
+ * }
319
+ * ```
320
+ *
321
+ * @prop {RpgGui} [rpgGui]
322
+ * @memberof VueInject
323
+ * */
324
+ rpgGui: this,
325
+
326
+ /**
327
+ * Equivalent to RpgSound
328
+ *
329
+ * ```js
330
+ * export default {
331
+ * inject: ['rpgSound'],
332
+ * mounted() {
333
+ * this.rpgSound.get('my-sound-id').play()
334
+ * }
335
+ * }
336
+ * ```
337
+ *
338
+ * @prop {RpgSound} [rpgSound]
339
+ * @memberof VueInject
340
+ * */
341
+ rpgSound: RpgSound,
342
+
343
+ /**
344
+ * Find the game's image and sound library
345
+ *
346
+ * ```js
347
+ * export default {
348
+ * inject: ['rpgResource'],
349
+ * mounted() {
350
+ * const resourceImage = this.rpgResource.spritesheets.get('image_id')
351
+ * const resourceSound = this.rpgResource.sounds.get('sound_id')
352
+ * }
353
+ * }
354
+ * ```
355
+ *
356
+ * @prop { { spritesheets: Map, sounds: Map } } [rpgResource]
357
+ * @memberof VueInject
358
+ * */
359
+ rpgResource: RpgResource,
360
+
361
+ /**
362
+ * Get RpgClientEngine instance
363
+ *
364
+ * ```js
365
+ * export default {
366
+ * inject: ['rpgEngine'],
367
+ * mounted() {
368
+ * const vueInstance = this.rpgEngine.vueInstance
369
+ * }
370
+ * }
371
+ * ```
372
+ *
373
+ * @prop {RpgClientEngine} [rpgEngine]
374
+ * @memberof VueInject
375
+ * */
376
+ rpgEngine: this.clientEngine
377
+ }
378
+ }
379
+
380
+ /** @internal */
381
+ _setSocket(socket) {
382
+ this.socket = socket
383
+ this.socket.on('gui.open', ({ guiId, data }) => {
384
+ this.display(guiId, data)
385
+ })
386
+ this.socket.on('gui.tooltip', ({ players, display }) => {
387
+ for (let playerId of players) {
388
+ const sprite = this.renderer.getScene<SceneMap>()?.getSprite(playerId)
389
+ if (sprite) sprite.guiDisplay = display
390
+ }
391
+ })
392
+ this.socket.on('gui.exit', (guiId) => {
393
+ this.hide(guiId)
394
+ })
395
+ }
396
+
397
+ /** @internal */
398
+ _setGui(id, obj) {
399
+ const guiObj = this.get(id)
400
+ if (!guiObj) {
401
+ throw `The GUI named ${id} is non-existent. Please add the component in the gui property of the decorator @RpgClient`
402
+ }
403
+ for (let key in obj) {
404
+ guiObj[key] = obj[key]
405
+ }
406
+ this.librariesInstances.forEach(instance => {
407
+ instance.gui = Object.assign({}, this.gui)
408
+ })
409
+ }
410
+
411
+ /**
412
+ * Get a GUI. You retrieve GUI data and information whether it is displayed or not
413
+ *
414
+ * ```ts
415
+ * import { RpgGui } from '@rpgjs/client'
416
+ *
417
+ * const gui = RpgGui.get('my-gui')
418
+ * console.log(gui.display) // false
419
+ * ```
420
+ *
421
+ * @title Get a GUI
422
+ * @method RpgGui.get(id)
423
+ * @param {string} id
424
+ * @returns { { data: any, display: boolean } }
425
+ * @memberof RpgGui
426
+ */
427
+ get(id) {
428
+ if (typeof id != 'string') {
429
+ id = id.name
430
+ }
431
+ return this.gui[id]
432
+ }
433
+
434
+ /**
435
+ * Get all GUI. You retrieve GUI data and information whether it is displayed or not
436
+ *
437
+ * ```ts
438
+ * import { RpgGui } from '@rpgjs/client'
439
+ *
440
+ * const gui = RpgGui.getAll()
441
+ * console.log(gui) // { 'rpg-dialog': { data: {}, display: true } }
442
+ * ```
443
+ *
444
+ * @title Get all GUI
445
+ * @method RpgGui.getAll()
446
+ * @returns { { [guiName]: { data: any, display: boolean } }}
447
+ * @memberof RpgGui
448
+ */
449
+ getAll() {
450
+ return this.gui
451
+ }
452
+
453
+ /**
454
+ * Checks if the GUI exists RpgClient's gui array
455
+ *
456
+ * ```ts
457
+ * import { RpgGui } from '@rpgjs/client'
458
+ *
459
+ * RpgGui.exists('my-gui') // true
460
+ * ```
461
+ *
462
+ * @title GUI Exists ?
463
+ * @method RpgGui.exists(id)
464
+ * @param {string} id
465
+ * @returns {boolean}
466
+ * @memberof RpgGui
467
+ */
468
+ exists(id: string): boolean {
469
+ return !!this.get(id)
470
+ }
471
+
472
+ /**
473
+ * Calls a GUI according to identifier. You can send retrievable data in the component
474
+ *
475
+ * ```ts
476
+ * import { RpgGui } from '@rpgjs/client'
477
+ *
478
+ * RpgGui.display('my-gui')
479
+ * ```
480
+ *
481
+ * @title Display GUI
482
+ * @method RpgGui.display(id,data)
483
+ * @param {string} id
484
+ * @param {object} [data]
485
+ * @returns {void}
486
+ * @memberof RpgGui
487
+ */
488
+ display(id: string, data = {}) {
489
+ this._setGui(id, {
490
+ display: true,
491
+ data
492
+ })
493
+ }
494
+
495
+ /**
496
+ * Hide a GUI according to its identifier
497
+ *
498
+ * ```ts
499
+ * import { RpgGui } from '@rpgjs/client'
500
+ *
501
+ * RpgGui.hide('my-gui')
502
+ * ```
503
+ *
504
+ * @title Hide GUI
505
+ * @method RpgGui.hide(id)
506
+ * @param {string} id
507
+ * @returns {void}
508
+ * @memberof RpgGui
509
+ */
510
+ hide(id: string) {
511
+ this._setGui(id, {
512
+ display: false
513
+ })
514
+ }
515
+
516
+ /** @internal */
517
+ clear() {
518
+ this.gui = {}
519
+ }
520
+
521
+ /** @internal */
522
+ tooltipPosition(position: { x: number, y: number }) {
523
+ const scene = this.renderer.getScene<SceneMap>()
524
+ const viewport = scene?.viewport
525
+ if (viewport) {
526
+ const currentZoom = viewport.scale.x
527
+ const left = (position.x - viewport.left) * currentZoom
528
+ const top = (position.y - viewport.top) * currentZoom
529
+ return {
530
+ transform: `translate(${left}px,${top}px)`
531
+ }
532
+ }
533
+ return {}
534
+ }
535
+
536
+ /** @internal */
537
+ tooltipFilter(sprites: RpgCommonPlayer[]): RpgCommonPlayer[] {
538
+ return sprites.filter(tooltip => tooltip.guiDisplay)
539
+ }
540
+
541
+ /** @internal */
542
+ get listenTooltipObjects(): Observable<RpgCommonPlayer[]> {
543
+ return combineLatest(
544
+ [
545
+ this.clientEngine.gameEngine.all,
546
+ this.currentScene?.objectsMoving as Subject<any>
547
+ ]
548
+ ).pipe(
549
+ map(([objects]) => {
550
+ return Object.values(objects).map((obj: any) => obj.object)
551
+ })
552
+ )
553
+ }
554
+ }
555
+
556
+ export const RpgGui = new Gui()
@@ -0,0 +1,116 @@
1
+ import { createRoot } from 'react-dom/client';
2
+ import { createElement, Fragment, useState, createContext, useEffect, useContext, useCallback, useSyncExternalStore, useRef } from 'react'
3
+ import { RpgClientEngine } from '../RpgClientEngine';
4
+ import { RpgRenderer } from '../Renderer';
5
+ import { BehaviorSubject, map, tap, combineLatest, Subject } from 'rxjs';
6
+ import type { Gui } from './Gui';
7
+
8
+ export { useStore } from '@nanostores/react'
9
+ export const RpgReactContext = createContext({} as any)
10
+
11
+ // TODO
12
+ export const useObjects = () => {
13
+ const [objects, setObjects] = useState([] as any[])
14
+ const { rpgObjects } = useContext(RpgReactContext)
15
+ useEffect(() => {
16
+ rpgObjects
17
+ .pipe(
18
+ map((objects: any) => Object.values(objects).map((obj: any) => obj.object))
19
+ )
20
+ .subscribe(setObjects)
21
+ }, [])
22
+ return objects
23
+ }
24
+
25
+ // TODO
26
+ export const useCurrentPlayer = () => {
27
+ const { rpgCurrentPlayer } = useContext(RpgReactContext);
28
+
29
+ const currentPlayerRef = useRef({});
30
+ let _onChanges
31
+
32
+ const subscribe = (onChanges) => {
33
+ _onChanges = onChanges
34
+ return () => {
35
+ _onChanges = null
36
+ }
37
+ }
38
+
39
+ useEffect(() => {
40
+ const ob$ = rpgCurrentPlayer
41
+ .pipe(
42
+ map((player: any) => player.object),
43
+ tap((player: any) => currentPlayerRef.current = player)
44
+ );
45
+ const subscription = ob$.subscribe(() => {
46
+ _onChanges?.()
47
+ });
48
+ return () => subscription.unsubscribe();
49
+ }, []);
50
+
51
+ return useSyncExternalStore(subscribe, () => currentPlayerRef.current);
52
+ }
53
+
54
+ export class ReactGui {
55
+ private app: any
56
+ private clientEngine: RpgClientEngine
57
+ private renderer: RpgRenderer
58
+ private _gui: BehaviorSubject<any[]> = new BehaviorSubject([] as any)
59
+ //private _tooltips: BehaviorSubject<any[]> = new BehaviorSubject([] as any)
60
+
61
+ constructor(rootEl: HTMLDivElement, parentGui: Gui) {
62
+ this.app = createRoot(rootEl)
63
+ this.clientEngine = parentGui.clientEngine
64
+ this.renderer = this.clientEngine.renderer
65
+
66
+ const GuiTooltip = (ui): any => {
67
+ return () => {
68
+ const [_tooltip, setTooltip] = useState<any[]>([])
69
+ useEffect(() => {
70
+ parentGui.listenTooltipObjects.subscribe(setTooltip)
71
+ // force combineLatest to emit first value
72
+ parentGui.currentScene?.objectsMoving.next({})
73
+ }, [parentGui.currentScene])
74
+ return parentGui.tooltipFilter(_tooltip).map(sprite => createElement('div', {
75
+ style: parentGui.tooltipPosition({ x: sprite.position.x, y: sprite.position.y }),
76
+ key: sprite.id,
77
+ }, createElement(ui.gui, {
78
+ spriteData: sprite,
79
+ ...(ui.data || {}),
80
+ })))
81
+ }
82
+ }
83
+
84
+ const GuiWrapper = () => {
85
+ const [_gui, setGui] = useState<any[]>([])
86
+ useEffect(() => {
87
+ this._gui.subscribe(gui => setGui(gui))
88
+ }, [])
89
+ return createElement(RpgReactContext.Provider, {
90
+ value: parentGui.getInjectObject()
91
+ },
92
+ ..._gui.filter(ui => ui.display && !ui.attachToSprite).map(ui => createElement(ui.gui, {
93
+ key: ui.name,
94
+ ...(ui.data || {})
95
+ })),
96
+ ..._gui.filter(ui => ui.display && ui.attachToSprite).map(ui => createElement('div', {
97
+ key: ui.name
98
+ }, createElement(GuiTooltip(ui)))),
99
+ )
100
+ }
101
+
102
+ this.app.render(
103
+ createElement(GuiWrapper)
104
+ )
105
+ }
106
+
107
+ set gui(val) {
108
+ let array: any = []
109
+ for (let key in val) {
110
+ // ignore vuejs component
111
+ if (!val[key].isFunction) continue
112
+ array.push(val[key])
113
+ }
114
+ this._gui.next(array)
115
+ }
116
+ }