@energy8platform/game-engine 0.1.0 → 0.2.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.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) */
@@ -1789,6 +1935,7 @@ class GameApplication extends EventEmitter {
1789
1935
  this._destroyed = true;
1790
1936
  this._running = false;
1791
1937
  this.scenes?.destroy();
1938
+ this.input?.destroy();
1792
1939
  this.audio?.destroy();
1793
1940
  this.viewport?.destroy();
1794
1941
  this.sdk?.destroy();
@@ -1856,6 +2003,8 @@ class GameApplication extends EventEmitter {
1856
2003
  this.assets = new AssetManager(basePath, this.config.manifest);
1857
2004
  // Audio Manager
1858
2005
  this.audio = new AudioManager(this.config.audio);
2006
+ // Input Manager
2007
+ this.input = new InputManager(this.app.canvas);
1859
2008
  // Viewport Manager
1860
2009
  this.viewport = new ViewportManager(this.app, this._container, {
1861
2010
  designWidth: this.config.designWidth,
@@ -2339,150 +2488,6 @@ class SpineHelper {
2339
2488
  }
2340
2489
  }
2341
2490
 
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
2491
  const DEFAULT_COLORS = {
2487
2492
  normal: 0xffd700,
2488
2493
  hover: 0xffe44d,