@energy8platform/game-engine 0.1.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 +1134 -0
- package/dist/animation.cjs.js +505 -0
- package/dist/animation.cjs.js.map +1 -0
- package/dist/animation.d.ts +235 -0
- package/dist/animation.esm.js +500 -0
- package/dist/animation.esm.js.map +1 -0
- package/dist/assets.cjs.js +148 -0
- package/dist/assets.cjs.js.map +1 -0
- package/dist/assets.d.ts +97 -0
- package/dist/assets.esm.js +146 -0
- package/dist/assets.esm.js.map +1 -0
- package/dist/audio.cjs.js +345 -0
- package/dist/audio.cjs.js.map +1 -0
- package/dist/audio.d.ts +135 -0
- package/dist/audio.esm.js +343 -0
- package/dist/audio.esm.js.map +1 -0
- package/dist/core.cjs.js +1832 -0
- package/dist/core.cjs.js.map +1 -0
- package/dist/core.d.ts +633 -0
- package/dist/core.esm.js +1827 -0
- package/dist/core.esm.js.map +1 -0
- package/dist/debug.cjs.js +298 -0
- package/dist/debug.cjs.js.map +1 -0
- package/dist/debug.d.ts +114 -0
- package/dist/debug.esm.js +295 -0
- package/dist/debug.esm.js.map +1 -0
- package/dist/index.cjs.js +3623 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +1607 -0
- package/dist/index.esm.js +3598 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/ui.cjs.js +1081 -0
- package/dist/ui.cjs.js.map +1 -0
- package/dist/ui.d.ts +387 -0
- package/dist/ui.esm.js +1072 -0
- package/dist/ui.esm.js.map +1 -0
- package/dist/vite.cjs.js +125 -0
- package/dist/vite.cjs.js.map +1 -0
- package/dist/vite.d.ts +42 -0
- package/dist/vite.esm.js +122 -0
- package/dist/vite.esm.js.map +1 -0
- package/package.json +107 -0
- package/src/animation/Easing.ts +116 -0
- package/src/animation/SpineHelper.ts +162 -0
- package/src/animation/Timeline.ts +138 -0
- package/src/animation/Tween.ts +225 -0
- package/src/animation/index.ts +4 -0
- package/src/assets/AssetManager.ts +174 -0
- package/src/assets/index.ts +2 -0
- package/src/audio/AudioManager.ts +366 -0
- package/src/audio/index.ts +1 -0
- package/src/core/EventEmitter.ts +47 -0
- package/src/core/GameApplication.ts +306 -0
- package/src/core/Scene.ts +48 -0
- package/src/core/SceneManager.ts +258 -0
- package/src/core/index.ts +4 -0
- package/src/debug/DevBridge.ts +259 -0
- package/src/debug/FPSOverlay.ts +102 -0
- package/src/debug/index.ts +3 -0
- package/src/index.ts +71 -0
- package/src/input/InputManager.ts +171 -0
- package/src/input/index.ts +1 -0
- package/src/loading/CSSPreloader.ts +155 -0
- package/src/loading/LoadingScene.ts +356 -0
- package/src/loading/index.ts +2 -0
- package/src/state/StateMachine.ts +228 -0
- package/src/state/index.ts +1 -0
- package/src/types.ts +218 -0
- package/src/ui/BalanceDisplay.ts +155 -0
- package/src/ui/Button.ts +199 -0
- package/src/ui/Label.ts +111 -0
- package/src/ui/Modal.ts +134 -0
- package/src/ui/Panel.ts +125 -0
- package/src/ui/ProgressBar.ts +121 -0
- package/src/ui/Toast.ts +124 -0
- package/src/ui/WinDisplay.ts +133 -0
- package/src/ui/index.ts +16 -0
- package/src/viewport/ViewportManager.ts +241 -0
- package/src/viewport/index.ts +1 -0
- package/src/vite/index.ts +153 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { LoadingScreenConfig } from '../types';
|
|
2
|
+
|
|
3
|
+
const PRELOADER_ID = '__ge-css-preloader__';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Inline SVG logo with animated loader bar.
|
|
7
|
+
* The `#loader` path acts as the progress fill — animated via clipPath.
|
|
8
|
+
*/
|
|
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>`;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates a lightweight CSS-only preloader that appears instantly,
|
|
45
|
+
* BEFORE PixiJS/WebGL is initialized.
|
|
46
|
+
*
|
|
47
|
+
* Displays the Energy8 logo SVG with an animated loader bar.
|
|
48
|
+
*/
|
|
49
|
+
export function createCSSPreloader(
|
|
50
|
+
container: HTMLElement,
|
|
51
|
+
config?: LoadingScreenConfig,
|
|
52
|
+
): void {
|
|
53
|
+
if (document.getElementById(PRELOADER_ID)) return;
|
|
54
|
+
|
|
55
|
+
const bgColor =
|
|
56
|
+
typeof config?.backgroundColor === 'string'
|
|
57
|
+
? config.backgroundColor
|
|
58
|
+
: typeof config?.backgroundColor === 'number'
|
|
59
|
+
? `#${config.backgroundColor.toString(16).padStart(6, '0')}`
|
|
60
|
+
: '#0a0a1a';
|
|
61
|
+
|
|
62
|
+
const bgGradient = config?.backgroundGradient ?? `linear-gradient(135deg, ${bgColor} 0%, #1a1a3e 100%)`;
|
|
63
|
+
|
|
64
|
+
const customHTML = config?.cssPreloaderHTML ?? '';
|
|
65
|
+
|
|
66
|
+
const el = document.createElement('div');
|
|
67
|
+
el.id = PRELOADER_ID;
|
|
68
|
+
el.innerHTML = customHTML || `
|
|
69
|
+
<div class="ge-preloader-content">
|
|
70
|
+
${LOGO_SVG}
|
|
71
|
+
</div>
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
const style = document.createElement('style');
|
|
75
|
+
style.textContent = `
|
|
76
|
+
#${PRELOADER_ID} {
|
|
77
|
+
position: absolute;
|
|
78
|
+
top: 0; left: 0;
|
|
79
|
+
width: 100%; height: 100%;
|
|
80
|
+
background: ${bgGradient};
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
z-index: 10000;
|
|
85
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
86
|
+
transition: opacity 0.4s ease-out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#${PRELOADER_ID}.ge-preloader-hidden {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
pointer-events: none;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.ge-preloader-content {
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
align-items: center;
|
|
98
|
+
width: 80%;
|
|
99
|
+
max-width: 700px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.ge-logo-svg {
|
|
103
|
+
width: 100%;
|
|
104
|
+
height: auto;
|
|
105
|
+
filter: drop-shadow(0 0 30px rgba(121, 57, 194, 0.4));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Animate the loader clip-rect to shimmer while waiting */
|
|
109
|
+
.ge-clip-rect {
|
|
110
|
+
animation: ge-loader-fill 2s ease-in-out infinite;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@keyframes ge-loader-fill {
|
|
114
|
+
0% { width: 0; }
|
|
115
|
+
50% { width: 174; }
|
|
116
|
+
100% { width: 0; }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Animate the SVG text opacity */
|
|
120
|
+
.ge-preloader-svg-text {
|
|
121
|
+
animation: ge-pulse 1.5s ease-in-out infinite;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@keyframes ge-pulse {
|
|
125
|
+
0%, 100% { opacity: 0.4; }
|
|
126
|
+
50% { opacity: 1; }
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
container.style.position = container.style.position || 'relative';
|
|
131
|
+
container.appendChild(style);
|
|
132
|
+
container.appendChild(el);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Remove the CSS preloader with a smooth fade-out transition.
|
|
137
|
+
*/
|
|
138
|
+
export function removeCSSPreloader(container: HTMLElement): void {
|
|
139
|
+
const el = document.getElementById(PRELOADER_ID);
|
|
140
|
+
if (!el) return;
|
|
141
|
+
|
|
142
|
+
el.classList.add('ge-preloader-hidden');
|
|
143
|
+
|
|
144
|
+
// Remove after transition
|
|
145
|
+
el.addEventListener('transitionend', () => {
|
|
146
|
+
el.remove();
|
|
147
|
+
// Also remove the style element
|
|
148
|
+
const styles = container.querySelectorAll('style');
|
|
149
|
+
for (const style of styles) {
|
|
150
|
+
if (style.textContent?.includes(PRELOADER_ID)) {
|
|
151
|
+
style.remove();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { Container, Graphics, Text, Sprite, Assets } from 'pixi.js';
|
|
2
|
+
import { Scene } from '../core/Scene';
|
|
3
|
+
import { Tween } from '../animation/Tween';
|
|
4
|
+
import { Easing } from '../animation/Easing';
|
|
5
|
+
import type { LoadingScreenConfig } from '../types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
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.
|
|
10
|
+
*/
|
|
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>`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Max width of the loader bar in SVG units */
|
|
47
|
+
const LOADER_BAR_MAX_WIDTH = 174;
|
|
48
|
+
|
|
49
|
+
interface LoadingSceneData {
|
|
50
|
+
engine: any; // GameApplication — avoid circular import
|
|
51
|
+
targetScene: string;
|
|
52
|
+
targetData?: unknown;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Built-in loading screen using the Energy8 SVG logo with animated loader bar.
|
|
57
|
+
*
|
|
58
|
+
* Renders as an HTML overlay on top of the canvas for crisp SVG quality.
|
|
59
|
+
* The loader bar fill width is driven by asset loading progress.
|
|
60
|
+
*/
|
|
61
|
+
export class LoadingScene extends Scene {
|
|
62
|
+
private _engine!: any;
|
|
63
|
+
private _targetScene!: string;
|
|
64
|
+
private _targetData?: unknown;
|
|
65
|
+
private _config!: LoadingScreenConfig;
|
|
66
|
+
|
|
67
|
+
// HTML overlay
|
|
68
|
+
private _overlay: HTMLDivElement | null = null;
|
|
69
|
+
private _loaderRect: SVGRectElement | null = null;
|
|
70
|
+
private _percentEl: Element | null = null;
|
|
71
|
+
private _tapToStartEl: Element | null = null;
|
|
72
|
+
|
|
73
|
+
// State
|
|
74
|
+
private _displayedProgress = 0;
|
|
75
|
+
private _targetProgress = 0;
|
|
76
|
+
private _loadingComplete = false;
|
|
77
|
+
private _startTime = 0;
|
|
78
|
+
|
|
79
|
+
override async onEnter(data?: unknown): Promise<void> {
|
|
80
|
+
const { engine, targetScene, targetData } = data as LoadingSceneData;
|
|
81
|
+
this._engine = engine;
|
|
82
|
+
this._targetScene = targetScene;
|
|
83
|
+
this._targetData = targetData;
|
|
84
|
+
this._config = engine.config.loading ?? {};
|
|
85
|
+
this._startTime = Date.now();
|
|
86
|
+
|
|
87
|
+
// Create the HTML overlay with the SVG logo
|
|
88
|
+
this.createOverlay();
|
|
89
|
+
|
|
90
|
+
// Initialize asset manager
|
|
91
|
+
await this._engine.assets.init();
|
|
92
|
+
|
|
93
|
+
// Initialize audio manager
|
|
94
|
+
await this._engine.audio.init();
|
|
95
|
+
|
|
96
|
+
// Phase 1: Load preload bundle
|
|
97
|
+
const bundles = this._engine.assets.getBundleNames();
|
|
98
|
+
const hasPreload = bundles.includes('preload');
|
|
99
|
+
|
|
100
|
+
if (hasPreload) {
|
|
101
|
+
const preloadAssets = this._engine.config.manifest?.bundles?.find(
|
|
102
|
+
(b: any) => b.name === 'preload',
|
|
103
|
+
)?.assets;
|
|
104
|
+
|
|
105
|
+
if (preloadAssets && preloadAssets.length > 0) {
|
|
106
|
+
await this._engine.assets.loadBundle('preload', (p: number) => {
|
|
107
|
+
this._targetProgress = p * 0.15;
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
this._targetProgress = 0.15;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Phase 2: Load remaining bundles
|
|
115
|
+
const remainingBundles = bundles.filter(
|
|
116
|
+
(b: string) => b !== 'preload' && !this._engine.assets.isBundleLoaded(b),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (remainingBundles.length > 0) {
|
|
120
|
+
const hasAssets = remainingBundles.some((name: string) => {
|
|
121
|
+
const bundle = this._engine.config.manifest?.bundles?.find(
|
|
122
|
+
(b: any) => b.name === name,
|
|
123
|
+
);
|
|
124
|
+
return bundle?.assets && bundle.assets.length > 0;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (hasAssets) {
|
|
128
|
+
await this._engine.assets.loadBundles(remainingBundles, (p: number) => {
|
|
129
|
+
this._targetProgress = 0.15 + p * 0.85;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this._targetProgress = 1;
|
|
135
|
+
this._loadingComplete = true;
|
|
136
|
+
|
|
137
|
+
// Enforce minimum display time: spread the remaining progress fill
|
|
138
|
+
// over the remaining time so the bar fills smoothly, not abruptly
|
|
139
|
+
const minTime = this._config.minDisplayTime ?? 1500;
|
|
140
|
+
const elapsed = Date.now() - this._startTime;
|
|
141
|
+
const remaining = Math.max(0, minTime - elapsed);
|
|
142
|
+
|
|
143
|
+
if (remaining > 0) {
|
|
144
|
+
// Distribute fill animation over the remaining time
|
|
145
|
+
await this.animateProgressTo(1, remaining);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Final snap to 100%
|
|
149
|
+
this._displayedProgress = 1;
|
|
150
|
+
this.updateLoaderBar(1);
|
|
151
|
+
|
|
152
|
+
// Show "Tap to Start" or transition directly
|
|
153
|
+
if (this._config.tapToStart !== false) {
|
|
154
|
+
await this.showTapToStart();
|
|
155
|
+
} else {
|
|
156
|
+
await this.transitionToGame();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
override onUpdate(dt: number): void {
|
|
161
|
+
// Smooth progress bar fill via HTML (during active loading)
|
|
162
|
+
if (!this._loadingComplete && this._displayedProgress < this._targetProgress) {
|
|
163
|
+
this._displayedProgress = Math.min(
|
|
164
|
+
this._displayedProgress + dt * 1.5,
|
|
165
|
+
this._targetProgress,
|
|
166
|
+
);
|
|
167
|
+
this.updateLoaderBar(this._displayedProgress);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
override onResize(_width: number, _height: number): void {
|
|
172
|
+
// Overlay is CSS-based, auto-resizes
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
override onDestroy(): void {
|
|
176
|
+
this.removeOverlay();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─── HTML Overlay ──────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
private createOverlay(): void {
|
|
182
|
+
const bgColor =
|
|
183
|
+
typeof this._config.backgroundColor === 'string'
|
|
184
|
+
? this._config.backgroundColor
|
|
185
|
+
: typeof this._config.backgroundColor === 'number'
|
|
186
|
+
? `#${this._config.backgroundColor.toString(16).padStart(6, '0')}`
|
|
187
|
+
: '#0a0a1a';
|
|
188
|
+
|
|
189
|
+
const bgGradient =
|
|
190
|
+
this._config.backgroundGradient ??
|
|
191
|
+
`linear-gradient(135deg, ${bgColor} 0%, #1a1a3e 100%)`;
|
|
192
|
+
|
|
193
|
+
this._overlay = document.createElement('div');
|
|
194
|
+
this._overlay.id = '__ge-loading-overlay__';
|
|
195
|
+
this._overlay.innerHTML = `
|
|
196
|
+
<div class="ge-loading-content">
|
|
197
|
+
${buildLogoSVG()}
|
|
198
|
+
</div>
|
|
199
|
+
`;
|
|
200
|
+
|
|
201
|
+
const style = document.createElement('style');
|
|
202
|
+
style.id = '__ge-loading-style__';
|
|
203
|
+
style.textContent = `
|
|
204
|
+
#__ge-loading-overlay__ {
|
|
205
|
+
position: absolute;
|
|
206
|
+
top: 0; left: 0;
|
|
207
|
+
width: 100%; height: 100%;
|
|
208
|
+
background: ${bgGradient};
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
z-index: 9999;
|
|
213
|
+
transition: opacity 0.5s ease-out;
|
|
214
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
215
|
+
}
|
|
216
|
+
#__ge-loading-overlay__.ge-fade-out {
|
|
217
|
+
opacity: 0;
|
|
218
|
+
pointer-events: none;
|
|
219
|
+
}
|
|
220
|
+
.ge-loading-content {
|
|
221
|
+
display: flex;
|
|
222
|
+
flex-direction: column;
|
|
223
|
+
align-items: center;
|
|
224
|
+
width: 75%;
|
|
225
|
+
max-width: 650px;
|
|
226
|
+
}
|
|
227
|
+
.ge-loading-content svg {
|
|
228
|
+
filter: drop-shadow(0 0 40px rgba(121, 57, 194, 0.5));
|
|
229
|
+
cursor: default;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.ge-svg-pulse {
|
|
233
|
+
animation: ge-tap-pulse 1.2s ease-in-out infinite;
|
|
234
|
+
}
|
|
235
|
+
@keyframes ge-tap-pulse {
|
|
236
|
+
0%, 100% { opacity: 0.5; }
|
|
237
|
+
50% { opacity: 1; }
|
|
238
|
+
}
|
|
239
|
+
`;
|
|
240
|
+
|
|
241
|
+
// Get the container that holds the canvas
|
|
242
|
+
const container = this._engine.app?.canvas?.parentElement;
|
|
243
|
+
if (container) {
|
|
244
|
+
container.style.position = container.style.position || 'relative';
|
|
245
|
+
container.appendChild(style);
|
|
246
|
+
container.appendChild(this._overlay);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Cache the SVG loader rect for progress updates
|
|
250
|
+
this._loaderRect = this._overlay.querySelector('#ge-loader-rect');
|
|
251
|
+
this._percentEl = this._overlay.querySelector('#ge-loader-pct');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private removeOverlay(): void {
|
|
255
|
+
this._overlay?.remove();
|
|
256
|
+
document.getElementById('__ge-loading-style__')?.remove();
|
|
257
|
+
this._overlay = null;
|
|
258
|
+
this._loaderRect = null;
|
|
259
|
+
this._percentEl = null;
|
|
260
|
+
this._tapToStartEl = null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ─── Progress ──────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
private updateLoaderBar(progress: number): void {
|
|
266
|
+
if (this._loaderRect) {
|
|
267
|
+
this._loaderRect.setAttribute('width', String(LOADER_BAR_MAX_WIDTH * progress));
|
|
268
|
+
}
|
|
269
|
+
if (this._percentEl) {
|
|
270
|
+
const pct = Math.round(progress * 100);
|
|
271
|
+
(this._percentEl as SVGTextElement).textContent = `${pct}%`;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Smoothly animate the displayed progress from its current value to `target`
|
|
277
|
+
* over `durationMs` using an easeOutCubic curve.
|
|
278
|
+
*/
|
|
279
|
+
private async animateProgressTo(target: number, durationMs: number): Promise<void> {
|
|
280
|
+
const startVal = this._displayedProgress;
|
|
281
|
+
const delta = target - startVal;
|
|
282
|
+
if (delta <= 0 || durationMs <= 0) return;
|
|
283
|
+
|
|
284
|
+
const startTime = Date.now();
|
|
285
|
+
|
|
286
|
+
return new Promise<void>((resolve) => {
|
|
287
|
+
const tick = () => {
|
|
288
|
+
const elapsed = Date.now() - startTime;
|
|
289
|
+
const t = Math.min(elapsed / durationMs, 1);
|
|
290
|
+
// easeOutCubic for a natural deceleration feel
|
|
291
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
292
|
+
this._displayedProgress = startVal + delta * eased;
|
|
293
|
+
this.updateLoaderBar(this._displayedProgress);
|
|
294
|
+
|
|
295
|
+
if (t < 1) {
|
|
296
|
+
requestAnimationFrame(tick);
|
|
297
|
+
} else {
|
|
298
|
+
resolve();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
requestAnimationFrame(tick);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ─── Tap to Start ─────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
private async showTapToStart(): Promise<void> {
|
|
308
|
+
const tapText = this._config.tapToStartText ?? 'TAP TO START';
|
|
309
|
+
|
|
310
|
+
// Reuse the same SVG text element — replace percentage with tap text
|
|
311
|
+
if (this._percentEl) {
|
|
312
|
+
const el = this._percentEl as SVGTextElement;
|
|
313
|
+
el.textContent = tapText;
|
|
314
|
+
el.setAttribute('fill', '#ffffff');
|
|
315
|
+
el.classList.add('ge-svg-pulse');
|
|
316
|
+
this._tapToStartEl = el;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Make overlay clickable
|
|
320
|
+
if (this._overlay) {
|
|
321
|
+
this._overlay.style.cursor = 'pointer';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Wait for tap
|
|
325
|
+
return new Promise<void>((resolve) => {
|
|
326
|
+
const handler = async () => {
|
|
327
|
+
this._overlay?.removeEventListener('click', handler);
|
|
328
|
+
await this.transitionToGame();
|
|
329
|
+
resolve();
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// Listen on the full overlay for easier mobile tap
|
|
333
|
+
this._overlay?.addEventListener('click', handler);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ─── Transition ────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
private async transitionToGame(): Promise<void> {
|
|
340
|
+
// Fade out the HTML overlay
|
|
341
|
+
if (this._overlay) {
|
|
342
|
+
this._overlay.classList.add('ge-fade-out');
|
|
343
|
+
await new Promise<void>((resolve) => {
|
|
344
|
+
this._overlay!.addEventListener('transitionend', () => resolve(), { once: true });
|
|
345
|
+
// Safety timeout
|
|
346
|
+
setTimeout(resolve, 600);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Remove overlay
|
|
351
|
+
this.removeOverlay();
|
|
352
|
+
|
|
353
|
+
// Navigate to the target scene
|
|
354
|
+
await this._engine.scenes.goto(this._targetScene, this._targetData);
|
|
355
|
+
}
|
|
356
|
+
}
|