@energy8platform/game-engine 0.7.1 → 0.9.0
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/README.md +178 -68
- package/dist/core.cjs.js +31 -7
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.d.ts +8 -1
- package/dist/core.esm.js +31 -7
- package/dist/core.esm.js.map +1 -1
- package/dist/index.cjs.js +39 -15
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.esm.js +39 -15
- package/dist/index.esm.js.map +1 -1
- package/dist/react.cjs.js +470 -0
- package/dist/react.cjs.js.map +1 -0
- package/dist/react.d.ts +871 -0
- package/dist/react.esm.js +455 -0
- package/dist/react.esm.js.map +1 -0
- package/dist/vite.cjs.js +5 -0
- package/dist/vite.cjs.js.map +1 -1
- package/dist/vite.esm.js +5 -0
- package/dist/vite.esm.js.map +1 -1
- package/package.json +25 -3
- package/src/core/GameApplication.ts +1 -0
- package/src/core/SceneManager.ts +33 -7
- package/src/react/EngineContext.ts +26 -0
- package/src/react/ReactScene.ts +88 -0
- package/src/react/applyProps.ts +107 -0
- package/src/react/catalogue.ts +17 -0
- package/src/react/createPixiRoot.ts +31 -0
- package/src/react/extendAll.ts +51 -0
- package/src/react/hooks.ts +46 -0
- package/src/react/index.ts +23 -0
- package/src/react/reconciler.ts +169 -0
- package/src/state/StateMachine.ts +11 -8
- package/src/types.ts +3 -0
- package/src/vite/index.ts +5 -0
package/src/core/SceneManager.ts
CHANGED
|
@@ -25,17 +25,22 @@ interface SceneManagerEvents {
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
28
|
+
private static MAX_TRANSITION_DEPTH = 10;
|
|
29
|
+
|
|
28
30
|
/** Root container that scenes are added to */
|
|
29
31
|
public root!: Container;
|
|
30
32
|
|
|
31
33
|
private registry = new Map<string, SceneConstructor>();
|
|
32
34
|
private stack: SceneEntry[] = [];
|
|
33
|
-
private
|
|
35
|
+
private _transitionDepth = 0;
|
|
34
36
|
|
|
35
37
|
/** Current viewport dimensions — set by ViewportManager */
|
|
36
38
|
private _width = 0;
|
|
37
39
|
private _height = 0;
|
|
38
40
|
|
|
41
|
+
/** @internal GameApplication reference — passed to scenes */
|
|
42
|
+
private _app: any;
|
|
43
|
+
|
|
39
44
|
constructor(root?: Container) {
|
|
40
45
|
super();
|
|
41
46
|
if (root) this.root = root;
|
|
@@ -46,6 +51,11 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
46
51
|
this.root = root;
|
|
47
52
|
}
|
|
48
53
|
|
|
54
|
+
/** @internal Set the app reference (called by GameApplication) */
|
|
55
|
+
setApp(app: any): void {
|
|
56
|
+
this._app = app;
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
/** Register a scene class by key */
|
|
50
60
|
register(key: string, ctor: SceneConstructor): this {
|
|
51
61
|
this.registry.set(key, ctor);
|
|
@@ -64,7 +74,7 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
64
74
|
|
|
65
75
|
/** Whether a scene transition is in progress */
|
|
66
76
|
get isTransitioning(): boolean {
|
|
67
|
-
return this.
|
|
77
|
+
return this._transitionDepth > 0;
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
/**
|
|
@@ -75,6 +85,9 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
75
85
|
data?: unknown,
|
|
76
86
|
transition?: TransitionConfig,
|
|
77
87
|
): Promise<void> {
|
|
88
|
+
if (this._transitionDepth >= SceneManager.MAX_TRANSITION_DEPTH) {
|
|
89
|
+
throw new Error('[SceneManager] Max transition depth exceeded — possible infinite loop');
|
|
90
|
+
}
|
|
78
91
|
const prevKey = this.currentKey;
|
|
79
92
|
|
|
80
93
|
// Exit all current scenes
|
|
@@ -96,6 +109,9 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
96
109
|
data?: unknown,
|
|
97
110
|
transition?: TransitionConfig,
|
|
98
111
|
): Promise<void> {
|
|
112
|
+
if (this._transitionDepth >= SceneManager.MAX_TRANSITION_DEPTH) {
|
|
113
|
+
throw new Error('[SceneManager] Max transition depth exceeded — possible infinite loop');
|
|
114
|
+
}
|
|
99
115
|
const prevKey = this.currentKey;
|
|
100
116
|
await this.pushInternal(key, data, transition);
|
|
101
117
|
this.emit('change', { from: prevKey, to: key });
|
|
@@ -109,6 +125,9 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
109
125
|
console.warn('[SceneManager] Cannot pop the last scene');
|
|
110
126
|
return;
|
|
111
127
|
}
|
|
128
|
+
if (this._transitionDepth >= SceneManager.MAX_TRANSITION_DEPTH) {
|
|
129
|
+
throw new Error('[SceneManager] Max transition depth exceeded — possible infinite loop');
|
|
130
|
+
}
|
|
112
131
|
const prevKey = this.currentKey;
|
|
113
132
|
await this.popInternal(true, transition);
|
|
114
133
|
this.emit('change', { from: prevKey, to: this.currentKey! });
|
|
@@ -122,6 +141,9 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
122
141
|
data?: unknown,
|
|
123
142
|
transition?: TransitionConfig,
|
|
124
143
|
): Promise<void> {
|
|
144
|
+
if (this._transitionDepth >= SceneManager.MAX_TRANSITION_DEPTH) {
|
|
145
|
+
throw new Error('[SceneManager] Max transition depth exceeded — possible infinite loop');
|
|
146
|
+
}
|
|
125
147
|
const prevKey = this.currentKey;
|
|
126
148
|
await this.popInternal(false);
|
|
127
149
|
await this.pushInternal(key, data, transition);
|
|
@@ -169,7 +191,11 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
169
191
|
if (!Ctor) {
|
|
170
192
|
throw new Error(`[SceneManager] Scene "${key}" is not registered`);
|
|
171
193
|
}
|
|
172
|
-
|
|
194
|
+
const scene = new Ctor();
|
|
195
|
+
if (this._app) {
|
|
196
|
+
scene.__engineApp = this._app;
|
|
197
|
+
}
|
|
198
|
+
return scene;
|
|
173
199
|
}
|
|
174
200
|
|
|
175
201
|
private async pushInternal(
|
|
@@ -177,7 +203,7 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
177
203
|
data?: unknown,
|
|
178
204
|
transition?: TransitionConfig,
|
|
179
205
|
): Promise<void> {
|
|
180
|
-
this.
|
|
206
|
+
this._transitionDepth++;
|
|
181
207
|
|
|
182
208
|
const scene = this.createScene(key);
|
|
183
209
|
this.root.addChild(scene.container);
|
|
@@ -195,7 +221,7 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
195
221
|
|
|
196
222
|
await scene.onEnter?.(data);
|
|
197
223
|
|
|
198
|
-
this.
|
|
224
|
+
this._transitionDepth--;
|
|
199
225
|
}
|
|
200
226
|
|
|
201
227
|
private async popInternal(
|
|
@@ -205,7 +231,7 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
205
231
|
const entry = this.stack.pop();
|
|
206
232
|
if (!entry) return;
|
|
207
233
|
|
|
208
|
-
this.
|
|
234
|
+
this._transitionDepth++;
|
|
209
235
|
|
|
210
236
|
await entry.scene.onExit?.();
|
|
211
237
|
|
|
@@ -216,7 +242,7 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
216
242
|
entry.scene.onDestroy?.();
|
|
217
243
|
entry.scene.container.destroy({ children: true });
|
|
218
244
|
|
|
219
|
-
this.
|
|
245
|
+
this._transitionDepth--;
|
|
220
246
|
}
|
|
221
247
|
|
|
222
248
|
private async transitionIn(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import type { GameApplication } from '../core/GameApplication';
|
|
3
|
+
import type { AudioManager } from '../audio/AudioManager';
|
|
4
|
+
import type { InputManager } from '../input/InputManager';
|
|
5
|
+
import type { ViewportManager } from '../viewport/ViewportManager';
|
|
6
|
+
import type { CasinoGameSDK } from '@energy8platform/game-sdk';
|
|
7
|
+
import type { GameConfigData } from '@energy8platform/game-sdk';
|
|
8
|
+
|
|
9
|
+
export interface EngineContextValue {
|
|
10
|
+
app: GameApplication;
|
|
11
|
+
sdk: CasinoGameSDK | null;
|
|
12
|
+
audio: AudioManager;
|
|
13
|
+
input: InputManager;
|
|
14
|
+
viewport: ViewportManager;
|
|
15
|
+
gameConfig: GameConfigData | null;
|
|
16
|
+
screen: { width: number; height: number; scale: number };
|
|
17
|
+
isPortrait: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const EngineContext = createContext<EngineContextValue | null>(null);
|
|
21
|
+
|
|
22
|
+
export function useEngine(): EngineContextValue {
|
|
23
|
+
const ctx = useContext(EngineContext);
|
|
24
|
+
if (!ctx) throw new Error('useEngine() must be used inside a ReactScene');
|
|
25
|
+
return ctx;
|
|
26
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
import { Scene } from '../core/Scene';
|
|
4
|
+
import { createPixiRoot } from './createPixiRoot';
|
|
5
|
+
import type { PixiRoot } from './createPixiRoot';
|
|
6
|
+
import { EngineContext } from './EngineContext';
|
|
7
|
+
import type { EngineContextValue } from './EngineContext';
|
|
8
|
+
import type { GameApplication } from '../core/GameApplication';
|
|
9
|
+
import { Orientation } from '../types';
|
|
10
|
+
|
|
11
|
+
export abstract class ReactScene extends Scene {
|
|
12
|
+
private _pixiRoot: PixiRoot | null = null;
|
|
13
|
+
private _contextValue: EngineContextValue | null = null;
|
|
14
|
+
|
|
15
|
+
/** Subclasses implement this to return their React element tree. */
|
|
16
|
+
abstract render(): ReactElement;
|
|
17
|
+
|
|
18
|
+
/** Access the GameApplication instance. */
|
|
19
|
+
protected getApp(): GameApplication {
|
|
20
|
+
const app = (this as any).__engineApp;
|
|
21
|
+
if (!app) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'[ReactScene] No GameApplication reference. ' +
|
|
24
|
+
'Ensure this scene is managed by SceneManager (not instantiated manually).',
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return app;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override async onEnter(data?: unknown): Promise<void> {
|
|
31
|
+
const app = this.getApp();
|
|
32
|
+
|
|
33
|
+
this._contextValue = {
|
|
34
|
+
app,
|
|
35
|
+
sdk: app.sdk,
|
|
36
|
+
audio: app.audio,
|
|
37
|
+
input: app.input,
|
|
38
|
+
viewport: app.viewport,
|
|
39
|
+
gameConfig: app.gameConfig,
|
|
40
|
+
screen: {
|
|
41
|
+
width: app.viewport.width,
|
|
42
|
+
height: app.viewport.height,
|
|
43
|
+
scale: app.viewport.scale,
|
|
44
|
+
},
|
|
45
|
+
isPortrait: app.viewport.orientation === Orientation.PORTRAIT,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
this._pixiRoot = createPixiRoot(this.container);
|
|
49
|
+
this._mountReactTree();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override async onExit(): Promise<void> {
|
|
53
|
+
this._pixiRoot?.unmount();
|
|
54
|
+
this._pixiRoot = null;
|
|
55
|
+
this._contextValue = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override onResize(width: number, height: number): void {
|
|
59
|
+
if (!this._contextValue) return;
|
|
60
|
+
const app = this.getApp();
|
|
61
|
+
|
|
62
|
+
this._contextValue = {
|
|
63
|
+
...this._contextValue,
|
|
64
|
+
screen: { width, height, scale: app.viewport.scale },
|
|
65
|
+
isPortrait: height > width,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
this._mountReactTree();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override onDestroy(): void {
|
|
72
|
+
this._pixiRoot?.unmount();
|
|
73
|
+
this._pixiRoot = null;
|
|
74
|
+
this._contextValue = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private _mountReactTree(): void {
|
|
78
|
+
if (!this._pixiRoot || !this._contextValue) return;
|
|
79
|
+
|
|
80
|
+
this._pixiRoot.render(
|
|
81
|
+
createElement(
|
|
82
|
+
EngineContext.Provider,
|
|
83
|
+
{ value: this._contextValue },
|
|
84
|
+
this.render(),
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const RESERVED = new Set(['children', 'key', 'ref']);
|
|
2
|
+
|
|
3
|
+
const REACT_TO_PIXI_EVENTS: Record<string, string> = {
|
|
4
|
+
onClick: 'onclick',
|
|
5
|
+
onPointerDown: 'onpointerdown',
|
|
6
|
+
onPointerUp: 'onpointerup',
|
|
7
|
+
onPointerMove: 'onpointermove',
|
|
8
|
+
onPointerOver: 'onpointerover',
|
|
9
|
+
onPointerOut: 'onpointerout',
|
|
10
|
+
onPointerEnter: 'onpointerenter',
|
|
11
|
+
onPointerLeave: 'onpointerleave',
|
|
12
|
+
onPointerCancel: 'onpointercancel',
|
|
13
|
+
onPointerTap: 'onpointertap',
|
|
14
|
+
onPointerUpOutside: 'onpointerupoutside',
|
|
15
|
+
onMouseDown: 'onmousedown',
|
|
16
|
+
onMouseUp: 'onmouseup',
|
|
17
|
+
onMouseMove: 'onmousemove',
|
|
18
|
+
onMouseOver: 'onmouseover',
|
|
19
|
+
onMouseOut: 'onmouseout',
|
|
20
|
+
onMouseEnter: 'onmouseenter',
|
|
21
|
+
onMouseLeave: 'onmouseleave',
|
|
22
|
+
onMouseUpOutside: 'onmouseupoutside',
|
|
23
|
+
onTouchStart: 'ontouchstart',
|
|
24
|
+
onTouchEnd: 'ontouchend',
|
|
25
|
+
onTouchMove: 'ontouchmove',
|
|
26
|
+
onTouchCancel: 'ontouchcancel',
|
|
27
|
+
onTouchEndOutside: 'ontouchendoutside',
|
|
28
|
+
onWheel: 'onwheel',
|
|
29
|
+
onRightClick: 'onrightclick',
|
|
30
|
+
onRightDown: 'onrightdown',
|
|
31
|
+
onRightUp: 'onrightup',
|
|
32
|
+
onRightUpOutside: 'onrightupoutside',
|
|
33
|
+
onTap: 'ontap',
|
|
34
|
+
onGlobalpointermove: 'onglobalpointermove',
|
|
35
|
+
onGlobalmousemove: 'onglobalmousemove',
|
|
36
|
+
onGlobaltouchmove: 'onglobaltouchmove',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function isEventProp(key: string): boolean {
|
|
40
|
+
return key in REACT_TO_PIXI_EVENTS;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function hasEventProps(props: Record<string, any>): boolean {
|
|
44
|
+
for (const key in props) {
|
|
45
|
+
if (isEventProp(key)) return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function setNestedValue(target: any, path: string[], value: any): void {
|
|
51
|
+
let obj = target;
|
|
52
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
53
|
+
obj = obj[path[i]];
|
|
54
|
+
if (obj == null) return;
|
|
55
|
+
}
|
|
56
|
+
obj[path[path.length - 1]] = value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function applyProps(
|
|
60
|
+
instance: any,
|
|
61
|
+
newProps: Record<string, any>,
|
|
62
|
+
oldProps: Record<string, any> = {},
|
|
63
|
+
): void {
|
|
64
|
+
// Remove old props not in newProps
|
|
65
|
+
for (const key in oldProps) {
|
|
66
|
+
if (RESERVED.has(key) || key in newProps) continue;
|
|
67
|
+
|
|
68
|
+
const pixiEvent = REACT_TO_PIXI_EVENTS[key];
|
|
69
|
+
if (pixiEvent) {
|
|
70
|
+
instance[pixiEvent] = null;
|
|
71
|
+
} else if (key === 'draw') {
|
|
72
|
+
// no-op: can't un-draw
|
|
73
|
+
} else if (key.includes('-')) {
|
|
74
|
+
// Nested property reset not trivially possible, skip
|
|
75
|
+
} else {
|
|
76
|
+
try {
|
|
77
|
+
instance[key] = undefined;
|
|
78
|
+
} catch {
|
|
79
|
+
// read-only or non-configurable
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Apply new props
|
|
85
|
+
for (const key in newProps) {
|
|
86
|
+
if (RESERVED.has(key)) continue;
|
|
87
|
+
|
|
88
|
+
const value = newProps[key];
|
|
89
|
+
const pixiEvent = REACT_TO_PIXI_EVENTS[key];
|
|
90
|
+
|
|
91
|
+
if (pixiEvent) {
|
|
92
|
+
instance[pixiEvent] = value;
|
|
93
|
+
} else if (key === 'draw' && typeof value === 'function') {
|
|
94
|
+
instance.clear?.();
|
|
95
|
+
value(instance);
|
|
96
|
+
} else if (key.includes('-')) {
|
|
97
|
+
const parts = key.split('-');
|
|
98
|
+
setNestedValue(instance, parts, value);
|
|
99
|
+
} else {
|
|
100
|
+
try {
|
|
101
|
+
instance[key] = value;
|
|
102
|
+
} catch {
|
|
103
|
+
// read-only property
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Mutable catalogue: PascalCase name -> PixiJS constructor */
|
|
2
|
+
export const catalogue: Record<string, any> = {};
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Register PixiJS classes for use as JSX elements.
|
|
6
|
+
* Keys must be PascalCase; JSX uses the camelCase equivalent.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { Container, Sprite, Text } from 'pixi.js';
|
|
11
|
+
* extend({ Container, Sprite, Text });
|
|
12
|
+
* // Now <container>, <sprite>, <text> work in JSX
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function extend(components: Record<string, any>): void {
|
|
16
|
+
Object.assign(catalogue, components);
|
|
17
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ConcurrentRoot } from 'react-reconciler/constants';
|
|
2
|
+
import type { Container } from 'pixi.js';
|
|
3
|
+
import type { ReactElement } from 'react';
|
|
4
|
+
import { reconciler } from './reconciler';
|
|
5
|
+
|
|
6
|
+
export interface PixiRoot {
|
|
7
|
+
render(element: ReactElement): void;
|
|
8
|
+
unmount(): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createPixiRoot(container: Container): PixiRoot {
|
|
12
|
+
const fiberRoot = reconciler.createContainer(
|
|
13
|
+
container, // containerInfo
|
|
14
|
+
ConcurrentRoot, // tag
|
|
15
|
+
null, // hydrationCallbacks
|
|
16
|
+
false, // isStrictMode
|
|
17
|
+
null, // concurrentUpdatesByDefaultOverride
|
|
18
|
+
'', // identifierPrefix
|
|
19
|
+
(err: Error) => console.error('[PixiRoot]', err),
|
|
20
|
+
null, // transitionCallbacks
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
render(element: ReactElement) {
|
|
25
|
+
reconciler.updateContainer(element, fiberRoot, null, () => {});
|
|
26
|
+
},
|
|
27
|
+
unmount() {
|
|
28
|
+
reconciler.updateContainer(null, fiberRoot, null, () => {});
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Container,
|
|
3
|
+
Sprite,
|
|
4
|
+
Graphics,
|
|
5
|
+
Text,
|
|
6
|
+
AnimatedSprite,
|
|
7
|
+
NineSliceSprite,
|
|
8
|
+
TilingSprite,
|
|
9
|
+
Mesh,
|
|
10
|
+
MeshPlane,
|
|
11
|
+
MeshRope,
|
|
12
|
+
MeshSimple,
|
|
13
|
+
BitmapText,
|
|
14
|
+
HTMLText,
|
|
15
|
+
} from 'pixi.js';
|
|
16
|
+
import { extend } from './catalogue';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Register all standard PixiJS display objects for JSX use.
|
|
20
|
+
* Call once at app startup before rendering any React scenes.
|
|
21
|
+
*/
|
|
22
|
+
export function extendPixiElements(): void {
|
|
23
|
+
extend({
|
|
24
|
+
Container,
|
|
25
|
+
Sprite,
|
|
26
|
+
Graphics,
|
|
27
|
+
Text,
|
|
28
|
+
AnimatedSprite,
|
|
29
|
+
NineSliceSprite,
|
|
30
|
+
TilingSprite,
|
|
31
|
+
Mesh,
|
|
32
|
+
MeshPlane,
|
|
33
|
+
MeshRope,
|
|
34
|
+
MeshSimple,
|
|
35
|
+
BitmapText,
|
|
36
|
+
HTMLText,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register @pixi/layout components for JSX use.
|
|
42
|
+
* Pass the dynamically imported module:
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* const layout = await import('@pixi/layout/components');
|
|
46
|
+
* extendLayoutElements(layout);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function extendLayoutElements(layoutModule: Record<string, any>): void {
|
|
50
|
+
extend(layoutModule);
|
|
51
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useEngine } from './EngineContext';
|
|
3
|
+
import type { CasinoGameSDK, SessionData, GameConfigData } from '@energy8platform/game-sdk';
|
|
4
|
+
import type { AudioManager } from '../audio/AudioManager';
|
|
5
|
+
import type { InputManager } from '../input/InputManager';
|
|
6
|
+
|
|
7
|
+
export function useSDK(): CasinoGameSDK | null {
|
|
8
|
+
return useEngine().sdk;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useAudio(): AudioManager {
|
|
12
|
+
return useEngine().audio;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useInput(): InputManager {
|
|
16
|
+
return useEngine().input;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useViewport(): { width: number; height: number; scale: number; isPortrait: boolean } {
|
|
20
|
+
const { screen, isPortrait } = useEngine();
|
|
21
|
+
return { ...screen, isPortrait };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useBalance(): number {
|
|
25
|
+
const { sdk } = useEngine();
|
|
26
|
+
const [balance, setBalance] = useState(sdk?.balance ?? 0);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!sdk) return;
|
|
30
|
+
const handler = (data: { balance: number }) => setBalance(data.balance);
|
|
31
|
+
sdk.on('balanceUpdate', handler);
|
|
32
|
+
return () => {
|
|
33
|
+
sdk.off('balanceUpdate', handler);
|
|
34
|
+
};
|
|
35
|
+
}, [sdk]);
|
|
36
|
+
|
|
37
|
+
return balance;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function useSession(): SessionData | null {
|
|
41
|
+
return useEngine().app.session;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function useGameConfig<T = GameConfigData>(): T | null {
|
|
45
|
+
return useEngine().gameConfig as T | null;
|
|
46
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Core
|
|
2
|
+
export { createPixiRoot } from './createPixiRoot';
|
|
3
|
+
export type { PixiRoot } from './createPixiRoot';
|
|
4
|
+
|
|
5
|
+
// Catalogue
|
|
6
|
+
export { extend } from './catalogue';
|
|
7
|
+
export { extendPixiElements, extendLayoutElements } from './extendAll';
|
|
8
|
+
|
|
9
|
+
// Scene
|
|
10
|
+
export { ReactScene } from './ReactScene';
|
|
11
|
+
|
|
12
|
+
// Context & Hooks
|
|
13
|
+
export { EngineContext, useEngine } from './EngineContext';
|
|
14
|
+
export type { EngineContextValue } from './EngineContext';
|
|
15
|
+
export {
|
|
16
|
+
useSDK,
|
|
17
|
+
useAudio,
|
|
18
|
+
useInput,
|
|
19
|
+
useViewport,
|
|
20
|
+
useBalance,
|
|
21
|
+
useSession,
|
|
22
|
+
useGameConfig,
|
|
23
|
+
} from './hooks';
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import Reconciler from 'react-reconciler';
|
|
2
|
+
import { DefaultEventPriority } from 'react-reconciler/constants';
|
|
3
|
+
import { Container } from 'pixi.js';
|
|
4
|
+
import { catalogue } from './catalogue';
|
|
5
|
+
import { applyProps, hasEventProps } from './applyProps';
|
|
6
|
+
|
|
7
|
+
function toPascalCase(str: string): string {
|
|
8
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const hostConfig: Reconciler.HostConfig<
|
|
12
|
+
string, // Type
|
|
13
|
+
Record<string, any>, // Props
|
|
14
|
+
Container, // Container
|
|
15
|
+
any, // Instance
|
|
16
|
+
any, // TextInstance
|
|
17
|
+
any, // SuspenseInstance
|
|
18
|
+
any, // HydratableInstance
|
|
19
|
+
any, // PublicInstance
|
|
20
|
+
any, // HostContext
|
|
21
|
+
any, // UpdatePayload
|
|
22
|
+
any, // ChildSet
|
|
23
|
+
any, // TimeoutHandle
|
|
24
|
+
any // NoTimeout
|
|
25
|
+
> = {
|
|
26
|
+
isPrimaryRenderer: false,
|
|
27
|
+
supportsMutation: true,
|
|
28
|
+
supportsPersistence: false,
|
|
29
|
+
supportsHydration: false,
|
|
30
|
+
|
|
31
|
+
createInstance(type, props) {
|
|
32
|
+
const name = toPascalCase(type);
|
|
33
|
+
const Ctor = catalogue[name];
|
|
34
|
+
if (!Ctor) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`[PixiReconciler] Unknown element "<${type}>". ` +
|
|
37
|
+
`Call extend({ ${name} }) before rendering.`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const instance = new Ctor();
|
|
41
|
+
applyProps(instance, props);
|
|
42
|
+
|
|
43
|
+
// Enable interactivity if any event prop is present
|
|
44
|
+
if (hasEventProps(props) && instance.eventMode === 'auto') {
|
|
45
|
+
instance.eventMode = 'static';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return instance;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
createTextInstance() {
|
|
52
|
+
throw new Error(
|
|
53
|
+
'[PixiReconciler] Text strings are not supported. Use a <text> element.',
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
appendInitialChild(parent, child) {
|
|
58
|
+
if (child instanceof Container) parent.addChild(child);
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
appendChild(parent, child) {
|
|
62
|
+
if (child instanceof Container) parent.addChild(child);
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
appendChildToContainer(container, child) {
|
|
66
|
+
if (child instanceof Container) container.addChild(child);
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
removeChild(parent, child) {
|
|
70
|
+
if (child instanceof Container) {
|
|
71
|
+
parent.removeChild(child);
|
|
72
|
+
child.destroy({ children: true });
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
removeChildFromContainer(container, child) {
|
|
77
|
+
if (child instanceof Container) {
|
|
78
|
+
container.removeChild(child);
|
|
79
|
+
child.destroy({ children: true });
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
insertBefore(parent, child, beforeChild) {
|
|
84
|
+
if (child instanceof Container && beforeChild instanceof Container) {
|
|
85
|
+
if (child.parent) child.parent.removeChild(child);
|
|
86
|
+
const index = parent.getChildIndex(beforeChild);
|
|
87
|
+
parent.addChildAt(child, index);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
insertInContainerBefore(container, child, beforeChild) {
|
|
92
|
+
if (child instanceof Container && beforeChild instanceof Container) {
|
|
93
|
+
if (child.parent) child.parent.removeChild(child);
|
|
94
|
+
const index = container.getChildIndex(beforeChild);
|
|
95
|
+
container.addChildAt(child, index);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
commitUpdate(instance, _updatePayload, _type, oldProps, newProps) {
|
|
100
|
+
applyProps(instance, newProps, oldProps);
|
|
101
|
+
|
|
102
|
+
if (hasEventProps(newProps) && instance.eventMode === 'auto') {
|
|
103
|
+
instance.eventMode = 'static';
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
finalizeInitialChildren() {
|
|
108
|
+
return false;
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
prepareUpdate() {
|
|
112
|
+
return true;
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
shouldSetTextContent() {
|
|
116
|
+
return false;
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
getRootHostContext() {
|
|
120
|
+
return null;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
getChildHostContext(parentHostContext: any) {
|
|
124
|
+
return parentHostContext;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
getPublicInstance(instance: any) {
|
|
128
|
+
return instance;
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
prepareForCommit() {
|
|
132
|
+
return null;
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
resetAfterCommit() {},
|
|
136
|
+
|
|
137
|
+
preparePortalMount() {},
|
|
138
|
+
|
|
139
|
+
scheduleTimeout: setTimeout,
|
|
140
|
+
cancelTimeout: clearTimeout,
|
|
141
|
+
noTimeout: -1,
|
|
142
|
+
|
|
143
|
+
getCurrentEventPriority() {
|
|
144
|
+
return DefaultEventPriority;
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
hideInstance(instance) {
|
|
148
|
+
instance.visible = false;
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
unhideInstance(instance) {
|
|
152
|
+
instance.visible = true;
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
hideTextInstance() {},
|
|
156
|
+
unhideTextInstance() {},
|
|
157
|
+
|
|
158
|
+
clearContainer() {},
|
|
159
|
+
|
|
160
|
+
detachDeletedInstance() {},
|
|
161
|
+
|
|
162
|
+
prepareScopeUpdate() {},
|
|
163
|
+
getInstanceFromNode() { return null; },
|
|
164
|
+
getInstanceFromScope() { return null; },
|
|
165
|
+
beforeActiveInstanceBlur() {},
|
|
166
|
+
afterActiveInstanceBlur() {},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const reconciler = Reconciler(hostConfig);
|