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