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