@energy8platform/game-engine 0.2.1 → 0.4.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.
Files changed (63) hide show
  1. package/README.md +400 -35
  2. package/dist/animation.cjs.js +191 -1
  3. package/dist/animation.cjs.js.map +1 -1
  4. package/dist/animation.d.ts +117 -1
  5. package/dist/animation.esm.js +192 -3
  6. package/dist/animation.esm.js.map +1 -1
  7. package/dist/audio.cjs.js +66 -16
  8. package/dist/audio.cjs.js.map +1 -1
  9. package/dist/audio.d.ts +4 -0
  10. package/dist/audio.esm.js +66 -16
  11. package/dist/audio.esm.js.map +1 -1
  12. package/dist/core.cjs.js +307 -85
  13. package/dist/core.cjs.js.map +1 -1
  14. package/dist/core.d.ts +60 -1
  15. package/dist/core.esm.js +308 -86
  16. package/dist/core.esm.js.map +1 -1
  17. package/dist/debug.cjs.js +36 -68
  18. package/dist/debug.cjs.js.map +1 -1
  19. package/dist/debug.d.ts +4 -6
  20. package/dist/debug.esm.js +36 -68
  21. package/dist/debug.esm.js.map +1 -1
  22. package/dist/index.cjs.js +997 -475
  23. package/dist/index.cjs.js.map +1 -1
  24. package/dist/index.d.ts +356 -79
  25. package/dist/index.esm.js +983 -478
  26. package/dist/index.esm.js.map +1 -1
  27. package/dist/ui.cjs.js +816 -529
  28. package/dist/ui.cjs.js.map +1 -1
  29. package/dist/ui.d.ts +179 -41
  30. package/dist/ui.esm.js +798 -531
  31. package/dist/ui.esm.js.map +1 -1
  32. package/dist/vite.cjs.js +85 -68
  33. package/dist/vite.cjs.js.map +1 -1
  34. package/dist/vite.d.ts +17 -23
  35. package/dist/vite.esm.js +86 -68
  36. package/dist/vite.esm.js.map +1 -1
  37. package/package.json +19 -5
  38. package/src/animation/SpriteAnimation.ts +210 -0
  39. package/src/animation/Tween.ts +27 -1
  40. package/src/animation/index.ts +2 -0
  41. package/src/audio/AudioManager.ts +64 -15
  42. package/src/core/EventEmitter.ts +7 -1
  43. package/src/core/GameApplication.ts +19 -7
  44. package/src/core/SceneManager.ts +3 -1
  45. package/src/debug/DevBridge.ts +49 -80
  46. package/src/index.ts +22 -0
  47. package/src/input/InputManager.ts +26 -0
  48. package/src/loading/CSSPreloader.ts +7 -33
  49. package/src/loading/LoadingScene.ts +17 -41
  50. package/src/loading/index.ts +1 -0
  51. package/src/loading/logo.ts +95 -0
  52. package/src/types.ts +4 -0
  53. package/src/ui/BalanceDisplay.ts +12 -1
  54. package/src/ui/Button.ts +71 -130
  55. package/src/ui/Layout.ts +286 -0
  56. package/src/ui/Modal.ts +6 -5
  57. package/src/ui/Panel.ts +52 -55
  58. package/src/ui/ProgressBar.ts +52 -57
  59. package/src/ui/ScrollContainer.ts +126 -0
  60. package/src/ui/Toast.ts +19 -13
  61. package/src/ui/index.ts +17 -0
  62. package/src/viewport/ViewportManager.ts +2 -0
  63. package/src/vite/index.ts +103 -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', undefined as any);
161
+ this.emit('initialized');
158
162
 
159
163
  // 7. Remove CSS preloader, show Canvas loading screen
160
164
  removeCSSPreloader(this._container);
@@ -162,11 +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
 
165
- this.emit('loaded', undefined as any);
169
+ this.emit('loaded');
166
170
 
167
171
  // 9. Start the game loop
168
172
  this._running = true;
169
- this.emit('started', undefined as any);
173
+ this.emit('started');
170
174
  } catch (err) {
171
175
  console.error('[GameEngine] Failed to start:', err);
172
176
  this.emit('error', err instanceof Error ? err : new Error(String(err)));
@@ -189,7 +193,7 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
189
193
  this.sdk?.destroy();
190
194
  this.app?.destroy(true, { children: true, texture: true });
191
195
 
192
- this.emit('destroyed', undefined as any);
196
+ this.emit('destroyed');
193
197
  this.removeAllListeners();
194
198
  }
195
199
 
@@ -208,6 +212,7 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
208
212
  this.app = new Application();
209
213
 
210
214
  const pixiOpts = {
215
+ preference: 'webgl' as const,
211
216
  background: typeof this.config.loading?.backgroundColor === 'number'
212
217
  ? this.config.loading.backgroundColor
213
218
  : 0x000000,
@@ -282,9 +287,10 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
282
287
  // Wire SceneManager to the PixiJS stage
283
288
  this.scenes.setRoot(this.app.stage);
284
289
 
285
- // Wire viewport resize → scene manager
286
- this.viewport.on('resize', ({ width, height }) => {
290
+ // Wire viewport resize → scene manager + input manager
291
+ this.viewport.on('resize', ({ width, height, scale }) => {
287
292
  this.scenes.resize(width, height);
293
+ this.input.setViewportTransform(scale, this.app.stage.x, this.app.stage.y);
288
294
  this.emit('resize', { width, height });
289
295
  });
290
296
 
@@ -305,11 +311,17 @@ export class GameApplication extends EventEmitter<GameEngineEvents> {
305
311
 
306
312
  // Trigger initial resize
307
313
  this.viewport.refresh();
314
+
315
+ // Enable FPS overlay in debug mode
316
+ if (this.config.debug) {
317
+ this.fpsOverlay = new FPSOverlay(this.app);
318
+ this.fpsOverlay.show();
319
+ }
308
320
  }
309
321
 
310
322
  private async loadAssets(firstScene: string, sceneData?: unknown): Promise<void> {
311
323
  // Register built-in loading scene
312
- this.scenes.register('__loading__', LoadingScene as any);
324
+ this.scenes.register('__loading__', LoadingScene);
313
325
 
314
326
  // Enter loading scene
315
327
  await this.scenes.goto('__loading__', {
@@ -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
 
@@ -1,8 +1,10 @@
1
- import type {
2
- InitData,
3
- GameConfigData,
4
- PlayResultData,
5
- SessionData,
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
- * Intercepts postMessage communication from the SDK and responds
48
- * with mock data, simulating a real casino host environment.
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 _listening = false;
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._listening) return;
93
+ if (this._bridge) return;
92
94
 
93
- this._handler = (e: MessageEvent) => {
94
- this.handleMessage(e);
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
- window.addEventListener('message', this._handler);
98
- this._listening = true;
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 for SDK messages');
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._handler) {
108
- window.removeEventListener('message', this._handler);
109
- this._handler = null;
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
- // Send balance update
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, _id?: string): void {
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('BALANCE_RESPONSE', { balance: this._balance }, id);
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.sendMessage('BALANCE_UPDATE', { balance: this._balance });
215
+ this._bridge?.send('BALANCE_UPDATE', { balance: this._balance });
231
216
  }
232
217
 
233
218
  // ─── Communication ─────────────────────────────────────
234
219
 
235
- private delayedSend(type: string, payload: unknown, id?: string): void {
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.sendMessage(type, payload, id), delay);
223
+ setTimeout(() => this._bridge?.send(type, payload, id), delay);
239
224
  } else {
240
- this.sendMessage(type, payload, id);
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,19 +48,41 @@ 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';
54
56
 
55
57
  // ─── UI ──────────────────────────────────────────────────
58
+ // Activate @pixi/layout mixin (must be imported before creating containers)
59
+ import '@pixi/layout';
60
+
56
61
  export { Button } from './ui/Button';
62
+ export type { ButtonConfig, ButtonState } from './ui/Button';
57
63
  export { ProgressBar } from './ui/ProgressBar';
64
+ export type { ProgressBarConfig } from './ui/ProgressBar';
58
65
  export { Label } from './ui/Label';
66
+ export type { LabelConfig } from './ui/Label';
59
67
  export { Panel } from './ui/Panel';
68
+ export type { PanelConfig } from './ui/Panel';
60
69
  export { BalanceDisplay } from './ui/BalanceDisplay';
70
+ export type { BalanceDisplayConfig } from './ui/BalanceDisplay';
61
71
  export { WinDisplay } from './ui/WinDisplay';
72
+ export type { WinDisplayConfig } from './ui/WinDisplay';
62
73
  export { Modal } from './ui/Modal';
74
+ export type { ModalConfig } from './ui/Modal';
63
75
  export { Toast } from './ui/Toast';
76
+ export type { ToastConfig, ToastType } from './ui/Toast';
77
+ export { Layout } from './ui/Layout';
78
+ export type { LayoutConfig, LayoutDirection, LayoutAlignment, LayoutAnchor } from './ui/Layout';
79
+ export { ScrollContainer } from './ui/ScrollContainer';
80
+ export type { ScrollContainerConfig, ScrollDirection } from './ui/ScrollContainer';
81
+
82
+ // Re-exports from @pixi/ui and @pixi/layout for direct use
83
+ export { FancyButton, ScrollBox, ButtonContainer } from '@pixi/ui';
84
+ export { LayoutContainer } from '@pixi/layout/components';
85
+ export type { LayoutStyles } from '@pixi/layout';
64
86
 
65
87
  // ─── Loading ─────────────────────────────────────────────
66
88
  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 = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 200" fill="none" class="ge-logo-svg">
10
- <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(#pl0)"/>
11
- <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(#pl1)"/>
12
- <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(#pl2)"/>
13
- <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(#pl3)"/>
14
- <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(#pl4)"/>
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
- * Inline SVG logo with a loader bar (clip-animated for progress).
9
- * The clipPath rect width is set to 0 initially, expanded as loading progresses.
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 buildLogoSVG(): string {
12
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 200" fill="none" style="width:100%;height:auto;">
13
- <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(#ls0)"/>
14
- <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(#ls1)"/>
15
- <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(#ls2)"/>
16
- <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(#ls3)"/>
17
- <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(#ls4)"/>
18
- <clipPath id="ge-canvas-loader-clip">
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
- ${buildLogoSVG()}
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, this._targetData);
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
  }
@@ -1,2 +1,3 @@
1
1
  export { LoadingScene } from './LoadingScene';
2
2
  export { createCSSPreloader, removeCSSPreloader } from './CSSPreloader';
3
+ export { buildLogoSVG, LOADER_BAR_MAX_WIDTH } from './logo';
@@ -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 {