@duyquangnvx/pixi-game-engine 0.1.0 → 0.1.2

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.js CHANGED
@@ -1,4 +1,4 @@
1
- // src/core/game.ts
1
+ // src/core/engine.ts
2
2
  import * as PIXI10 from "pixi.js";
3
3
 
4
4
  // src/ui/alert/alert-manager.ts
@@ -109,19 +109,19 @@ var BaseModal = class extends PIXI.Container {
109
109
  didHide = new Signal();
110
110
  /** Signal emitted when modal is shown (with data) */
111
111
  didShow = new Signal();
112
- /** Get screen width from Game singleton */
112
+ /** Get screen width from Engine */
113
113
  get screenWidth() {
114
- return Game.instance.screen.width;
114
+ return Engine.screen.width;
115
115
  }
116
- /** Get screen height from Game singleton */
116
+ /** Get screen height from Engine */
117
117
  get screenHeight() {
118
- return Game.instance.screen.height;
118
+ return Engine.screen.height;
119
119
  }
120
120
  constructor(config) {
121
121
  super();
122
122
  this.config = { ...DEFAULT_CONFIG, ...config };
123
123
  this.setup();
124
- this.resizeBinding = Game.instance.onResize.add(() => {
124
+ this.resizeBinding = Engine.onResize.add(() => {
125
125
  this.drawBackdrop();
126
126
  if (this.config.centered) {
127
127
  this.centerModal();
@@ -255,7 +255,7 @@ var BaseModal = class extends PIXI.Container {
255
255
  drawBackdrop() {
256
256
  this.backdrop.clear();
257
257
  if (this.config.backdrop) {
258
- const bounds = Game.instance.getFullscreenBounds();
258
+ const bounds = Engine.getFullscreenBounds();
259
259
  this.backdrop.beginFill(0, this.config.backdropAlpha);
260
260
  this.backdrop.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
261
261
  this.backdrop.endFill();
@@ -914,17 +914,17 @@ var PointerManager = class {
914
914
  this.onPointerMove = this.onPointerMove.bind(this);
915
915
  this.onPointerDown = this.onPointerDown.bind(this);
916
916
  this.onPointerUp = this.onPointerUp.bind(this);
917
- const view = Game.instance.view;
917
+ const view = Engine.view;
918
918
  view.addEventListener("pointermove", this.onPointerMove);
919
919
  view.addEventListener("pointerdown", this.onPointerDown);
920
920
  view.addEventListener("pointerup", this.onPointerUp);
921
921
  view.addEventListener("pointerleave", this.onPointerUp);
922
922
  }
923
923
  getCanvasCoords(event) {
924
- const view = Game.instance.view;
924
+ const view = Engine.view;
925
925
  const rect = view.getBoundingClientRect();
926
- const scaleX = Game.instance.screen.width / rect.width;
927
- const scaleY = Game.instance.screen.height / rect.height;
926
+ const scaleX = Engine.screen.width / rect.width;
927
+ const scaleY = Engine.screen.height / rect.height;
928
928
  return {
929
929
  x: (event.clientX - rect.left) * scaleX,
930
930
  y: (event.clientY - rect.top) * scaleY
@@ -982,7 +982,7 @@ var PointerManager = class {
982
982
  }
983
983
  /** Clean up event listeners */
984
984
  destroy() {
985
- const view = Game.instance.view;
985
+ const view = Engine.view;
986
986
  view.removeEventListener("pointermove", this.onPointerMove);
987
987
  view.removeEventListener("pointerdown", this.onPointerDown);
988
988
  view.removeEventListener("pointerup", this.onPointerUp);
@@ -1220,13 +1220,13 @@ var SceneManager = class {
1220
1220
  }
1221
1221
  if (this.currentScene) {
1222
1222
  this.currentScene.onExit();
1223
- Game.instance.stage.removeChild(this.currentScene);
1223
+ Engine.stage.removeChild(this.currentScene);
1224
1224
  this.currentScene.destroy({ children: true });
1225
1225
  }
1226
1226
  const scene = new SceneClass();
1227
1227
  this.currentScene = scene;
1228
1228
  this.currentSceneName = name;
1229
- Game.instance.stage.addChild(scene);
1229
+ Engine.stage.addChild(scene);
1230
1230
  await scene.onEnter();
1231
1231
  }
1232
1232
  /** Get current scene name */
@@ -1791,13 +1791,13 @@ var ToastManager = class {
1791
1791
  currentColors;
1792
1792
  currentThemeName;
1793
1793
  resizeBinding = null;
1794
- /** Get screen width from Game singleton */
1794
+ /** Get screen width from Engine */
1795
1795
  get screenWidth() {
1796
- return Game.instance.screen.width;
1796
+ return Engine.screen.width;
1797
1797
  }
1798
- /** Get screen height from Game singleton */
1798
+ /** Get screen height from Engine */
1799
1799
  get screenHeight() {
1800
- return Game.instance.screen.height;
1800
+ return Engine.screen.height;
1801
1801
  }
1802
1802
  constructor(stage, config) {
1803
1803
  if (config?.theme) {
@@ -1817,7 +1817,7 @@ var ToastManager = class {
1817
1817
  this.layer.sortableChildren = true;
1818
1818
  this.layer.zIndex = 9999;
1819
1819
  stage.addChild(this.layer);
1820
- this.resizeBinding = Game.instance.onResize.add(() => {
1820
+ this.resizeBinding = Engine.onResize.add(() => {
1821
1821
  this.repositionToasts();
1822
1822
  });
1823
1823
  }
@@ -2285,135 +2285,198 @@ var UIManager = class {
2285
2285
  }
2286
2286
  };
2287
2287
 
2288
- // src/core/game.ts
2289
- var Game = class _Game {
2290
- static _instance = null;
2291
- /** Get the singleton Game instance. Throws if not initialized. */
2292
- static get instance() {
2293
- if (!_Game._instance) {
2294
- throw new Error("Game not initialized. Call Game.init() first.");
2295
- }
2296
- return _Game._instance;
2297
- }
2298
- /** Initialize the Game singleton. Throws if already initialized. */
2299
- static init(config) {
2300
- if (_Game._instance) {
2301
- throw new Error("Game already initialized. Call destroy() first to reinitialize.");
2302
- }
2303
- return new _Game(config);
2288
+ // src/core/engine.ts
2289
+ var Engine = class _Engine {
2290
+ constructor() {
2304
2291
  }
2305
- app;
2306
- scenes;
2307
- input;
2308
- assets;
2309
- sound;
2310
- particles;
2311
- tween;
2312
- ui;
2313
- spine;
2314
- toast;
2315
- alert;
2316
- modal;
2317
- /** Logger instance for consistent logging across the game */
2318
- logger = Logger;
2319
- /** Signal emitted when game is paused */
2320
- onPause = new Signal();
2321
- /** Signal emitted when game is resumed */
2322
- onResume = new Signal();
2323
- /** Signal emitted when game is resized */
2324
- onResize = new Signal();
2325
- /** Signal emitted when game is updated */
2326
- onUpdate = new Signal();
2292
+ static _app;
2293
+ static _scenes;
2294
+ static _input;
2295
+ static _assets;
2296
+ static _sound;
2297
+ static _particles;
2298
+ static _tween;
2299
+ static _ui;
2300
+ static _spine;
2301
+ static _toast;
2302
+ static _alert;
2303
+ static _modal;
2304
+ /** Signal emitted when engine is paused */
2305
+ static onPause = new Signal();
2306
+ /** Signal emitted when engine is resumed */
2307
+ static onResume = new Signal();
2308
+ /** Signal emitted when engine is resized */
2309
+ static onResize = new Signal();
2310
+ /** Signal emitted when engine is updated */
2311
+ static onUpdate = new Signal();
2312
+ /** Signal emitted at the end of init() after all managers are constructed */
2313
+ static onReady = new Signal();
2314
+ /** Signal emitted at the start of destroy() before any teardown */
2315
+ static onDestroy = new Signal();
2316
+ /** Signal emitted when browser tab visibility changes (true = visible, false = hidden) */
2317
+ static onVisibilityChange = new Signal();
2318
+ /** Signal emitted when browser window gains/loses focus (true = focused, false = blurred) */
2319
+ static onFocusChange = new Signal();
2327
2320
  /** Design resolution width (original config width) */
2328
- designWidth;
2321
+ static _designWidth;
2329
2322
  /** Design resolution height (original config height) */
2330
- designHeight;
2323
+ static _designHeight;
2331
2324
  /** Current scale mode */
2332
- scaleMode;
2325
+ static _scaleMode;
2333
2326
  /** Current scale factor */
2334
- _scale = 1;
2335
- _isRunning = false;
2336
- _resizeHandler;
2337
- constructor(config) {
2338
- _Game._instance = this;
2339
- this.designWidth = config.width;
2340
- this.designHeight = config.height;
2341
- this.scaleMode = config.scaleMode ?? "letterbox";
2342
- this.app = new PIXI10.Application({
2343
- width: config.width,
2344
- height: config.height,
2345
- backgroundColor: config.backgroundColor ?? 0,
2346
- antialias: config.antialias ?? true,
2347
- resolution: config.resolution ?? window.devicePixelRatio,
2348
- autoDensity: config.autoDensity ?? true
2349
- });
2350
- this.app.stage.sortableChildren = true;
2351
- this.scenes = new SceneManager();
2352
- this.input = new InputManager();
2353
- this.assets = new AssetManager();
2354
- this.sound = new SoundManager();
2355
- this.particles = new ParticleManager();
2356
- this.tween = new TweenManager();
2357
- this.ui = new UIManager();
2358
- this.spine = new SpineManager();
2359
- this.toast = new ToastManager(this.app.stage);
2360
- this.alert = new AlertManager(this.app.stage);
2361
- this.modal = new ModalManager(this.app.stage);
2362
- this._resizeHandler = () => this.resizeToWindow();
2363
- if (config.autoResize !== false) {
2364
- window.addEventListener("resize", this._resizeHandler);
2365
- }
2366
- this.app.ticker.add(this.update, this);
2367
- this._isRunning = true;
2327
+ static _scale = 1;
2328
+ static _isRunning = false;
2329
+ static _resizeHandler;
2330
+ static _visibilityHandler;
2331
+ static _focusHandler;
2332
+ static _blurHandler;
2333
+ static _initialized = false;
2334
+ /** Logger instance for consistent logging across the engine */
2335
+ static logger = Logger;
2336
+ static get scenes() {
2337
+ return _Engine._scenes;
2368
2338
  }
2369
- update(delta) {
2370
- this.onUpdate.emit(delta);
2371
- this.input.update();
2372
- this.scenes.update(delta);
2373
- this.particles.update(delta);
2374
- this.input.postUpdate();
2339
+ static get input() {
2340
+ return _Engine._input;
2341
+ }
2342
+ static get assets() {
2343
+ return _Engine._assets;
2344
+ }
2345
+ static get sound() {
2346
+ return _Engine._sound;
2347
+ }
2348
+ static get particles() {
2349
+ return _Engine._particles;
2350
+ }
2351
+ static get tween() {
2352
+ return _Engine._tween;
2353
+ }
2354
+ static get ui() {
2355
+ return _Engine._ui;
2356
+ }
2357
+ static get spine() {
2358
+ return _Engine._spine;
2359
+ }
2360
+ static get toast() {
2361
+ return _Engine._toast;
2375
2362
  }
2376
- get view() {
2377
- return this.app.view;
2363
+ static get alert() {
2364
+ return _Engine._alert;
2378
2365
  }
2379
- get stage() {
2380
- return this.app.stage;
2366
+ static get modal() {
2367
+ return _Engine._modal;
2368
+ }
2369
+ static get app() {
2370
+ return _Engine._app;
2371
+ }
2372
+ static get view() {
2373
+ return _Engine._app.view;
2374
+ }
2375
+ static get stage() {
2376
+ return _Engine._app.stage;
2381
2377
  }
2382
2378
  /** Returns design resolution dimensions (use for positioning game elements) */
2383
- get screen() {
2384
- return new PIXI10.Rectangle(0, 0, this.designWidth, this.designHeight);
2379
+ static get screen() {
2380
+ return new PIXI10.Rectangle(0, 0, _Engine._designWidth, _Engine._designHeight);
2385
2381
  }
2386
2382
  /** Returns actual renderer/viewport dimensions */
2387
- get viewport() {
2388
- return this.app.screen;
2383
+ static get viewport() {
2384
+ return _Engine._app.screen;
2389
2385
  }
2390
- get isRunning() {
2391
- return this._isRunning;
2386
+ static get isRunning() {
2387
+ return _Engine._isRunning;
2392
2388
  }
2393
- pause() {
2394
- this.app.ticker.stop();
2395
- this._isRunning = false;
2396
- this.onPause.emit();
2389
+ /** Design resolution width (original config width) */
2390
+ static get designWidth() {
2391
+ return _Engine._designWidth;
2397
2392
  }
2398
- resume() {
2399
- this.app.ticker.start();
2400
- this._isRunning = true;
2401
- this.onResume.emit();
2393
+ /** Design resolution height (original config height) */
2394
+ static get designHeight() {
2395
+ return _Engine._designHeight;
2396
+ }
2397
+ /** Current scale mode */
2398
+ static get scaleMode() {
2399
+ return _Engine._scaleMode;
2400
+ }
2401
+ static set scaleMode(value) {
2402
+ _Engine._scaleMode = value;
2402
2403
  }
2403
2404
  /** Get current scale factor */
2404
- get scale() {
2405
- return this._scale;
2405
+ static get scale() {
2406
+ return _Engine._scale;
2407
+ }
2408
+ /** Initialize the Engine. Throws if already initialized. */
2409
+ static init(config) {
2410
+ if (_Engine._initialized) {
2411
+ throw new Error("Engine already initialized. Call destroy() first to reinitialize.");
2412
+ }
2413
+ _Engine._initialized = true;
2414
+ _Engine._designWidth = config.width;
2415
+ _Engine._designHeight = config.height;
2416
+ _Engine._scaleMode = config.scaleMode ?? "letterbox";
2417
+ _Engine._app = new PIXI10.Application({
2418
+ width: config.width,
2419
+ height: config.height,
2420
+ backgroundColor: config.backgroundColor ?? 0,
2421
+ antialias: config.antialias ?? true,
2422
+ resolution: config.resolution ?? window.devicePixelRatio,
2423
+ autoDensity: config.autoDensity ?? true
2424
+ });
2425
+ _Engine._app.stage.sortableChildren = true;
2426
+ _Engine._scenes = new SceneManager();
2427
+ _Engine._input = new InputManager();
2428
+ _Engine._assets = new AssetManager();
2429
+ _Engine._sound = new SoundManager();
2430
+ _Engine._particles = new ParticleManager();
2431
+ _Engine._tween = new TweenManager();
2432
+ _Engine._ui = new UIManager();
2433
+ _Engine._spine = new SpineManager();
2434
+ _Engine._toast = new ToastManager(_Engine._app.stage);
2435
+ _Engine._alert = new AlertManager(_Engine._app.stage);
2436
+ _Engine._modal = new ModalManager(_Engine._app.stage);
2437
+ _Engine._resizeHandler = () => _Engine.resizeToWindow();
2438
+ if (config.autoResize !== false) {
2439
+ window.addEventListener("resize", _Engine._resizeHandler);
2440
+ }
2441
+ _Engine._app.ticker.add(_Engine.update);
2442
+ _Engine._isRunning = true;
2443
+ _Engine._visibilityHandler = () => {
2444
+ _Engine.onVisibilityChange.emit(!document.hidden);
2445
+ };
2446
+ _Engine._focusHandler = () => _Engine.onFocusChange.emit(true);
2447
+ _Engine._blurHandler = () => _Engine.onFocusChange.emit(false);
2448
+ document.addEventListener("visibilitychange", _Engine._visibilityHandler);
2449
+ window.addEventListener("focus", _Engine._focusHandler);
2450
+ window.addEventListener("blur", _Engine._blurHandler);
2451
+ _Engine.onReady.emit();
2452
+ }
2453
+ static update(delta) {
2454
+ _Engine.onUpdate.emit(delta);
2455
+ _Engine._input.update();
2456
+ _Engine._scenes.update(delta);
2457
+ _Engine._particles.update(delta);
2458
+ _Engine._input.postUpdate();
2459
+ }
2460
+ static pause() {
2461
+ _Engine._app.ticker.stop();
2462
+ _Engine._isRunning = false;
2463
+ _Engine.onPause.emit();
2464
+ }
2465
+ static resume() {
2466
+ _Engine._app.ticker.start();
2467
+ _Engine._isRunning = true;
2468
+ _Engine.onResume.emit();
2406
2469
  }
2407
2470
  /**
2408
2471
  * Get bounds in stage coordinates that cover the full viewport (including letterbox areas).
2409
2472
  * Use this for fullscreen overlays, backdrops, modals that need to cover the entire screen.
2410
2473
  */
2411
- getFullscreenBounds() {
2412
- const scale = this._scale || 1;
2413
- const stagePos = this.app.stage.position;
2414
- const resolution = this.app.renderer.resolution || 1;
2415
- const rendererWidth = this.app.renderer.width / resolution;
2416
- const rendererHeight = this.app.renderer.height / resolution;
2474
+ static getFullscreenBounds() {
2475
+ const scale = _Engine._scale || 1;
2476
+ const stagePos = _Engine._app.stage.position;
2477
+ const resolution = _Engine._app.renderer.resolution || 1;
2478
+ const rendererWidth = _Engine._app.renderer.width / resolution;
2479
+ const rendererHeight = _Engine._app.renderer.height / resolution;
2417
2480
  return {
2418
2481
  x: -stagePos.x / scale,
2419
2482
  y: -stagePos.y / scale,
@@ -2424,75 +2487,76 @@ var Game = class _Game {
2424
2487
  /**
2425
2488
  * Resize to fit window while maintaining aspect ratio based on scaleMode
2426
2489
  */
2427
- resizeToWindow() {
2428
- const parent = this.app.view.parentElement;
2490
+ static resizeToWindow() {
2491
+ const parent = _Engine._app.view.parentElement;
2429
2492
  const windowWidth = parent?.clientWidth ?? window.innerWidth;
2430
2493
  const windowHeight = parent?.clientHeight ?? window.innerHeight;
2431
- this.resize(windowWidth, windowHeight);
2494
+ _Engine.resize(windowWidth, windowHeight);
2432
2495
  }
2433
2496
  /**
2434
- * Resize game to fit target dimensions while maintaining aspect ratio
2497
+ * Resize engine to fit target dimensions while maintaining aspect ratio
2435
2498
  */
2436
- resize(targetWidth, targetHeight) {
2437
- if (this.scaleMode === "none") {
2438
- this.app.renderer.resize(targetWidth, targetHeight);
2439
- this._scale = 1;
2440
- this.app.stage.scale.set(1);
2441
- this.app.stage.position.set(0, 0);
2442
- this.propagateResize(targetWidth, targetHeight);
2443
- this.onResize.emit({ width: targetWidth, height: targetHeight, scale: 1 });
2499
+ static resize(targetWidth, targetHeight) {
2500
+ if (_Engine._scaleMode === "none") {
2501
+ _Engine._app.renderer.resize(targetWidth, targetHeight);
2502
+ _Engine._scale = 1;
2503
+ _Engine._app.stage.scale.set(1);
2504
+ _Engine._app.stage.position.set(0, 0);
2505
+ _Engine.onResize.emit({ width: targetWidth, height: targetHeight, scale: 1 });
2444
2506
  return;
2445
2507
  }
2446
- const designRatio = this.designWidth / this.designHeight;
2508
+ const designRatio = _Engine._designWidth / _Engine._designHeight;
2447
2509
  const targetRatio = targetWidth / targetHeight;
2448
2510
  let scale;
2449
- if (this.scaleMode === "letterbox") {
2450
- scale = targetRatio > designRatio ? targetHeight / this.designHeight : targetWidth / this.designWidth;
2511
+ if (_Engine._scaleMode === "letterbox") {
2512
+ scale = targetRatio > designRatio ? targetHeight / _Engine._designHeight : targetWidth / _Engine._designWidth;
2451
2513
  } else {
2452
- scale = targetRatio > designRatio ? targetWidth / this.designWidth : targetHeight / this.designHeight;
2453
- }
2454
- this._scale = scale;
2455
- this.app.renderer.resize(targetWidth, targetHeight);
2456
- this.app.stage.scale.set(scale);
2457
- this.app.stage.position.set(
2458
- (targetWidth - this.designWidth * scale) / 2,
2459
- (targetHeight - this.designHeight * scale) / 2
2514
+ scale = targetRatio > designRatio ? targetWidth / _Engine._designWidth : targetHeight / _Engine._designHeight;
2515
+ }
2516
+ _Engine._scale = scale;
2517
+ _Engine._app.renderer.resize(targetWidth, targetHeight);
2518
+ _Engine._app.stage.scale.set(scale);
2519
+ _Engine._app.stage.position.set(
2520
+ (targetWidth - _Engine._designWidth * scale) / 2,
2521
+ (targetHeight - _Engine._designHeight * scale) / 2
2460
2522
  );
2461
- this.propagateResize(targetWidth, targetHeight);
2462
- this.onResize.emit({ width: targetWidth, height: targetHeight, scale });
2463
- }
2464
- /**
2465
- * Propagate resize to all managers that need viewport dimensions
2466
- * Note: Toast/BaseModal auto-resize via Game.instance.onResize signal
2467
- */
2468
- propagateResize(_width, _height) {
2469
- }
2470
- destroy() {
2471
- window.removeEventListener("resize", this._resizeHandler);
2472
- this.app.ticker.remove(this.update, this);
2473
- this.input.destroy();
2474
- this.scenes.destroy();
2475
- this.sound.destroy();
2476
- this.particles.destroy();
2477
- this.tween.destroy();
2478
- this.toast.destroy();
2479
- this.alert.destroy();
2480
- this.modal.destroy();
2481
- this.onPause.clear();
2482
- this.onResume.clear();
2483
- this.onResize.clear();
2484
- this.app.destroy(true, { children: true, texture: true });
2485
- this._isRunning = false;
2486
- _Game._instance = null;
2523
+ _Engine.onResize.emit({ width: targetWidth, height: targetHeight, scale });
2524
+ }
2525
+ static destroy() {
2526
+ if (!_Engine._initialized) return;
2527
+ _Engine.onDestroy.emit();
2528
+ document.removeEventListener("visibilitychange", _Engine._visibilityHandler);
2529
+ window.removeEventListener("focus", _Engine._focusHandler);
2530
+ window.removeEventListener("blur", _Engine._blurHandler);
2531
+ window.removeEventListener("resize", _Engine._resizeHandler);
2532
+ _Engine._app.ticker.remove(_Engine.update);
2533
+ _Engine._input.destroy();
2534
+ _Engine._scenes.destroy();
2535
+ _Engine._sound.destroy();
2536
+ _Engine._particles.destroy();
2537
+ _Engine._tween.destroy();
2538
+ _Engine._toast.destroy();
2539
+ _Engine._alert.destroy();
2540
+ _Engine._modal.destroy();
2541
+ _Engine.onPause.clear();
2542
+ _Engine.onResume.clear();
2543
+ _Engine.onResize.clear();
2544
+ _Engine.onReady.clear();
2545
+ _Engine.onDestroy.clear();
2546
+ _Engine.onVisibilityChange.clear();
2547
+ _Engine.onFocusChange.clear();
2548
+ _Engine._app.destroy(true, { children: true, texture: true });
2549
+ _Engine._isRunning = false;
2550
+ _Engine._initialized = false;
2487
2551
  }
2488
2552
  };
2489
2553
 
2490
2554
  // src/scenes/scene.ts
2491
2555
  import * as PIXI11 from "pixi.js";
2492
2556
  var Scene = class extends PIXI11.Container {
2493
- /** Get game instance */
2494
- get game() {
2495
- return Game.instance;
2557
+ /** Get engine class for static access */
2558
+ get engine() {
2559
+ return Engine;
2496
2560
  }
2497
2561
  /** Called when scene is paused (another scene pushed on top) */
2498
2562
  onPause() {
@@ -2719,7 +2783,7 @@ export {
2719
2783
  BaseModal,
2720
2784
  COUNT_PRESETS,
2721
2785
  CountAnimator,
2722
- Game,
2786
+ Engine,
2723
2787
  GamepadManager,
2724
2788
  InputManager,
2725
2789
  KeyboardManager,