@energy8platform/game-engine 0.18.0 → 0.20.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/dist/audio.cjs.js +15 -5
- package/dist/audio.cjs.js.map +1 -1
- package/dist/audio.d.ts +5 -0
- package/dist/audio.esm.js +15 -5
- package/dist/audio.esm.js.map +1 -1
- package/dist/core.cjs.js +79 -174
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.d.ts +13 -1
- package/dist/core.esm.js +80 -175
- package/dist/core.esm.js.map +1 -1
- package/dist/host.cjs.js +401 -251
- package/dist/host.cjs.js.map +1 -1
- package/dist/host.d.ts +117 -26
- package/dist/host.esm.js +403 -253
- package/dist/host.esm.js.map +1 -1
- package/dist/index.cjs.js +79 -174
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +23 -12
- package/dist/index.esm.js +80 -175
- package/dist/index.esm.js.map +1 -1
- package/dist/react.d.ts +13 -1
- package/package.json +3 -2
- package/src/audio/AudioManager.ts +17 -5
- package/src/core/GameApplication.ts +28 -10
- package/src/host/createSlotGame.ts +143 -23
- package/src/host/index.ts +4 -1
- package/src/host/overlayController.ts +81 -0
- package/src/host/pauseController.ts +21 -0
- package/src/host/runRound.ts +35 -43
- package/src/host/sceneAudio.ts +14 -0
- package/src/host/sceneController.ts +84 -19
- package/src/host/shellConfig.ts +53 -35
- package/src/host/skipGesture.ts +24 -0
- package/src/host/types.ts +5 -2
- package/src/loading/LoadingScene.ts +31 -174
- package/src/viewport/ViewportManager.ts +19 -9
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
import { Container, Graphics, Text, Sprite, Assets } from 'pixi.js';
|
|
2
1
|
import { Scene } from '../core/Scene';
|
|
3
|
-
import { Tween } from '../animation/Tween';
|
|
4
|
-
import { Easing } from '../animation/Easing';
|
|
5
2
|
import type { LoadingScreenConfig } from '../types';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
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
|
-
});
|
|
20
|
-
}
|
|
3
|
+
import {
|
|
4
|
+
setCSSPreloaderProgress,
|
|
5
|
+
waitCSSPreloaderTap,
|
|
6
|
+
removeCSSPreloader,
|
|
7
|
+
} from '@energy8platform/platform-core/loading';
|
|
21
8
|
|
|
22
9
|
interface LoadingSceneData {
|
|
23
10
|
engine: any; // GameApplication — avoid circular import
|
|
@@ -26,10 +13,14 @@ interface LoadingSceneData {
|
|
|
26
13
|
}
|
|
27
14
|
|
|
28
15
|
/**
|
|
29
|
-
* Built-in loading screen
|
|
16
|
+
* Built-in loading screen.
|
|
30
17
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
18
|
+
* It does NOT render its own overlay — the CSS preloader created at boot
|
|
19
|
+
* (`createPlatformSession`/`GameApplication.start`) stays on screen, and this
|
|
20
|
+
* scene merely drives it: asset-load progress → `setCSSPreloaderProgress`,
|
|
21
|
+
* tap-to-start → `waitCSSPreloaderTap`, then fades it out via
|
|
22
|
+
* `removeCSSPreloader` before entering the game. One continuous overlay from
|
|
23
|
+
* boot to gameplay — no second logo, no mid-load flash.
|
|
33
24
|
*/
|
|
34
25
|
export class LoadingScene extends Scene {
|
|
35
26
|
private _engine!: any;
|
|
@@ -37,12 +28,6 @@ export class LoadingScene extends Scene {
|
|
|
37
28
|
private _targetData?: unknown;
|
|
38
29
|
private _config!: LoadingScreenConfig;
|
|
39
30
|
|
|
40
|
-
// HTML overlay
|
|
41
|
-
private _overlay: HTMLDivElement | null = null;
|
|
42
|
-
private _loaderRect: SVGRectElement | null = null;
|
|
43
|
-
private _percentEl: Element | null = null;
|
|
44
|
-
private _tapToStartEl: Element | null = null;
|
|
45
|
-
|
|
46
31
|
// State
|
|
47
32
|
private _displayedProgress = 0;
|
|
48
33
|
private _targetProgress = 0;
|
|
@@ -57,9 +42,6 @@ export class LoadingScene extends Scene {
|
|
|
57
42
|
this._config = engine.config.loading ?? {};
|
|
58
43
|
this._startTime = Date.now();
|
|
59
44
|
|
|
60
|
-
// Create the HTML overlay with the SVG logo
|
|
61
|
-
this.createOverlay();
|
|
62
|
-
|
|
63
45
|
// Initialize asset manager
|
|
64
46
|
await this._engine.assets.init();
|
|
65
47
|
|
|
@@ -122,16 +104,14 @@ export class LoadingScene extends Scene {
|
|
|
122
104
|
this._displayedProgress = 1;
|
|
123
105
|
this.updateLoaderBar(1);
|
|
124
106
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
await this.transitionToGame();
|
|
130
|
-
}
|
|
107
|
+
// Wait for the player's tap — resolves immediately when tapToStart is
|
|
108
|
+
// false (the preloader honours that flag) — then enter the game.
|
|
109
|
+
await waitCSSPreloaderTap();
|
|
110
|
+
await this.transitionToGame();
|
|
131
111
|
}
|
|
132
112
|
|
|
133
113
|
override onUpdate(dt: number): void {
|
|
134
|
-
// Smooth progress bar fill
|
|
114
|
+
// Smooth progress bar fill (during active loading)
|
|
135
115
|
if (!this._loadingComplete && this._displayedProgress < this._targetProgress) {
|
|
136
116
|
this._displayedProgress = Math.min(
|
|
137
117
|
this._displayedProgress + dt * 1.5,
|
|
@@ -142,107 +122,19 @@ export class LoadingScene extends Scene {
|
|
|
142
122
|
}
|
|
143
123
|
|
|
144
124
|
override onResize(_width: number, _height: number): void {
|
|
145
|
-
//
|
|
125
|
+
// The preloader overlay is CSS-based and auto-resizes.
|
|
146
126
|
}
|
|
147
127
|
|
|
148
128
|
override onDestroy(): void {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// ─── HTML Overlay ──────────────────────────────────────
|
|
153
|
-
|
|
154
|
-
private createOverlay(): void {
|
|
155
|
-
const bgColor =
|
|
156
|
-
typeof this._config.backgroundColor === 'string'
|
|
157
|
-
? this._config.backgroundColor
|
|
158
|
-
: typeof this._config.backgroundColor === 'number'
|
|
159
|
-
? `#${this._config.backgroundColor.toString(16).padStart(6, '0')}`
|
|
160
|
-
: '#0a0a1a';
|
|
161
|
-
|
|
162
|
-
const bgGradient =
|
|
163
|
-
this._config.backgroundGradient ??
|
|
164
|
-
`linear-gradient(135deg, ${bgColor} 0%, #1a1a3e 100%)`;
|
|
165
|
-
|
|
166
|
-
this._overlay = document.createElement('div');
|
|
167
|
-
this._overlay.id = '__ge-loading-overlay__';
|
|
168
|
-
this._overlay.innerHTML = `
|
|
169
|
-
<div class="ge-loading-content">
|
|
170
|
-
${buildLoadingLogoSVG()}
|
|
171
|
-
</div>
|
|
172
|
-
`;
|
|
173
|
-
|
|
174
|
-
const style = document.createElement('style');
|
|
175
|
-
style.id = '__ge-loading-style__';
|
|
176
|
-
style.textContent = `
|
|
177
|
-
#__ge-loading-overlay__ {
|
|
178
|
-
position: absolute;
|
|
179
|
-
top: 0; left: 0;
|
|
180
|
-
width: 100%; height: 100%;
|
|
181
|
-
background: ${bgGradient};
|
|
182
|
-
display: flex;
|
|
183
|
-
align-items: center;
|
|
184
|
-
justify-content: center;
|
|
185
|
-
z-index: 9999;
|
|
186
|
-
transition: opacity 0.5s ease-out;
|
|
187
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
188
|
-
}
|
|
189
|
-
#__ge-loading-overlay__.ge-fade-out {
|
|
190
|
-
opacity: 0;
|
|
191
|
-
pointer-events: none;
|
|
192
|
-
}
|
|
193
|
-
.ge-loading-content {
|
|
194
|
-
display: flex;
|
|
195
|
-
flex-direction: column;
|
|
196
|
-
align-items: center;
|
|
197
|
-
width: 75%;
|
|
198
|
-
max-width: 650px;
|
|
199
|
-
}
|
|
200
|
-
.ge-loading-content svg {
|
|
201
|
-
filter: drop-shadow(0 0 40px rgba(121, 57, 194, 0.5));
|
|
202
|
-
cursor: default;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.ge-svg-pulse {
|
|
206
|
-
animation: ge-tap-pulse 1.2s ease-in-out infinite;
|
|
207
|
-
}
|
|
208
|
-
@keyframes ge-tap-pulse {
|
|
209
|
-
0%, 100% { opacity: 0.5; }
|
|
210
|
-
50% { opacity: 1; }
|
|
211
|
-
}
|
|
212
|
-
`;
|
|
213
|
-
|
|
214
|
-
// Get the container that holds the canvas
|
|
215
|
-
const container = this._engine.app?.canvas?.parentElement;
|
|
216
|
-
if (container) {
|
|
217
|
-
container.style.position = container.style.position || 'relative';
|
|
218
|
-
container.appendChild(style);
|
|
219
|
-
container.appendChild(this._overlay);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Cache the SVG loader rect for progress updates
|
|
223
|
-
this._loaderRect = this._overlay.querySelector('#ge-loader-rect');
|
|
224
|
-
this._percentEl = this._overlay.querySelector('#ge-loader-pct');
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
private removeOverlay(): void {
|
|
228
|
-
this._overlay?.remove();
|
|
229
|
-
document.getElementById('__ge-loading-style__')?.remove();
|
|
230
|
-
this._overlay = null;
|
|
231
|
-
this._loaderRect = null;
|
|
232
|
-
this._percentEl = null;
|
|
233
|
-
this._tapToStartEl = null;
|
|
129
|
+
// Defensive: ensure the preloader is gone even if we never transitioned
|
|
130
|
+
// (e.g. the scene was popped externally). Idempotent.
|
|
131
|
+
void removeCSSPreloader(this.hostElement());
|
|
234
132
|
}
|
|
235
133
|
|
|
236
134
|
// ─── Progress ──────────────────────────────────────────
|
|
237
135
|
|
|
238
136
|
private updateLoaderBar(progress: number): void {
|
|
239
|
-
|
|
240
|
-
this._loaderRect.setAttribute('width', String(LOADER_BAR_MAX_WIDTH * progress));
|
|
241
|
-
}
|
|
242
|
-
if (this._percentEl) {
|
|
243
|
-
const pct = Math.round(progress * 100);
|
|
244
|
-
(this._percentEl as SVGTextElement).textContent = `${pct}%`;
|
|
245
|
-
}
|
|
137
|
+
setCSSPreloaderProgress(Math.max(0, Math.min(1, progress)));
|
|
246
138
|
}
|
|
247
139
|
|
|
248
140
|
/**
|
|
@@ -275,58 +167,23 @@ export class LoadingScene extends Scene {
|
|
|
275
167
|
});
|
|
276
168
|
}
|
|
277
169
|
|
|
278
|
-
// ───
|
|
279
|
-
|
|
280
|
-
private async showTapToStart(): Promise<void> {
|
|
281
|
-
const tapText = this._config.tapToStartText ?? 'TAP TO START';
|
|
282
|
-
|
|
283
|
-
// Reuse the same SVG text element — replace percentage with tap text
|
|
284
|
-
if (this._percentEl) {
|
|
285
|
-
const el = this._percentEl as SVGTextElement;
|
|
286
|
-
el.textContent = tapText;
|
|
287
|
-
el.setAttribute('fill', '#ffffff');
|
|
288
|
-
el.classList.add('ge-svg-pulse');
|
|
289
|
-
this._tapToStartEl = el;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Make overlay clickable
|
|
293
|
-
if (this._overlay) {
|
|
294
|
-
this._overlay.style.cursor = 'pointer';
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Wait for tap
|
|
298
|
-
return new Promise<void>((resolve) => {
|
|
299
|
-
const handler = async () => {
|
|
300
|
-
this._overlay?.removeEventListener('click', handler);
|
|
301
|
-
await this.transitionToGame();
|
|
302
|
-
resolve();
|
|
303
|
-
};
|
|
170
|
+
// ─── Transition ────────────────────────────────────────
|
|
304
171
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
172
|
+
/** The DOM element hosting the canvas + preloader overlay. */
|
|
173
|
+
private hostElement(): HTMLElement {
|
|
174
|
+
return this._engine?.app?.canvas?.parentElement ?? document.body;
|
|
308
175
|
}
|
|
309
176
|
|
|
310
|
-
// ─── Transition ────────────────────────────────────────
|
|
311
|
-
|
|
312
177
|
private async transitionToGame(): Promise<void> {
|
|
313
|
-
// Fade out the
|
|
314
|
-
|
|
315
|
-
this._overlay.classList.add('ge-fade-out');
|
|
316
|
-
await new Promise<void>((resolve) => {
|
|
317
|
-
this._overlay!.addEventListener('transitionend', () => resolve(), { once: true });
|
|
318
|
-
// Safety timeout
|
|
319
|
-
setTimeout(resolve, 600);
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Remove overlay
|
|
324
|
-
this.removeOverlay();
|
|
178
|
+
// Fade out and remove the shared CSS preloader (resolves after the fade).
|
|
179
|
+
await removeCSSPreloader(this.hostElement());
|
|
325
180
|
|
|
326
181
|
// Navigate to the target scene, always passing the engine reference
|
|
327
182
|
await this._engine.scenes.goto(this._targetScene, {
|
|
328
183
|
engine: this._engine,
|
|
329
|
-
...(this._targetData && typeof this._targetData === 'object'
|
|
184
|
+
...(this._targetData && typeof this._targetData === 'object'
|
|
185
|
+
? (this._targetData as Record<string, unknown>)
|
|
186
|
+
: { data: this._targetData }),
|
|
330
187
|
});
|
|
331
188
|
}
|
|
332
189
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Application } from 'pixi.js';
|
|
1
|
+
import type { Application, Container } from 'pixi.js';
|
|
2
2
|
import { EventEmitter } from '../core/EventEmitter';
|
|
3
3
|
import { ScaleMode, Orientation } from '../types';
|
|
4
4
|
|
|
@@ -45,6 +45,7 @@ export class ViewportManager extends EventEmitter<ViewportEvents> {
|
|
|
45
45
|
private _app: Application;
|
|
46
46
|
private _container: HTMLElement;
|
|
47
47
|
private _config: ViewportConfig;
|
|
48
|
+
private _target: Container;
|
|
48
49
|
private _resizeObserver: ResizeObserver | null = null;
|
|
49
50
|
private _currentOrientation: Orientation = Orientation.LANDSCAPE;
|
|
50
51
|
private _currentWidth = 0;
|
|
@@ -53,11 +54,20 @@ export class ViewportManager extends EventEmitter<ViewportEvents> {
|
|
|
53
54
|
private _destroyed = false;
|
|
54
55
|
private _resizeTimeout: number | null = null;
|
|
55
56
|
|
|
56
|
-
constructor(
|
|
57
|
+
constructor(
|
|
58
|
+
app: Application,
|
|
59
|
+
container: HTMLElement,
|
|
60
|
+
config: ViewportConfig,
|
|
61
|
+
target?: Container,
|
|
62
|
+
) {
|
|
57
63
|
super();
|
|
58
64
|
this._app = app;
|
|
59
65
|
this._container = container;
|
|
60
66
|
this._config = config;
|
|
67
|
+
// The container this manager scales/offsets. Defaults to app.stage for backward
|
|
68
|
+
// compatibility; the engine passes a dedicated scaled world root so app.stage stays
|
|
69
|
+
// identity (screen space) for unscaled UI layers.
|
|
70
|
+
this._target = target ?? app.stage;
|
|
61
71
|
|
|
62
72
|
this.setupObserver();
|
|
63
73
|
}
|
|
@@ -164,18 +174,18 @@ export class ViewportManager extends EventEmitter<ViewportEvents> {
|
|
|
164
174
|
? Math.min(containerWidth / designWidth, containerHeight / designHeight)
|
|
165
175
|
: scale;
|
|
166
176
|
|
|
167
|
-
this.
|
|
177
|
+
this._target.scale.set(stageScale);
|
|
168
178
|
|
|
169
179
|
// Center the stage for FIT mode
|
|
170
180
|
if (scaleMode === ScaleMode.FIT) {
|
|
171
|
-
this.
|
|
172
|
-
this.
|
|
181
|
+
this._target.x = Math.round((containerWidth - designWidth * stageScale) / 2);
|
|
182
|
+
this._target.y = Math.round((containerHeight - designHeight * stageScale) / 2);
|
|
173
183
|
} else if (scaleMode === ScaleMode.FILL) {
|
|
174
|
-
this.
|
|
175
|
-
this.
|
|
184
|
+
this._target.x = Math.round((containerWidth - gameWidth * stageScale) / 2);
|
|
185
|
+
this._target.y = Math.round((containerHeight - gameHeight * stageScale) / 2);
|
|
176
186
|
} else {
|
|
177
|
-
this.
|
|
178
|
-
this.
|
|
187
|
+
this._target.x = 0;
|
|
188
|
+
this._target.y = 0;
|
|
179
189
|
}
|
|
180
190
|
|
|
181
191
|
this._currentWidth = gameWidth;
|