@energy8platform/game-engine 0.2.0 → 0.3.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 +318 -49
- package/dist/animation.cjs.js +191 -1
- package/dist/animation.cjs.js.map +1 -1
- package/dist/animation.d.ts +117 -1
- package/dist/animation.esm.js +192 -3
- package/dist/animation.esm.js.map +1 -1
- package/dist/audio.cjs.js +66 -16
- package/dist/audio.cjs.js.map +1 -1
- package/dist/audio.d.ts +4 -0
- package/dist/audio.esm.js +66 -16
- package/dist/audio.esm.js.map +1 -1
- package/dist/core.cjs.js +310 -84
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.d.ts +60 -1
- package/dist/core.esm.js +311 -85
- package/dist/core.esm.js.map +1 -1
- package/dist/debug.cjs.js +36 -68
- package/dist/debug.cjs.js.map +1 -1
- package/dist/debug.d.ts +4 -6
- package/dist/debug.esm.js +36 -68
- package/dist/debug.esm.js.map +1 -1
- package/dist/index.cjs.js +1250 -251
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +386 -41
- package/dist/index.esm.js +1250 -254
- package/dist/index.esm.js.map +1 -1
- package/dist/ui.cjs.js +757 -1
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.d.ts +208 -2
- package/dist/ui.esm.js +756 -2
- package/dist/ui.esm.js.map +1 -1
- package/dist/vite.cjs.js +65 -68
- package/dist/vite.cjs.js.map +1 -1
- package/dist/vite.d.ts +17 -23
- package/dist/vite.esm.js +66 -68
- package/dist/vite.esm.js.map +1 -1
- package/package.json +4 -5
- package/src/animation/SpriteAnimation.ts +210 -0
- package/src/animation/Tween.ts +27 -1
- package/src/animation/index.ts +2 -0
- package/src/audio/AudioManager.ts +64 -15
- package/src/core/EventEmitter.ts +7 -1
- package/src/core/GameApplication.ts +25 -7
- package/src/core/SceneManager.ts +3 -1
- package/src/debug/DevBridge.ts +49 -80
- package/src/index.ts +6 -0
- package/src/input/InputManager.ts +26 -0
- package/src/loading/CSSPreloader.ts +7 -33
- package/src/loading/LoadingScene.ts +17 -41
- package/src/loading/index.ts +1 -0
- package/src/loading/logo.ts +95 -0
- package/src/types.ts +4 -0
- package/src/ui/BalanceDisplay.ts +14 -0
- package/src/ui/Button.ts +1 -1
- package/src/ui/Layout.ts +364 -0
- package/src/ui/ScrollContainer.ts +557 -0
- package/src/ui/index.ts +4 -0
- package/src/viewport/ViewportManager.ts +2 -0
- package/src/vite/index.ts +83 -83
|
@@ -11,6 +11,7 @@ import { InputManager } from '../input/InputManager';
|
|
|
11
11
|
import { ViewportManager } from '../viewport/ViewportManager';
|
|
12
12
|
import { LoadingScene } from '../loading/LoadingScene';
|
|
13
13
|
import { createCSSPreloader, removeCSSPreloader } from '../loading/CSSPreloader';
|
|
14
|
+
import { FPSOverlay } from '../debug/FPSOverlay';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* The main entry point for a game built on @energy8platform/game-engine.
|
|
@@ -67,6 +68,9 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
|
|
|
67
68
|
/** SDK instance (null in offline mode) */
|
|
68
69
|
public sdk: CasinoGameSDK | null = null;
|
|
69
70
|
|
|
71
|
+
/** FPS overlay instance (only when debug: true) */
|
|
72
|
+
public fpsOverlay: FPSOverlay | null = null;
|
|
73
|
+
|
|
70
74
|
/** Data received from SDK initialization */
|
|
71
75
|
public initData: InitData | null = null;
|
|
72
76
|
|
|
@@ -154,7 +158,7 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
|
|
|
154
158
|
// 6. Initialize sub-systems
|
|
155
159
|
this.initSubSystems();
|
|
156
160
|
|
|
157
|
-
this.emit('initialized'
|
|
161
|
+
this.emit('initialized');
|
|
158
162
|
|
|
159
163
|
// 7. Remove CSS preloader, show Canvas loading screen
|
|
160
164
|
removeCSSPreloader(this._container);
|
|
@@ -162,9 +166,11 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
|
|
|
162
166
|
// 8. Load assets with loading screen
|
|
163
167
|
await this.loadAssets(firstScene, sceneData);
|
|
164
168
|
|
|
169
|
+
this.emit('loaded');
|
|
170
|
+
|
|
165
171
|
// 9. Start the game loop
|
|
166
172
|
this._running = true;
|
|
167
|
-
this.emit('started'
|
|
173
|
+
this.emit('started');
|
|
168
174
|
} catch (err) {
|
|
169
175
|
console.error('[GameEngine] Failed to start:', err);
|
|
170
176
|
this.emit('error', err instanceof Error ? err : new Error(String(err)));
|
|
@@ -186,9 +192,9 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
|
|
|
186
192
|
this.viewport?.destroy();
|
|
187
193
|
this.sdk?.destroy();
|
|
188
194
|
this.app?.destroy(true, { children: true, texture: true });
|
|
189
|
-
this.removeAllListeners();
|
|
190
195
|
|
|
191
|
-
this.emit('destroyed'
|
|
196
|
+
this.emit('destroyed');
|
|
197
|
+
this.removeAllListeners();
|
|
192
198
|
}
|
|
193
199
|
|
|
194
200
|
// ─── Private initialization steps ──────────────────────
|
|
@@ -280,9 +286,10 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
|
|
|
280
286
|
// Wire SceneManager to the PixiJS stage
|
|
281
287
|
this.scenes.setRoot(this.app.stage);
|
|
282
288
|
|
|
283
|
-
// Wire viewport resize → scene manager
|
|
284
|
-
this.viewport.on('resize', ({ width, height }) => {
|
|
289
|
+
// Wire viewport resize → scene manager + input manager
|
|
290
|
+
this.viewport.on('resize', ({ width, height, scale }) => {
|
|
285
291
|
this.scenes.resize(width, height);
|
|
292
|
+
this.input.setViewportTransform(scale, this.app.stage.x, this.app.stage.y);
|
|
286
293
|
this.emit('resize', { width, height });
|
|
287
294
|
});
|
|
288
295
|
|
|
@@ -290,6 +297,11 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
|
|
|
290
297
|
this.emit('orientationChange', orientation);
|
|
291
298
|
});
|
|
292
299
|
|
|
300
|
+
// Wire scene changes → engine event
|
|
301
|
+
this.scenes.on('change', ({ from, to }) => {
|
|
302
|
+
this.emit('sceneChange', { from, to });
|
|
303
|
+
});
|
|
304
|
+
|
|
293
305
|
// Connect ticker → scene updates
|
|
294
306
|
this.app.ticker.add((ticker) => {
|
|
295
307
|
// Always update scenes (loading screen needs onUpdate before _running=true)
|
|
@@ -298,11 +310,17 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
|
|
|
298
310
|
|
|
299
311
|
// Trigger initial resize
|
|
300
312
|
this.viewport.refresh();
|
|
313
|
+
|
|
314
|
+
// Enable FPS overlay in debug mode
|
|
315
|
+
if (this.config.debug) {
|
|
316
|
+
this.fpsOverlay = new FPSOverlay(this.app);
|
|
317
|
+
this.fpsOverlay.show();
|
|
318
|
+
}
|
|
301
319
|
}
|
|
302
320
|
|
|
303
321
|
private async loadAssets(firstScene: string, sceneData?: unknown): Promise<void> {
|
|
304
322
|
// Register built-in loading scene
|
|
305
|
-
this.scenes.register('__loading__', LoadingScene
|
|
323
|
+
this.scenes.register('__loading__', LoadingScene);
|
|
306
324
|
|
|
307
325
|
// Enter loading scene
|
|
308
326
|
await this.scenes.goto('__loading__', {
|
package/src/core/SceneManager.ts
CHANGED
|
@@ -190,9 +190,11 @@ export class SceneManager extends EventEmitter<SceneManagerEvents> {
|
|
|
190
190
|
// Transition in
|
|
191
191
|
await this.transitionIn(scene.container, transition);
|
|
192
192
|
|
|
193
|
+
// Push to stack BEFORE onEnter so currentKey is correct during initialization
|
|
194
|
+
this.stack.push({ scene, key });
|
|
195
|
+
|
|
193
196
|
await scene.onEnter?.(data);
|
|
194
197
|
|
|
195
|
-
this.stack.push({ scene, key });
|
|
196
198
|
this._transitioning = false;
|
|
197
199
|
}
|
|
198
200
|
|
package/src/debug/DevBridge.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
Bridge,
|
|
3
|
+
type BridgeMessageType,
|
|
4
|
+
type InitData,
|
|
5
|
+
type GameConfigData,
|
|
6
|
+
type PlayResultData,
|
|
7
|
+
type SessionData,
|
|
6
8
|
} from '@energy8platform/game-sdk';
|
|
7
9
|
|
|
8
10
|
export interface DevBridgeConfig {
|
|
@@ -44,8 +46,9 @@ const DEFAULT_CONFIG: Required<DevBridgeConfig> = {
|
|
|
44
46
|
/**
|
|
45
47
|
* Mock host bridge for local development.
|
|
46
48
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
+
* Uses the SDK's `Bridge` class in `devMode` to communicate with
|
|
50
|
+
* `CasinoGameSDK` via a shared in-memory `MemoryChannel`, removing
|
|
51
|
+
* the need for postMessage and iframes.
|
|
49
52
|
*
|
|
50
53
|
* This allows games to be developed and tested without a real backend.
|
|
51
54
|
*
|
|
@@ -73,8 +76,7 @@ export class DevBridge {
|
|
|
73
76
|
private _config: Required<DevBridgeConfig>;
|
|
74
77
|
private _balance: number;
|
|
75
78
|
private _roundCounter = 0;
|
|
76
|
-
private
|
|
77
|
-
private _handler: ((e: MessageEvent) => void) | null = null;
|
|
79
|
+
private _bridge: Bridge | null = null;
|
|
78
80
|
|
|
79
81
|
constructor(config: DevBridgeConfig = {}) {
|
|
80
82
|
this._config = { ...DEFAULT_CONFIG, ...config };
|
|
@@ -88,27 +90,47 @@ export class DevBridge {
|
|
|
88
90
|
|
|
89
91
|
/** Start listening for SDK messages */
|
|
90
92
|
start(): void {
|
|
91
|
-
if (this.
|
|
93
|
+
if (this._bridge) return;
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
95
|
+
console.debug('[DevBridge] Starting with config:', this._config);
|
|
96
|
+
|
|
97
|
+
this._bridge = new Bridge({ devMode: true, debug: this._config.debug });
|
|
98
|
+
|
|
99
|
+
this._bridge.on('GAME_READY', (_payload: unknown, id?: string) => {
|
|
100
|
+
this.handleGameReady(id);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
this._bridge.on('PLAY_REQUEST', (payload: { action: string; bet: number; roundId?: string }, id?: string) => {
|
|
104
|
+
this.handlePlayRequest(payload, id);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
this._bridge.on('PLAY_RESULT_ACK', (payload: unknown) => {
|
|
108
|
+
this.handlePlayAck(payload);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this._bridge.on('GET_BALANCE', (_payload: unknown, id?: string) => {
|
|
112
|
+
this.handleGetBalance(id);
|
|
113
|
+
});
|
|
96
114
|
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
this._bridge.on('GET_STATE', (_payload: unknown, id?: string) => {
|
|
116
|
+
this.handleGetState(id);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this._bridge.on('OPEN_DEPOSIT', () => {
|
|
120
|
+
this.handleOpenDeposit();
|
|
121
|
+
});
|
|
99
122
|
|
|
100
123
|
if (this._config.debug) {
|
|
101
|
-
console.log('[DevBridge] Started — listening
|
|
124
|
+
console.log('[DevBridge] Started — listening via Bridge (devMode)');
|
|
102
125
|
}
|
|
103
126
|
}
|
|
104
127
|
|
|
105
128
|
/** Stop listening */
|
|
106
129
|
stop(): void {
|
|
107
|
-
if (this.
|
|
108
|
-
|
|
109
|
-
this.
|
|
130
|
+
if (this._bridge) {
|
|
131
|
+
this._bridge.destroy();
|
|
132
|
+
this._bridge = null;
|
|
110
133
|
}
|
|
111
|
-
this._listening = false;
|
|
112
134
|
|
|
113
135
|
if (this._config.debug) {
|
|
114
136
|
console.log('[DevBridge] Stopped');
|
|
@@ -118,8 +140,7 @@ export class DevBridge {
|
|
|
118
140
|
/** Set mock balance */
|
|
119
141
|
setBalance(balance: number): void {
|
|
120
142
|
this._balance = balance;
|
|
121
|
-
|
|
122
|
-
this.sendMessage('BALANCE_UPDATE', { balance: this._balance });
|
|
143
|
+
this._bridge?.send('BALANCE_UPDATE', { balance: this._balance });
|
|
123
144
|
}
|
|
124
145
|
|
|
125
146
|
/** Destroy the dev bridge */
|
|
@@ -129,42 +150,6 @@ export class DevBridge {
|
|
|
129
150
|
|
|
130
151
|
// ─── Message Handling ──────────────────────────────────
|
|
131
152
|
|
|
132
|
-
private handleMessage(e: MessageEvent): void {
|
|
133
|
-
const data = e.data;
|
|
134
|
-
|
|
135
|
-
// Only process bridge messages
|
|
136
|
-
if (!data || data.__casino_bridge !== true) return;
|
|
137
|
-
|
|
138
|
-
if (this._config.debug) {
|
|
139
|
-
console.log('[DevBridge] ←', data.type, data.payload);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
switch (data.type) {
|
|
143
|
-
case 'GAME_READY':
|
|
144
|
-
this.handleGameReady(data.id);
|
|
145
|
-
break;
|
|
146
|
-
case 'PLAY_REQUEST':
|
|
147
|
-
this.handlePlayRequest(data.payload, data.id);
|
|
148
|
-
break;
|
|
149
|
-
case 'PLAY_RESULT_ACK':
|
|
150
|
-
this.handlePlayAck(data.payload, data.id);
|
|
151
|
-
break;
|
|
152
|
-
case 'GET_BALANCE':
|
|
153
|
-
this.handleGetBalance(data.id);
|
|
154
|
-
break;
|
|
155
|
-
case 'GET_STATE':
|
|
156
|
-
this.handleGetState(data.id);
|
|
157
|
-
break;
|
|
158
|
-
case 'OPEN_DEPOSIT':
|
|
159
|
-
this.handleOpenDeposit();
|
|
160
|
-
break;
|
|
161
|
-
default:
|
|
162
|
-
if (this._config.debug) {
|
|
163
|
-
console.log('[DevBridge] Unknown message type:', data.type);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
153
|
private handleGameReady(id?: string): void {
|
|
169
154
|
const initData: InitData = {
|
|
170
155
|
balance: this._balance,
|
|
@@ -208,14 +193,14 @@ export class DevBridge {
|
|
|
208
193
|
this.delayedSend('PLAY_RESULT', result, id);
|
|
209
194
|
}
|
|
210
195
|
|
|
211
|
-
private handlePlayAck(_payload: unknown
|
|
196
|
+
private handlePlayAck(_payload: unknown): void {
|
|
212
197
|
if (this._config.debug) {
|
|
213
198
|
console.log('[DevBridge] Play acknowledged');
|
|
214
199
|
}
|
|
215
200
|
}
|
|
216
201
|
|
|
217
202
|
private handleGetBalance(id?: string): void {
|
|
218
|
-
this.delayedSend('
|
|
203
|
+
this.delayedSend('BALANCE_UPDATE', { balance: this._balance }, id);
|
|
219
204
|
}
|
|
220
205
|
|
|
221
206
|
private handleGetState(id?: string): void {
|
|
@@ -227,33 +212,17 @@ export class DevBridge {
|
|
|
227
212
|
console.log('[DevBridge] 💰 Open deposit requested (mock: adding 1000)');
|
|
228
213
|
}
|
|
229
214
|
this._balance += 1000;
|
|
230
|
-
this.
|
|
215
|
+
this._bridge?.send('BALANCE_UPDATE', { balance: this._balance });
|
|
231
216
|
}
|
|
232
217
|
|
|
233
218
|
// ─── Communication ─────────────────────────────────────
|
|
234
219
|
|
|
235
|
-
private delayedSend(type:
|
|
220
|
+
private delayedSend(type: BridgeMessageType, payload: unknown, id?: string): void {
|
|
236
221
|
const delay = this._config.networkDelay;
|
|
237
222
|
if (delay > 0) {
|
|
238
|
-
setTimeout(() => this.
|
|
223
|
+
setTimeout(() => this._bridge?.send(type, payload, id), delay);
|
|
239
224
|
} else {
|
|
240
|
-
this.
|
|
225
|
+
this._bridge?.send(type, payload, id);
|
|
241
226
|
}
|
|
242
227
|
}
|
|
243
|
-
|
|
244
|
-
private sendMessage(type: string, payload: unknown, id?: string): void {
|
|
245
|
-
const message = {
|
|
246
|
-
__casino_bridge: true,
|
|
247
|
-
type,
|
|
248
|
-
payload,
|
|
249
|
-
id,
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
if (this._config.debug) {
|
|
253
|
-
console.log('[DevBridge] →', type, payload);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Post to the same window (SDK listens on window)
|
|
257
|
-
window.postMessage(message, '*');
|
|
258
|
-
}
|
|
259
228
|
}
|
package/src/index.ts
CHANGED
|
@@ -48,6 +48,8 @@ export { Tween } from './animation/Tween';
|
|
|
48
48
|
export { Timeline } from './animation/Timeline';
|
|
49
49
|
export { Easing } from './animation/Easing';
|
|
50
50
|
export { SpineHelper } from './animation/SpineHelper';
|
|
51
|
+
export { SpriteAnimation } from './animation/SpriteAnimation';
|
|
52
|
+
export type { SpriteAnimationConfig } from './animation/SpriteAnimation';
|
|
51
53
|
|
|
52
54
|
// ─── Input ───────────────────────────────────────────────
|
|
53
55
|
export { InputManager } from './input/InputManager';
|
|
@@ -61,6 +63,10 @@ export { BalanceDisplay } from './ui/BalanceDisplay';
|
|
|
61
63
|
export { WinDisplay } from './ui/WinDisplay';
|
|
62
64
|
export { Modal } from './ui/Modal';
|
|
63
65
|
export { Toast } from './ui/Toast';
|
|
66
|
+
export { Layout } from './ui/Layout';
|
|
67
|
+
export type { LayoutConfig, LayoutDirection, LayoutAlignment, LayoutAnchor } from './ui/Layout';
|
|
68
|
+
export { ScrollContainer } from './ui/ScrollContainer';
|
|
69
|
+
export type { ScrollContainerConfig, ScrollDirection } from './ui/ScrollContainer';
|
|
64
70
|
|
|
65
71
|
// ─── Loading ─────────────────────────────────────────────
|
|
66
72
|
export { LoadingScene } from './loading/LoadingScene';
|
|
@@ -41,6 +41,11 @@ export class InputManager extends EventEmitter<InputEvents> {
|
|
|
41
41
|
private _keysDown = new Set<string>();
|
|
42
42
|
private _destroyed = false;
|
|
43
43
|
|
|
44
|
+
// Viewport transform (set by ViewportManager via setViewportTransform)
|
|
45
|
+
private _viewportScale = 1;
|
|
46
|
+
private _viewportOffsetX = 0;
|
|
47
|
+
private _viewportOffsetY = 0;
|
|
48
|
+
|
|
44
49
|
// Gesture tracking
|
|
45
50
|
private _pointerStart: { x: number; y: number; time: number } | null = null;
|
|
46
51
|
private _swipeThreshold = 50; // minimum distance in px
|
|
@@ -73,6 +78,27 @@ export class InputManager extends EventEmitter<InputEvents> {
|
|
|
73
78
|
return this._keysDown.has(key.toLowerCase());
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Update the viewport transform used for DOM→world coordinate mapping.
|
|
83
|
+
* Called automatically by GameApplication when ViewportManager emits resize.
|
|
84
|
+
*/
|
|
85
|
+
setViewportTransform(scale: number, offsetX: number, offsetY: number): void {
|
|
86
|
+
this._viewportScale = scale;
|
|
87
|
+
this._viewportOffsetX = offsetX;
|
|
88
|
+
this._viewportOffsetY = offsetY;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Convert a DOM canvas position to game-world coordinates,
|
|
93
|
+
* accounting for viewport scaling and offset.
|
|
94
|
+
*/
|
|
95
|
+
getWorldPosition(canvasX: number, canvasY: number): { x: number; y: number } {
|
|
96
|
+
return {
|
|
97
|
+
x: (canvasX - this._viewportOffsetX) / this._viewportScale,
|
|
98
|
+
y: (canvasY - this._viewportOffsetY) / this._viewportScale,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
76
102
|
/** Destroy the input manager */
|
|
77
103
|
destroy(): void {
|
|
78
104
|
this._destroyed = true;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LoadingScreenConfig } from '../types';
|
|
2
|
+
import { buildLogoSVG } from './logo';
|
|
2
3
|
|
|
3
4
|
const PRELOADER_ID = '__ge-css-preloader__';
|
|
4
5
|
|
|
@@ -6,39 +7,12 @@ const PRELOADER_ID = '__ge-css-preloader__';
|
|
|
6
7
|
* Inline SVG logo with animated loader bar.
|
|
7
8
|
* The `#loader` path acts as the progress fill — animated via clipPath.
|
|
8
9
|
*/
|
|
9
|
-
const LOGO_SVG =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<!-- Loader fill with clip for progress animation -->
|
|
16
|
-
<clipPath id="ge-loader-clip">
|
|
17
|
-
<rect x="37" y="148" width="0" height="20" class="ge-clip-rect"/>
|
|
18
|
-
</clipPath>
|
|
19
|
-
<path d="m204.5 152.6h-159.8c-2.78 0-4.45 1.69-4.45 3.99v0.11c0 2.04 1.42 3.43 3.64 3.43h160.6c2.88 0 3.67-2.07 3.67-3.43v-0.25c0-2.04-1.48-3.85-3.67-3.85z" fill="url(#pl5)" clip-path="url(#ge-loader-clip)"/>
|
|
20
|
-
<text x="125" y="196" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif" font-size="8" font-weight="600" letter-spacing="1.5" class="ge-preloader-svg-text">Loading...</text>
|
|
21
|
-
<defs>
|
|
22
|
-
<linearGradient id="pl0" x1="223.7" x2="223.7" y1="81.75" y2="127.8" gradientUnits="userSpaceOnUse">
|
|
23
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
24
|
-
</linearGradient>
|
|
25
|
-
<linearGradient id="pl1" x1="194.6" x2="194.6" y1="81.75" y2="138.3" gradientUnits="userSpaceOnUse">
|
|
26
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
27
|
-
</linearGradient>
|
|
28
|
-
<linearGradient id="pl2" x1="157.8" x2="157.8" y1="81.75" y2="127" gradientUnits="userSpaceOnUse">
|
|
29
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
30
|
-
</linearGradient>
|
|
31
|
-
<linearGradient id="pl3" x1="79.96" x2="79.96" y1="81.75" y2="141.8" gradientUnits="userSpaceOnUse">
|
|
32
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
33
|
-
</linearGradient>
|
|
34
|
-
<linearGradient id="pl4" x1="36.18" x2="212.5" y1="156.6" y2="156.6" gradientUnits="userSpaceOnUse">
|
|
35
|
-
<stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
|
|
36
|
-
</linearGradient>
|
|
37
|
-
<linearGradient id="pl5" x1="40.27" x2="208.2" y1="156.4" y2="156.4" gradientUnits="userSpaceOnUse">
|
|
38
|
-
<stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
|
|
39
|
-
</linearGradient>
|
|
40
|
-
</defs>
|
|
41
|
-
</svg>`;
|
|
10
|
+
const LOGO_SVG = buildLogoSVG({
|
|
11
|
+
idPrefix: 'pl',
|
|
12
|
+
svgClass: 'ge-logo-svg',
|
|
13
|
+
clipRectClass: 'ge-clip-rect',
|
|
14
|
+
textClass: 'ge-preloader-svg-text',
|
|
15
|
+
});
|
|
42
16
|
|
|
43
17
|
/**
|
|
44
18
|
* Creates a lightweight CSS-only preloader that appears instantly,
|
|
@@ -3,49 +3,22 @@ import { Scene } from '../core/Scene';
|
|
|
3
3
|
import { Tween } from '../animation/Tween';
|
|
4
4
|
import { Easing } from '../animation/Easing';
|
|
5
5
|
import type { LoadingScreenConfig } from '../types';
|
|
6
|
+
import { buildLogoSVG, LOADER_BAR_MAX_WIDTH } from './logo';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* Build the loading scene variant of the logo SVG.
|
|
10
|
+
* Uses unique IDs (prefixed with 'ls') to avoid collisions with CSSPreloader.
|
|
10
11
|
*/
|
|
11
|
-
function
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<rect id="ge-loader-rect" x="37" y="148" width="0" height="20"/>
|
|
20
|
-
</clipPath>
|
|
21
|
-
<path d="m204.5 152.6h-159.8c-2.78 0-4.45 1.69-4.45 3.99v0.11c0 2.04 1.42 3.43 3.64 3.43h160.6c2.88 0 3.67-2.07 3.67-3.43v-0.25c0-2.04-1.48-3.85-3.67-3.85z" fill="url(#ls5)" clip-path="url(#ge-canvas-loader-clip)"/>
|
|
22
|
-
<text id="ge-loader-pct" x="125" y="196" text-anchor="middle" fill="rgba(255,255,255,0.7)" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif" font-size="8" font-weight="600" letter-spacing="1.5">0%</text>
|
|
23
|
-
<defs>
|
|
24
|
-
<linearGradient id="ls0" x1="223.7" x2="223.7" y1="81.75" y2="127.8" gradientUnits="userSpaceOnUse">
|
|
25
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
26
|
-
</linearGradient>
|
|
27
|
-
<linearGradient id="ls1" x1="194.6" x2="194.6" y1="81.75" y2="138.3" gradientUnits="userSpaceOnUse">
|
|
28
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
29
|
-
</linearGradient>
|
|
30
|
-
<linearGradient id="ls2" x1="157.8" x2="157.8" y1="81.75" y2="127" gradientUnits="userSpaceOnUse">
|
|
31
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
32
|
-
</linearGradient>
|
|
33
|
-
<linearGradient id="ls3" x1="79.96" x2="79.96" y1="81.75" y2="141.8" gradientUnits="userSpaceOnUse">
|
|
34
|
-
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
35
|
-
</linearGradient>
|
|
36
|
-
<linearGradient id="ls4" x1="36.18" x2="212.5" y1="156.6" y2="156.6" gradientUnits="userSpaceOnUse">
|
|
37
|
-
<stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
|
|
38
|
-
</linearGradient>
|
|
39
|
-
<linearGradient id="ls5" x1="40.27" x2="208.2" y1="156.4" y2="156.4" gradientUnits="userSpaceOnUse">
|
|
40
|
-
<stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
|
|
41
|
-
</linearGradient>
|
|
42
|
-
</defs>
|
|
43
|
-
</svg>`;
|
|
12
|
+
function buildLoadingLogoSVG(): string {
|
|
13
|
+
return buildLogoSVG({
|
|
14
|
+
idPrefix: 'ls',
|
|
15
|
+
svgStyle: 'width:100%;height:auto;',
|
|
16
|
+
clipRectId: 'ge-loader-rect',
|
|
17
|
+
textId: 'ge-loader-pct',
|
|
18
|
+
textContent: '0%',
|
|
19
|
+
});
|
|
44
20
|
}
|
|
45
21
|
|
|
46
|
-
/** Max width of the loader bar in SVG units */
|
|
47
|
-
const LOADER_BAR_MAX_WIDTH = 174;
|
|
48
|
-
|
|
49
22
|
interface LoadingSceneData {
|
|
50
23
|
engine: any; // GameApplication — avoid circular import
|
|
51
24
|
targetScene: string;
|
|
@@ -194,7 +167,7 @@ export class LoadingScene extends Scene {
|
|
|
194
167
|
this._overlay.id = '__ge-loading-overlay__';
|
|
195
168
|
this._overlay.innerHTML = `
|
|
196
169
|
<div class="ge-loading-content">
|
|
197
|
-
${
|
|
170
|
+
${buildLoadingLogoSVG()}
|
|
198
171
|
</div>
|
|
199
172
|
`;
|
|
200
173
|
|
|
@@ -350,7 +323,10 @@ export class LoadingScene extends Scene {
|
|
|
350
323
|
// Remove overlay
|
|
351
324
|
this.removeOverlay();
|
|
352
325
|
|
|
353
|
-
// Navigate to the target scene
|
|
354
|
-
await this._engine.scenes.goto(this._targetScene,
|
|
326
|
+
// Navigate to the target scene, always passing the engine reference
|
|
327
|
+
await this._engine.scenes.goto(this._targetScene, {
|
|
328
|
+
engine: this._engine,
|
|
329
|
+
...(this._targetData && typeof this._targetData === 'object' ? this._targetData as Record<string, unknown> : { data: this._targetData }),
|
|
330
|
+
});
|
|
355
331
|
}
|
|
356
332
|
}
|
package/src/loading/index.ts
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Energy8 SVG logo with an embedded loader bar.
|
|
3
|
+
*
|
|
4
|
+
* The loader bar fill is controlled via a `<clipPath>` whose `<rect>` width
|
|
5
|
+
* is animatable. Different consumers customise gradient IDs and the clip
|
|
6
|
+
* element's ID/class to avoid collisions when both CSSPreloader and
|
|
7
|
+
* LoadingScene appear in the same DOM.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** SVG path data for the Energy8 wordmark — reused across loaders */
|
|
11
|
+
const WORDMARK_PATHS = `
|
|
12
|
+
<path d="m241 81.75h-19.28c-1.77 0-6.73 4.98-7.43 6.99l-4.36 12.22c-0.49 1.37 0.05 2.92 1.06 4.32-2.07 1.19-3.69 3.08-4.36 5.43l-3.25 10.41c-0.86 2.89 2.39 6.63 4.31 6.63h19.28c1.96 0 7.4-5.56 7.96-7.51l2.96-10.22c0.63-2.25 0.1-3.98-1.22-4.99 2.55-1.56 3.86-4.14 4.55-6.31l2.77-9.31c0.74-2.57-1.37-7.66-2.99-7.66zm-13.36 28.31-2.27 7.03h-8.28l2.58-8.28h8.28l-0.31 1.25zm4.06-16.97-2.11 6.7h-7.04l2.25-7.34h7.26l-0.36 0.64z" fill="url(#GID0)"/>
|
|
13
|
+
<path d="m202.5 81.75-9.31 14.97-2.32-14.97h-11.82l4.32 25.15-0.57 4.91-8.64 26.44 15.31-12.76 5.63-16.48 19.96-27.26h-12.56z" fill="url(#GID1)"/>
|
|
14
|
+
<path d="m174.2 81.75h-19.78l-5.75 5.16-10.79 33.2c-0.77 2.53 2.48 6.93 4.87 6.93h17.38c2.63 0 7.85-5.34 8.32-6.83l5.37-18.14h-15.17l-2.2 7.64h3.78l-2.25 7.2h-8.01l7.1-25.52h7.58l-1.48 8.4 12.78-5.98c1.28-0.63 1.97-3.99 1.61-6.61-0.36-2.34-1.64-5.45-3.36-5.45z" fill="url(#GID2)"/>
|
|
15
|
+
<path d="m140.6 81.75h-70.6l-5.36 19.37-4.26-19.37h-46.76l2.95 5.88-10.58 39.28h26.84l2.95-9.52-15.63-0.13 2.55-8.34h8.74l8.47-9.81h-14.61l2.11-7.3h15.47l2.54-8.71 2.58 4.74-11.4 39.07h11.05l6.46-21.49 8.84 36.33 19.18-55.67-1.83-3.36 3.68 4.09-12.07 40.1h28.18l3.39-10.31h-17.01l2.67-8.03h9.98l7.58-9.52h-14.28l1.93-6.6h14.61l3.25-9.73 2.81 5.12-11.3 38.89h11.05l5.23-17.81h1.62l1.48 17.6h10.69l-1.48-16.81c4.75-1.28 7.52-5.9 8.64-9.81l2.95-11.3c0.86-2.73-1.43-6.85-3.3-6.85zm-9.8 17.3h-8.69l2.54-7.84h8.35l-2.2 7.84z" fill="url(#GID3)"/>
|
|
16
|
+
<path d="m205.9 148.9h-122.6l-2.61-3.12h-32.4l-2.51 3.12h-1.59c-5.34 0-7.94 4.88-7.94 7.65v0.03c0 4.2 3.55 7.6 7.74 7.6h103.6l2.11 3.12h36.09l1.82-3.12h18.3c5.25 0 6.64-5.3 6.64-7.35v-0.25c0-4.23-2.9-7.68-6.64-7.68zm-0.7 12.83h-160.6c-3.69 0-6.11-2.58-6.11-5.47v-0.03c0-2.89 2.1-5.47 5.61-5.47h161.1c3.45 0 4.89 3.12 4.89 5.65v0.17c0 2.57-2.11 5.15-4.89 5.15z" fill="url(#GID4)"/>`;
|
|
17
|
+
|
|
18
|
+
/** Gradient definitions template (gradient IDs are replaced per-consumer) */
|
|
19
|
+
const GRADIENT_DEFS = `
|
|
20
|
+
<linearGradient id="GID0" x1="223.7" x2="223.7" y1="81.75" y2="127.8" gradientUnits="userSpaceOnUse">
|
|
21
|
+
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
22
|
+
</linearGradient>
|
|
23
|
+
<linearGradient id="GID1" x1="194.6" x2="194.6" y1="81.75" y2="138.3" gradientUnits="userSpaceOnUse">
|
|
24
|
+
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
25
|
+
</linearGradient>
|
|
26
|
+
<linearGradient id="GID2" x1="157.8" x2="157.8" y1="81.75" y2="127" gradientUnits="userSpaceOnUse">
|
|
27
|
+
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
28
|
+
</linearGradient>
|
|
29
|
+
<linearGradient id="GID3" x1="79.96" x2="79.96" y1="81.75" y2="141.8" gradientUnits="userSpaceOnUse">
|
|
30
|
+
<stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
|
|
31
|
+
</linearGradient>
|
|
32
|
+
<linearGradient id="GID4" x1="36.18" x2="212.5" y1="156.6" y2="156.6" gradientUnits="userSpaceOnUse">
|
|
33
|
+
<stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
|
|
34
|
+
</linearGradient>
|
|
35
|
+
<linearGradient id="GID5" x1="40.27" x2="208.2" y1="156.4" y2="156.4" gradientUnits="userSpaceOnUse">
|
|
36
|
+
<stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
|
|
37
|
+
</linearGradient>`;
|
|
38
|
+
|
|
39
|
+
/** Max width of the loader bar in SVG units */
|
|
40
|
+
export const LOADER_BAR_MAX_WIDTH = 174;
|
|
41
|
+
|
|
42
|
+
interface LogoSVGOptions {
|
|
43
|
+
/** Prefix for gradient/clip IDs to avoid collisions (e.g. 'pl' or 'ls') */
|
|
44
|
+
idPrefix: string;
|
|
45
|
+
/** Optional CSS class on the root <svg> */
|
|
46
|
+
svgClass?: string;
|
|
47
|
+
/** Optional inline style on the root <svg> */
|
|
48
|
+
svgStyle?: string;
|
|
49
|
+
/** Optional CSS class on the clip <rect> */
|
|
50
|
+
clipRectClass?: string;
|
|
51
|
+
/** Optional id on the clip <rect> (for JS access) */
|
|
52
|
+
clipRectId?: string;
|
|
53
|
+
/** Optional id on the percentage <text> */
|
|
54
|
+
textId?: string;
|
|
55
|
+
/** Default text content */
|
|
56
|
+
textContent?: string;
|
|
57
|
+
/** Optional CSS class on the <text> */
|
|
58
|
+
textClass?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build the Energy8 SVG logo with a loader bar, using unique IDs.
|
|
63
|
+
*
|
|
64
|
+
* @param opts - Configuration to avoid element ID collisions
|
|
65
|
+
* @returns SVG markup string
|
|
66
|
+
*/
|
|
67
|
+
export function buildLogoSVG(opts: LogoSVGOptions): string {
|
|
68
|
+
const { idPrefix, svgClass, svgStyle, clipRectClass, clipRectId, textId, textContent, textClass } = opts;
|
|
69
|
+
|
|
70
|
+
// Replace gradient ID placeholders with prefixed versions
|
|
71
|
+
const paths = WORDMARK_PATHS.replace(/GID(\d)/g, `${idPrefix}$1`);
|
|
72
|
+
const defs = GRADIENT_DEFS.replace(/GID(\d)/g, `${idPrefix}$1`);
|
|
73
|
+
|
|
74
|
+
const clipId = `${idPrefix}-loader-clip`;
|
|
75
|
+
const fillGradientId = `${idPrefix}5`;
|
|
76
|
+
|
|
77
|
+
const classAttr = svgClass ? ` class="${svgClass}"` : '';
|
|
78
|
+
const styleAttr = svgStyle ? ` style="${svgStyle}"` : '';
|
|
79
|
+
const rectClassAttr = clipRectClass ? ` class="${clipRectClass}"` : '';
|
|
80
|
+
const rectIdAttr = clipRectId ? ` id="${clipRectId}"` : '';
|
|
81
|
+
const txtIdAttr = textId ? ` id="${textId}"` : '';
|
|
82
|
+
const txtClassAttr = textClass ? ` class="${textClass}"` : '';
|
|
83
|
+
|
|
84
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 200" fill="none"${classAttr}${styleAttr}>
|
|
85
|
+
${paths}
|
|
86
|
+
<clipPath id="${clipId}">
|
|
87
|
+
<rect${rectIdAttr} x="37" y="148" width="0" height="20"${rectClassAttr}/>
|
|
88
|
+
</clipPath>
|
|
89
|
+
<path d="m204.5 152.6h-159.8c-2.78 0-4.45 1.69-4.45 3.99v0.11c0 2.04 1.42 3.43 3.64 3.43h160.6c2.88 0 3.67-2.07 3.67-3.43v-0.25c0-2.04-1.48-3.85-3.67-3.85z" fill="url(#${fillGradientId})" clip-path="url(#${clipId})"/>
|
|
90
|
+
<text${txtIdAttr} x="125" y="196" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif" font-size="8" font-weight="600" letter-spacing="1.5"${txtClassAttr}>${textContent ?? 'Loading...'}</text>
|
|
91
|
+
<defs>
|
|
92
|
+
${defs}
|
|
93
|
+
</defs>
|
|
94
|
+
</svg>`;
|
|
95
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -121,6 +121,8 @@ export interface GameApplicationConfig {
|
|
|
121
121
|
parentOrigin?: string;
|
|
122
122
|
timeout?: number;
|
|
123
123
|
debug?: boolean;
|
|
124
|
+
/** Use in-memory channel instead of postMessage (no iframe required) */
|
|
125
|
+
devMode?: boolean;
|
|
124
126
|
}
|
|
125
127
|
| false;
|
|
126
128
|
|
|
@@ -135,6 +137,8 @@ export interface GameApplicationConfig {
|
|
|
135
137
|
|
|
136
138
|
export interface SceneConstructor {
|
|
137
139
|
new (): IScene;
|
|
140
|
+
// Allow Scene subclasses to be used as constructors
|
|
141
|
+
[key: string]: any;
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
export interface IScene {
|