@energy8platform/game-engine 0.19.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/core.cjs.js +33 -156
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.esm.js +34 -157
- package/dist/core.esm.js.map +1 -1
- package/dist/host.cjs.js +70 -186
- package/dist/host.cjs.js.map +1 -1
- package/dist/host.d.ts +6 -0
- package/dist/host.esm.js +72 -188
- package/dist/host.esm.js.map +1 -1
- package/dist/index.cjs.js +33 -156
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +10 -11
- package/dist/index.esm.js +34 -157
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/core/GameApplication.ts +6 -5
- package/src/host/shellConfig.ts +44 -31
- package/src/loading/LoadingScene.ts +31 -174
package/dist/index.cjs.js
CHANGED
|
@@ -1518,34 +1518,20 @@ class Scene {
|
|
|
1518
1518
|
}
|
|
1519
1519
|
|
|
1520
1520
|
/**
|
|
1521
|
-
*
|
|
1522
|
-
* Uses unique IDs (prefixed with 'ls') to avoid collisions with CSSPreloader.
|
|
1523
|
-
*/
|
|
1524
|
-
function buildLoadingLogoSVG() {
|
|
1525
|
-
return loading.buildLogoSVG({
|
|
1526
|
-
idPrefix: 'ls',
|
|
1527
|
-
svgStyle: 'width:100%;height:auto;',
|
|
1528
|
-
clipRectId: 'ge-loader-rect',
|
|
1529
|
-
textId: 'ge-loader-pct',
|
|
1530
|
-
textContent: '0%',
|
|
1531
|
-
});
|
|
1532
|
-
}
|
|
1533
|
-
/**
|
|
1534
|
-
* Built-in loading screen using the Energy8 SVG logo with animated loader bar.
|
|
1521
|
+
* Built-in loading screen.
|
|
1535
1522
|
*
|
|
1536
|
-
*
|
|
1537
|
-
*
|
|
1523
|
+
* It does NOT render its own overlay — the CSS preloader created at boot
|
|
1524
|
+
* (`createPlatformSession`/`GameApplication.start`) stays on screen, and this
|
|
1525
|
+
* scene merely drives it: asset-load progress → `setCSSPreloaderProgress`,
|
|
1526
|
+
* tap-to-start → `waitCSSPreloaderTap`, then fades it out via
|
|
1527
|
+
* `removeCSSPreloader` before entering the game. One continuous overlay from
|
|
1528
|
+
* boot to gameplay — no second logo, no mid-load flash.
|
|
1538
1529
|
*/
|
|
1539
1530
|
class LoadingScene extends Scene {
|
|
1540
1531
|
_engine;
|
|
1541
1532
|
_targetScene;
|
|
1542
1533
|
_targetData;
|
|
1543
1534
|
_config;
|
|
1544
|
-
// HTML overlay
|
|
1545
|
-
_overlay = null;
|
|
1546
|
-
_loaderRect = null;
|
|
1547
|
-
_percentEl = null;
|
|
1548
|
-
_tapToStartEl = null;
|
|
1549
1535
|
// State
|
|
1550
1536
|
_displayedProgress = 0;
|
|
1551
1537
|
_targetProgress = 0;
|
|
@@ -1558,8 +1544,6 @@ class LoadingScene extends Scene {
|
|
|
1558
1544
|
this._targetData = targetData;
|
|
1559
1545
|
this._config = engine.config.loading ?? {};
|
|
1560
1546
|
this._startTime = Date.now();
|
|
1561
|
-
// Create the HTML overlay with the SVG logo
|
|
1562
|
-
this.createOverlay();
|
|
1563
1547
|
// Initialize asset manager
|
|
1564
1548
|
await this._engine.assets.init();
|
|
1565
1549
|
// Initialize audio manager
|
|
@@ -1605,110 +1589,29 @@ class LoadingScene extends Scene {
|
|
|
1605
1589
|
// Final snap to 100%
|
|
1606
1590
|
this._displayedProgress = 1;
|
|
1607
1591
|
this.updateLoaderBar(1);
|
|
1608
|
-
//
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
else {
|
|
1613
|
-
await this.transitionToGame();
|
|
1614
|
-
}
|
|
1592
|
+
// Wait for the player's tap — resolves immediately when tapToStart is
|
|
1593
|
+
// false (the preloader honours that flag) — then enter the game.
|
|
1594
|
+
await loading.waitCSSPreloaderTap();
|
|
1595
|
+
await this.transitionToGame();
|
|
1615
1596
|
}
|
|
1616
1597
|
onUpdate(dt) {
|
|
1617
|
-
// Smooth progress bar fill
|
|
1598
|
+
// Smooth progress bar fill (during active loading)
|
|
1618
1599
|
if (!this._loadingComplete && this._displayedProgress < this._targetProgress) {
|
|
1619
1600
|
this._displayedProgress = Math.min(this._displayedProgress + dt * 1.5, this._targetProgress);
|
|
1620
1601
|
this.updateLoaderBar(this._displayedProgress);
|
|
1621
1602
|
}
|
|
1622
1603
|
}
|
|
1623
1604
|
onResize(_width, _height) {
|
|
1624
|
-
//
|
|
1605
|
+
// The preloader overlay is CSS-based and auto-resizes.
|
|
1625
1606
|
}
|
|
1626
1607
|
onDestroy() {
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
createOverlay() {
|
|
1631
|
-
const bgColor = typeof this._config.backgroundColor === 'string'
|
|
1632
|
-
? this._config.backgroundColor
|
|
1633
|
-
: typeof this._config.backgroundColor === 'number'
|
|
1634
|
-
? `#${this._config.backgroundColor.toString(16).padStart(6, '0')}`
|
|
1635
|
-
: '#0a0a1a';
|
|
1636
|
-
const bgGradient = this._config.backgroundGradient ??
|
|
1637
|
-
`linear-gradient(135deg, ${bgColor} 0%, #1a1a3e 100%)`;
|
|
1638
|
-
this._overlay = document.createElement('div');
|
|
1639
|
-
this._overlay.id = '__ge-loading-overlay__';
|
|
1640
|
-
this._overlay.innerHTML = `
|
|
1641
|
-
<div class="ge-loading-content">
|
|
1642
|
-
${buildLoadingLogoSVG()}
|
|
1643
|
-
</div>
|
|
1644
|
-
`;
|
|
1645
|
-
const style = document.createElement('style');
|
|
1646
|
-
style.id = '__ge-loading-style__';
|
|
1647
|
-
style.textContent = `
|
|
1648
|
-
#__ge-loading-overlay__ {
|
|
1649
|
-
position: absolute;
|
|
1650
|
-
top: 0; left: 0;
|
|
1651
|
-
width: 100%; height: 100%;
|
|
1652
|
-
background: ${bgGradient};
|
|
1653
|
-
display: flex;
|
|
1654
|
-
align-items: center;
|
|
1655
|
-
justify-content: center;
|
|
1656
|
-
z-index: 9999;
|
|
1657
|
-
transition: opacity 0.5s ease-out;
|
|
1658
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1659
|
-
}
|
|
1660
|
-
#__ge-loading-overlay__.ge-fade-out {
|
|
1661
|
-
opacity: 0;
|
|
1662
|
-
pointer-events: none;
|
|
1663
|
-
}
|
|
1664
|
-
.ge-loading-content {
|
|
1665
|
-
display: flex;
|
|
1666
|
-
flex-direction: column;
|
|
1667
|
-
align-items: center;
|
|
1668
|
-
width: 75%;
|
|
1669
|
-
max-width: 650px;
|
|
1670
|
-
}
|
|
1671
|
-
.ge-loading-content svg {
|
|
1672
|
-
filter: drop-shadow(0 0 40px rgba(121, 57, 194, 0.5));
|
|
1673
|
-
cursor: default;
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
.ge-svg-pulse {
|
|
1677
|
-
animation: ge-tap-pulse 1.2s ease-in-out infinite;
|
|
1678
|
-
}
|
|
1679
|
-
@keyframes ge-tap-pulse {
|
|
1680
|
-
0%, 100% { opacity: 0.5; }
|
|
1681
|
-
50% { opacity: 1; }
|
|
1682
|
-
}
|
|
1683
|
-
`;
|
|
1684
|
-
// Get the container that holds the canvas
|
|
1685
|
-
const container = this._engine.app?.canvas?.parentElement;
|
|
1686
|
-
if (container) {
|
|
1687
|
-
container.style.position = container.style.position || 'relative';
|
|
1688
|
-
container.appendChild(style);
|
|
1689
|
-
container.appendChild(this._overlay);
|
|
1690
|
-
}
|
|
1691
|
-
// Cache the SVG loader rect for progress updates
|
|
1692
|
-
this._loaderRect = this._overlay.querySelector('#ge-loader-rect');
|
|
1693
|
-
this._percentEl = this._overlay.querySelector('#ge-loader-pct');
|
|
1694
|
-
}
|
|
1695
|
-
removeOverlay() {
|
|
1696
|
-
this._overlay?.remove();
|
|
1697
|
-
document.getElementById('__ge-loading-style__')?.remove();
|
|
1698
|
-
this._overlay = null;
|
|
1699
|
-
this._loaderRect = null;
|
|
1700
|
-
this._percentEl = null;
|
|
1701
|
-
this._tapToStartEl = null;
|
|
1608
|
+
// Defensive: ensure the preloader is gone even if we never transitioned
|
|
1609
|
+
// (e.g. the scene was popped externally). Idempotent.
|
|
1610
|
+
void loading.removeCSSPreloader(this.hostElement());
|
|
1702
1611
|
}
|
|
1703
1612
|
// ─── Progress ──────────────────────────────────────────
|
|
1704
1613
|
updateLoaderBar(progress) {
|
|
1705
|
-
|
|
1706
|
-
this._loaderRect.setAttribute('width', String(loading.LOADER_BAR_MAX_WIDTH * progress));
|
|
1707
|
-
}
|
|
1708
|
-
if (this._percentEl) {
|
|
1709
|
-
const pct = Math.round(progress * 100);
|
|
1710
|
-
this._percentEl.textContent = `${pct}%`;
|
|
1711
|
-
}
|
|
1614
|
+
loading.setCSSPreloaderProgress(Math.max(0, Math.min(1, progress)));
|
|
1712
1615
|
}
|
|
1713
1616
|
/**
|
|
1714
1617
|
* Smoothly animate the displayed progress from its current value to `target`
|
|
@@ -1738,49 +1641,20 @@ class LoadingScene extends Scene {
|
|
|
1738
1641
|
requestAnimationFrame(tick);
|
|
1739
1642
|
});
|
|
1740
1643
|
}
|
|
1741
|
-
// ─── Tap to Start ─────────────────────────────────────
|
|
1742
|
-
async showTapToStart() {
|
|
1743
|
-
const tapText = this._config.tapToStartText ?? 'TAP TO START';
|
|
1744
|
-
// Reuse the same SVG text element — replace percentage with tap text
|
|
1745
|
-
if (this._percentEl) {
|
|
1746
|
-
const el = this._percentEl;
|
|
1747
|
-
el.textContent = tapText;
|
|
1748
|
-
el.setAttribute('fill', '#ffffff');
|
|
1749
|
-
el.classList.add('ge-svg-pulse');
|
|
1750
|
-
this._tapToStartEl = el;
|
|
1751
|
-
}
|
|
1752
|
-
// Make overlay clickable
|
|
1753
|
-
if (this._overlay) {
|
|
1754
|
-
this._overlay.style.cursor = 'pointer';
|
|
1755
|
-
}
|
|
1756
|
-
// Wait for tap
|
|
1757
|
-
return new Promise((resolve) => {
|
|
1758
|
-
const handler = async () => {
|
|
1759
|
-
this._overlay?.removeEventListener('click', handler);
|
|
1760
|
-
await this.transitionToGame();
|
|
1761
|
-
resolve();
|
|
1762
|
-
};
|
|
1763
|
-
// Listen on the full overlay for easier mobile tap
|
|
1764
|
-
this._overlay?.addEventListener('click', handler);
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
1644
|
// ─── Transition ────────────────────────────────────────
|
|
1645
|
+
/** The DOM element hosting the canvas + preloader overlay. */
|
|
1646
|
+
hostElement() {
|
|
1647
|
+
return this._engine?.app?.canvas?.parentElement ?? document.body;
|
|
1648
|
+
}
|
|
1768
1649
|
async transitionToGame() {
|
|
1769
|
-
// Fade out the
|
|
1770
|
-
|
|
1771
|
-
this._overlay.classList.add('ge-fade-out');
|
|
1772
|
-
await new Promise((resolve) => {
|
|
1773
|
-
this._overlay.addEventListener('transitionend', () => resolve(), { once: true });
|
|
1774
|
-
// Safety timeout
|
|
1775
|
-
setTimeout(resolve, 600);
|
|
1776
|
-
});
|
|
1777
|
-
}
|
|
1778
|
-
// Remove overlay
|
|
1779
|
-
this.removeOverlay();
|
|
1650
|
+
// Fade out and remove the shared CSS preloader (resolves after the fade).
|
|
1651
|
+
await loading.removeCSSPreloader(this.hostElement());
|
|
1780
1652
|
// Navigate to the target scene, always passing the engine reference
|
|
1781
1653
|
await this._engine.scenes.goto(this._targetScene, {
|
|
1782
1654
|
engine: this._engine,
|
|
1783
|
-
...(this._targetData && typeof this._targetData === 'object'
|
|
1655
|
+
...(this._targetData && typeof this._targetData === 'object'
|
|
1656
|
+
? this._targetData
|
|
1657
|
+
: { data: this._targetData }),
|
|
1784
1658
|
});
|
|
1785
1659
|
}
|
|
1786
1660
|
}
|
|
@@ -2007,17 +1881,20 @@ class GameApplication extends EventEmitter {
|
|
|
2007
1881
|
// 6. Initialize sub-systems
|
|
2008
1882
|
this.initSubSystems();
|
|
2009
1883
|
this.emit('initialized');
|
|
2010
|
-
// 7.
|
|
2011
|
-
|
|
2012
|
-
//
|
|
1884
|
+
// 7. Load assets. The CSS preloader stays on screen — LoadingScene drives
|
|
1885
|
+
// its progress/tap and removes it before entering the game, so there's
|
|
1886
|
+
// a single continuous overlay from boot to gameplay (no logo flash).
|
|
2013
1887
|
await this.loadAssets(firstScene, sceneData);
|
|
2014
1888
|
this.emit('loaded');
|
|
2015
|
-
//
|
|
1889
|
+
// 8. Start the game loop
|
|
2016
1890
|
this._running = true;
|
|
2017
1891
|
this.emit('started');
|
|
2018
1892
|
}
|
|
2019
1893
|
catch (err) {
|
|
2020
1894
|
console.error('[GameEngine] Failed to start:', err);
|
|
1895
|
+
// Tear down the preloader so a failure doesn't strand the brand frame.
|
|
1896
|
+
if (this._container)
|
|
1897
|
+
loading.removeCSSPreloader(this._container);
|
|
2021
1898
|
this.emit('error', err instanceof Error ? err : new Error(String(err)));
|
|
2022
1899
|
throw err;
|
|
2023
1900
|
}
|