@energy8platform/game-engine 0.1.0 → 0.2.1

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.esm.js CHANGED
@@ -991,6 +991,150 @@ class AudioManager {
991
991
  }
992
992
  }
993
993
 
994
+ /**
995
+ * Unified input manager for touch, mouse, and keyboard.
996
+ *
997
+ * Features:
998
+ * - Unified pointer events (works with touch + mouse)
999
+ * - Swipe gesture detection
1000
+ * - Keyboard input with isKeyDown state
1001
+ * - Input locking (block input during animations)
1002
+ *
1003
+ * @example
1004
+ * ```ts
1005
+ * const input = new InputManager(app.canvas);
1006
+ *
1007
+ * input.on('tap', ({ x, y }) => console.log('Tapped at', x, y));
1008
+ * input.on('swipe', ({ direction }) => console.log('Swiped', direction));
1009
+ * input.on('keydown', ({ key }) => {
1010
+ * if (key === ' ') spin();
1011
+ * });
1012
+ *
1013
+ * // Block input during animations
1014
+ * input.lock();
1015
+ * await playAnimation();
1016
+ * input.unlock();
1017
+ * ```
1018
+ */
1019
+ class InputManager extends EventEmitter {
1020
+ _canvas;
1021
+ _locked = false;
1022
+ _keysDown = new Set();
1023
+ _destroyed = false;
1024
+ // Gesture tracking
1025
+ _pointerStart = null;
1026
+ _swipeThreshold = 50; // minimum distance in px
1027
+ _swipeMaxTime = 300; // max ms for swipe gesture
1028
+ constructor(canvas) {
1029
+ super();
1030
+ this._canvas = canvas;
1031
+ this.setupPointerEvents();
1032
+ this.setupKeyboardEvents();
1033
+ }
1034
+ /** Whether input is currently locked */
1035
+ get locked() {
1036
+ return this._locked;
1037
+ }
1038
+ /** Lock all input (e.g., during animations) */
1039
+ lock() {
1040
+ this._locked = true;
1041
+ }
1042
+ /** Unlock input */
1043
+ unlock() {
1044
+ this._locked = false;
1045
+ }
1046
+ /** Check if a key is currently pressed */
1047
+ isKeyDown(key) {
1048
+ return this._keysDown.has(key.toLowerCase());
1049
+ }
1050
+ /** Destroy the input manager */
1051
+ destroy() {
1052
+ this._destroyed = true;
1053
+ this._canvas.removeEventListener('pointerdown', this.onPointerDown);
1054
+ this._canvas.removeEventListener('pointerup', this.onPointerUp);
1055
+ this._canvas.removeEventListener('pointermove', this.onPointerMove);
1056
+ document.removeEventListener('keydown', this.onKeyDown);
1057
+ document.removeEventListener('keyup', this.onKeyUp);
1058
+ this._keysDown.clear();
1059
+ this.removeAllListeners();
1060
+ }
1061
+ // ─── Private: Pointer ──────────────────────────────────
1062
+ setupPointerEvents() {
1063
+ this._canvas.addEventListener('pointerdown', this.onPointerDown);
1064
+ this._canvas.addEventListener('pointerup', this.onPointerUp);
1065
+ this._canvas.addEventListener('pointermove', this.onPointerMove);
1066
+ }
1067
+ onPointerDown = (e) => {
1068
+ if (this._locked || this._destroyed)
1069
+ return;
1070
+ const pos = this.getCanvasPosition(e);
1071
+ this._pointerStart = { ...pos, time: Date.now() };
1072
+ this.emit('press', pos);
1073
+ };
1074
+ onPointerUp = (e) => {
1075
+ if (this._locked || this._destroyed)
1076
+ return;
1077
+ const pos = this.getCanvasPosition(e);
1078
+ this.emit('release', pos);
1079
+ // Check for tap vs swipe
1080
+ if (this._pointerStart) {
1081
+ const dx = pos.x - this._pointerStart.x;
1082
+ const dy = pos.y - this._pointerStart.y;
1083
+ const dist = Math.sqrt(dx * dx + dy * dy);
1084
+ const elapsed = Date.now() - this._pointerStart.time;
1085
+ if (dist > this._swipeThreshold && elapsed < this._swipeMaxTime) {
1086
+ // Swipe detected
1087
+ const absDx = Math.abs(dx);
1088
+ const absDy = Math.abs(dy);
1089
+ let direction;
1090
+ if (absDx > absDy) {
1091
+ direction = dx > 0 ? 'right' : 'left';
1092
+ }
1093
+ else {
1094
+ direction = dy > 0 ? 'down' : 'up';
1095
+ }
1096
+ this.emit('swipe', { direction, velocity: dist / elapsed });
1097
+ }
1098
+ else if (dist < 10) {
1099
+ // Tap (minimal movement)
1100
+ this.emit('tap', pos);
1101
+ }
1102
+ }
1103
+ this._pointerStart = null;
1104
+ };
1105
+ onPointerMove = (e) => {
1106
+ if (this._locked || this._destroyed)
1107
+ return;
1108
+ this.emit('move', this.getCanvasPosition(e));
1109
+ };
1110
+ getCanvasPosition(e) {
1111
+ const rect = this._canvas.getBoundingClientRect();
1112
+ return {
1113
+ x: e.clientX - rect.left,
1114
+ y: e.clientY - rect.top,
1115
+ };
1116
+ }
1117
+ // ─── Private: Keyboard ─────────────────────────────────
1118
+ setupKeyboardEvents() {
1119
+ document.addEventListener('keydown', this.onKeyDown);
1120
+ document.addEventListener('keyup', this.onKeyUp);
1121
+ }
1122
+ onKeyDown = (e) => {
1123
+ if (this._locked || this._destroyed)
1124
+ return;
1125
+ this._keysDown.add(e.key.toLowerCase());
1126
+ this.emit('keydown', { key: e.key, code: e.code });
1127
+ };
1128
+ onKeyUp = (e) => {
1129
+ if (this._destroyed)
1130
+ return;
1131
+ this._keysDown.delete(e.key.toLowerCase());
1132
+ if (this._locked)
1133
+ return;
1134
+ this.emit('keyup', { key: e.key, code: e.code });
1135
+ };
1136
+ }
1137
+
994
1138
  /**
995
1139
  * Manages responsive scaling of the game canvas to fit its container.
996
1140
  *
@@ -1694,6 +1838,8 @@ class GameApplication extends EventEmitter {
1694
1838
  assets;
1695
1839
  /** Audio manager */
1696
1840
  audio;
1841
+ /** Input manager */
1842
+ input;
1697
1843
  /** Viewport manager */
1698
1844
  viewport;
1699
1845
  /** SDK instance (null in offline mode) */
@@ -1770,6 +1916,7 @@ class GameApplication extends EventEmitter {
1770
1916
  removeCSSPreloader(this._container);
1771
1917
  // 8. Load assets with loading screen
1772
1918
  await this.loadAssets(firstScene, sceneData);
1919
+ this.emit('loaded', undefined);
1773
1920
  // 9. Start the game loop
1774
1921
  this._running = true;
1775
1922
  this.emit('started', undefined);
@@ -1789,12 +1936,13 @@ class GameApplication extends EventEmitter {
1789
1936
  this._destroyed = true;
1790
1937
  this._running = false;
1791
1938
  this.scenes?.destroy();
1939
+ this.input?.destroy();
1792
1940
  this.audio?.destroy();
1793
1941
  this.viewport?.destroy();
1794
1942
  this.sdk?.destroy();
1795
1943
  this.app?.destroy(true, { children: true, texture: true });
1796
- this.removeAllListeners();
1797
1944
  this.emit('destroyed', undefined);
1945
+ this.removeAllListeners();
1798
1946
  }
1799
1947
  // ─── Private initialization steps ──────────────────────
1800
1948
  resolveContainer() {
@@ -1856,6 +2004,8 @@ class GameApplication extends EventEmitter {
1856
2004
  this.assets = new AssetManager(basePath, this.config.manifest);
1857
2005
  // Audio Manager
1858
2006
  this.audio = new AudioManager(this.config.audio);
2007
+ // Input Manager
2008
+ this.input = new InputManager(this.app.canvas);
1859
2009
  // Viewport Manager
1860
2010
  this.viewport = new ViewportManager(this.app, this._container, {
1861
2011
  designWidth: this.config.designWidth,
@@ -1873,6 +2023,10 @@ class GameApplication extends EventEmitter {
1873
2023
  this.viewport.on('orientationChange', (orientation) => {
1874
2024
  this.emit('orientationChange', orientation);
1875
2025
  });
2026
+ // Wire scene changes → engine event
2027
+ this.scenes.on('change', ({ from, to }) => {
2028
+ this.emit('sceneChange', { from, to });
2029
+ });
1876
2030
  // Connect ticker → scene updates
1877
2031
  this.app.ticker.add((ticker) => {
1878
2032
  // Always update scenes (loading screen needs onUpdate before _running=true)
@@ -2339,150 +2493,6 @@ class SpineHelper {
2339
2493
  }
2340
2494
  }
2341
2495
 
2342
- /**
2343
- * Unified input manager for touch, mouse, and keyboard.
2344
- *
2345
- * Features:
2346
- * - Unified pointer events (works with touch + mouse)
2347
- * - Swipe gesture detection
2348
- * - Keyboard input with isKeyDown state
2349
- * - Input locking (block input during animations)
2350
- *
2351
- * @example
2352
- * ```ts
2353
- * const input = new InputManager(app.canvas);
2354
- *
2355
- * input.on('tap', ({ x, y }) => console.log('Tapped at', x, y));
2356
- * input.on('swipe', ({ direction }) => console.log('Swiped', direction));
2357
- * input.on('keydown', ({ key }) => {
2358
- * if (key === ' ') spin();
2359
- * });
2360
- *
2361
- * // Block input during animations
2362
- * input.lock();
2363
- * await playAnimation();
2364
- * input.unlock();
2365
- * ```
2366
- */
2367
- class InputManager extends EventEmitter {
2368
- _canvas;
2369
- _locked = false;
2370
- _keysDown = new Set();
2371
- _destroyed = false;
2372
- // Gesture tracking
2373
- _pointerStart = null;
2374
- _swipeThreshold = 50; // minimum distance in px
2375
- _swipeMaxTime = 300; // max ms for swipe gesture
2376
- constructor(canvas) {
2377
- super();
2378
- this._canvas = canvas;
2379
- this.setupPointerEvents();
2380
- this.setupKeyboardEvents();
2381
- }
2382
- /** Whether input is currently locked */
2383
- get locked() {
2384
- return this._locked;
2385
- }
2386
- /** Lock all input (e.g., during animations) */
2387
- lock() {
2388
- this._locked = true;
2389
- }
2390
- /** Unlock input */
2391
- unlock() {
2392
- this._locked = false;
2393
- }
2394
- /** Check if a key is currently pressed */
2395
- isKeyDown(key) {
2396
- return this._keysDown.has(key.toLowerCase());
2397
- }
2398
- /** Destroy the input manager */
2399
- destroy() {
2400
- this._destroyed = true;
2401
- this._canvas.removeEventListener('pointerdown', this.onPointerDown);
2402
- this._canvas.removeEventListener('pointerup', this.onPointerUp);
2403
- this._canvas.removeEventListener('pointermove', this.onPointerMove);
2404
- document.removeEventListener('keydown', this.onKeyDown);
2405
- document.removeEventListener('keyup', this.onKeyUp);
2406
- this._keysDown.clear();
2407
- this.removeAllListeners();
2408
- }
2409
- // ─── Private: Pointer ──────────────────────────────────
2410
- setupPointerEvents() {
2411
- this._canvas.addEventListener('pointerdown', this.onPointerDown);
2412
- this._canvas.addEventListener('pointerup', this.onPointerUp);
2413
- this._canvas.addEventListener('pointermove', this.onPointerMove);
2414
- }
2415
- onPointerDown = (e) => {
2416
- if (this._locked || this._destroyed)
2417
- return;
2418
- const pos = this.getCanvasPosition(e);
2419
- this._pointerStart = { ...pos, time: Date.now() };
2420
- this.emit('press', pos);
2421
- };
2422
- onPointerUp = (e) => {
2423
- if (this._locked || this._destroyed)
2424
- return;
2425
- const pos = this.getCanvasPosition(e);
2426
- this.emit('release', pos);
2427
- // Check for tap vs swipe
2428
- if (this._pointerStart) {
2429
- const dx = pos.x - this._pointerStart.x;
2430
- const dy = pos.y - this._pointerStart.y;
2431
- const dist = Math.sqrt(dx * dx + dy * dy);
2432
- const elapsed = Date.now() - this._pointerStart.time;
2433
- if (dist > this._swipeThreshold && elapsed < this._swipeMaxTime) {
2434
- // Swipe detected
2435
- const absDx = Math.abs(dx);
2436
- const absDy = Math.abs(dy);
2437
- let direction;
2438
- if (absDx > absDy) {
2439
- direction = dx > 0 ? 'right' : 'left';
2440
- }
2441
- else {
2442
- direction = dy > 0 ? 'down' : 'up';
2443
- }
2444
- this.emit('swipe', { direction, velocity: dist / elapsed });
2445
- }
2446
- else if (dist < 10) {
2447
- // Tap (minimal movement)
2448
- this.emit('tap', pos);
2449
- }
2450
- }
2451
- this._pointerStart = null;
2452
- };
2453
- onPointerMove = (e) => {
2454
- if (this._locked || this._destroyed)
2455
- return;
2456
- this.emit('move', this.getCanvasPosition(e));
2457
- };
2458
- getCanvasPosition(e) {
2459
- const rect = this._canvas.getBoundingClientRect();
2460
- return {
2461
- x: e.clientX - rect.left,
2462
- y: e.clientY - rect.top,
2463
- };
2464
- }
2465
- // ─── Private: Keyboard ─────────────────────────────────
2466
- setupKeyboardEvents() {
2467
- document.addEventListener('keydown', this.onKeyDown);
2468
- document.addEventListener('keyup', this.onKeyUp);
2469
- }
2470
- onKeyDown = (e) => {
2471
- if (this._locked || this._destroyed)
2472
- return;
2473
- this._keysDown.add(e.key.toLowerCase());
2474
- this.emit('keydown', { key: e.key, code: e.code });
2475
- };
2476
- onKeyUp = (e) => {
2477
- if (this._destroyed)
2478
- return;
2479
- this._keysDown.delete(e.key.toLowerCase());
2480
- if (this._locked)
2481
- return;
2482
- this.emit('keyup', { key: e.key, code: e.code });
2483
- };
2484
- }
2485
-
2486
2496
  const DEFAULT_COLORS = {
2487
2497
  normal: 0xffd700,
2488
2498
  hover: 0xffe44d,