@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/README.md CHANGED
@@ -1,17 +1,17 @@
1
- # @pixi-game/game-engine
1
+ # @duyquangnvx/pixi-game-engine
2
2
 
3
3
  Full-featured PixiJS v7 game engine with TypeScript support.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- pnpm add @pixi-game/game-engine
8
+ pnpm add @duyquangnvx/pixi-game-engine
9
9
  ```
10
10
 
11
11
  ## Quick Start
12
12
 
13
13
  ```typescript
14
- import { Game, Scene, PIXI } from '@pixi-game/game-engine';
14
+ import { Engine, Scene, PIXI } from '@duyquangnvx/pixi-game-engine';
15
15
 
16
16
  class GameScene extends Scene {
17
17
  private player!: PIXI.Sprite;
@@ -20,13 +20,13 @@ class GameScene extends Scene {
20
20
  this.player = new PIXI.Graphics();
21
21
  this.player.beginFill(0x4ecca3);
22
22
  this.player.drawRect(-25, -25, 50, 50);
23
- this.player.x = this.game.screen.width / 2;
24
- this.player.y = this.game.screen.height / 2;
23
+ this.player.x = Engine.screen.width / 2;
24
+ this.player.y = Engine.screen.height / 2;
25
25
  this.addChild(this.player);
26
26
  }
27
27
 
28
28
  onUpdate(delta: number) {
29
- const { keyboard } = this.game.input;
29
+ const { keyboard } = Engine.input;
30
30
 
31
31
  if (keyboard.isDown('ArrowLeft')) this.player.x -= 5 * delta;
32
32
  if (keyboard.isDown('ArrowRight')) this.player.x += 5 * delta;
@@ -37,15 +37,15 @@ class GameScene extends Scene {
37
37
  onExit() {}
38
38
  }
39
39
 
40
- const game = new Game({
40
+ Engine.init({
41
41
  width: 800,
42
42
  height: 600,
43
43
  backgroundColor: 0x1a1a2e,
44
44
  });
45
45
 
46
- document.body.appendChild(game.view as HTMLCanvasElement);
47
- game.scenes.add('game', GameScene);
48
- game.scenes.start('game');
46
+ document.body.appendChild(Engine.view as HTMLCanvasElement);
47
+ Engine.scenes.add('game', GameScene);
48
+ Engine.scenes.start('game');
49
49
  ```
50
50
 
51
51
  ## Features
package/dist/index.cjs CHANGED
@@ -35,7 +35,7 @@ __export(index_exports, {
35
35
  BaseModal: () => BaseModal,
36
36
  COUNT_PRESETS: () => COUNT_PRESETS,
37
37
  CountAnimator: () => CountAnimator,
38
- Game: () => Game,
38
+ Engine: () => Engine,
39
39
  GamepadManager: () => GamepadManager,
40
40
  InputManager: () => InputManager,
41
41
  KeyboardManager: () => KeyboardManager,
@@ -63,7 +63,7 @@ __export(index_exports, {
63
63
  });
64
64
  module.exports = __toCommonJS(index_exports);
65
65
 
66
- // src/core/game.ts
66
+ // src/core/engine.ts
67
67
  var PIXI10 = __toESM(require("pixi.js"), 1);
68
68
 
69
69
  // src/ui/alert/alert-manager.ts
@@ -174,19 +174,19 @@ var BaseModal = class extends PIXI.Container {
174
174
  didHide = new Signal();
175
175
  /** Signal emitted when modal is shown (with data) */
176
176
  didShow = new Signal();
177
- /** Get screen width from Game singleton */
177
+ /** Get screen width from Engine */
178
178
  get screenWidth() {
179
- return Game.instance.screen.width;
179
+ return Engine.screen.width;
180
180
  }
181
- /** Get screen height from Game singleton */
181
+ /** Get screen height from Engine */
182
182
  get screenHeight() {
183
- return Game.instance.screen.height;
183
+ return Engine.screen.height;
184
184
  }
185
185
  constructor(config) {
186
186
  super();
187
187
  this.config = { ...DEFAULT_CONFIG, ...config };
188
188
  this.setup();
189
- this.resizeBinding = Game.instance.onResize.add(() => {
189
+ this.resizeBinding = Engine.onResize.add(() => {
190
190
  this.drawBackdrop();
191
191
  if (this.config.centered) {
192
192
  this.centerModal();
@@ -320,7 +320,7 @@ var BaseModal = class extends PIXI.Container {
320
320
  drawBackdrop() {
321
321
  this.backdrop.clear();
322
322
  if (this.config.backdrop) {
323
- const bounds = Game.instance.getFullscreenBounds();
323
+ const bounds = Engine.getFullscreenBounds();
324
324
  this.backdrop.beginFill(0, this.config.backdropAlpha);
325
325
  this.backdrop.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
326
326
  this.backdrop.endFill();
@@ -979,17 +979,17 @@ var PointerManager = class {
979
979
  this.onPointerMove = this.onPointerMove.bind(this);
980
980
  this.onPointerDown = this.onPointerDown.bind(this);
981
981
  this.onPointerUp = this.onPointerUp.bind(this);
982
- const view = Game.instance.view;
982
+ const view = Engine.view;
983
983
  view.addEventListener("pointermove", this.onPointerMove);
984
984
  view.addEventListener("pointerdown", this.onPointerDown);
985
985
  view.addEventListener("pointerup", this.onPointerUp);
986
986
  view.addEventListener("pointerleave", this.onPointerUp);
987
987
  }
988
988
  getCanvasCoords(event) {
989
- const view = Game.instance.view;
989
+ const view = Engine.view;
990
990
  const rect = view.getBoundingClientRect();
991
- const scaleX = Game.instance.screen.width / rect.width;
992
- const scaleY = Game.instance.screen.height / rect.height;
991
+ const scaleX = Engine.screen.width / rect.width;
992
+ const scaleY = Engine.screen.height / rect.height;
993
993
  return {
994
994
  x: (event.clientX - rect.left) * scaleX,
995
995
  y: (event.clientY - rect.top) * scaleY
@@ -1047,7 +1047,7 @@ var PointerManager = class {
1047
1047
  }
1048
1048
  /** Clean up event listeners */
1049
1049
  destroy() {
1050
- const view = Game.instance.view;
1050
+ const view = Engine.view;
1051
1051
  view.removeEventListener("pointermove", this.onPointerMove);
1052
1052
  view.removeEventListener("pointerdown", this.onPointerDown);
1053
1053
  view.removeEventListener("pointerup", this.onPointerUp);
@@ -1285,13 +1285,13 @@ var SceneManager = class {
1285
1285
  }
1286
1286
  if (this.currentScene) {
1287
1287
  this.currentScene.onExit();
1288
- Game.instance.stage.removeChild(this.currentScene);
1288
+ Engine.stage.removeChild(this.currentScene);
1289
1289
  this.currentScene.destroy({ children: true });
1290
1290
  }
1291
1291
  const scene = new SceneClass();
1292
1292
  this.currentScene = scene;
1293
1293
  this.currentSceneName = name;
1294
- Game.instance.stage.addChild(scene);
1294
+ Engine.stage.addChild(scene);
1295
1295
  await scene.onEnter();
1296
1296
  }
1297
1297
  /** Get current scene name */
@@ -1856,13 +1856,13 @@ var ToastManager = class {
1856
1856
  currentColors;
1857
1857
  currentThemeName;
1858
1858
  resizeBinding = null;
1859
- /** Get screen width from Game singleton */
1859
+ /** Get screen width from Engine */
1860
1860
  get screenWidth() {
1861
- return Game.instance.screen.width;
1861
+ return Engine.screen.width;
1862
1862
  }
1863
- /** Get screen height from Game singleton */
1863
+ /** Get screen height from Engine */
1864
1864
  get screenHeight() {
1865
- return Game.instance.screen.height;
1865
+ return Engine.screen.height;
1866
1866
  }
1867
1867
  constructor(stage, config) {
1868
1868
  if (config?.theme) {
@@ -1882,7 +1882,7 @@ var ToastManager = class {
1882
1882
  this.layer.sortableChildren = true;
1883
1883
  this.layer.zIndex = 9999;
1884
1884
  stage.addChild(this.layer);
1885
- this.resizeBinding = Game.instance.onResize.add(() => {
1885
+ this.resizeBinding = Engine.onResize.add(() => {
1886
1886
  this.repositionToasts();
1887
1887
  });
1888
1888
  }
@@ -2350,135 +2350,198 @@ var UIManager = class {
2350
2350
  }
2351
2351
  };
2352
2352
 
2353
- // src/core/game.ts
2354
- var Game = class _Game {
2355
- static _instance = null;
2356
- /** Get the singleton Game instance. Throws if not initialized. */
2357
- static get instance() {
2358
- if (!_Game._instance) {
2359
- throw new Error("Game not initialized. Call Game.init() first.");
2360
- }
2361
- return _Game._instance;
2362
- }
2363
- /** Initialize the Game singleton. Throws if already initialized. */
2364
- static init(config) {
2365
- if (_Game._instance) {
2366
- throw new Error("Game already initialized. Call destroy() first to reinitialize.");
2367
- }
2368
- return new _Game(config);
2353
+ // src/core/engine.ts
2354
+ var Engine = class _Engine {
2355
+ constructor() {
2369
2356
  }
2370
- app;
2371
- scenes;
2372
- input;
2373
- assets;
2374
- sound;
2375
- particles;
2376
- tween;
2377
- ui;
2378
- spine;
2379
- toast;
2380
- alert;
2381
- modal;
2382
- /** Logger instance for consistent logging across the game */
2383
- logger = Logger;
2384
- /** Signal emitted when game is paused */
2385
- onPause = new Signal();
2386
- /** Signal emitted when game is resumed */
2387
- onResume = new Signal();
2388
- /** Signal emitted when game is resized */
2389
- onResize = new Signal();
2390
- /** Signal emitted when game is updated */
2391
- onUpdate = new Signal();
2357
+ static _app;
2358
+ static _scenes;
2359
+ static _input;
2360
+ static _assets;
2361
+ static _sound;
2362
+ static _particles;
2363
+ static _tween;
2364
+ static _ui;
2365
+ static _spine;
2366
+ static _toast;
2367
+ static _alert;
2368
+ static _modal;
2369
+ /** Signal emitted when engine is paused */
2370
+ static onPause = new Signal();
2371
+ /** Signal emitted when engine is resumed */
2372
+ static onResume = new Signal();
2373
+ /** Signal emitted when engine is resized */
2374
+ static onResize = new Signal();
2375
+ /** Signal emitted when engine is updated */
2376
+ static onUpdate = new Signal();
2377
+ /** Signal emitted at the end of init() after all managers are constructed */
2378
+ static onReady = new Signal();
2379
+ /** Signal emitted at the start of destroy() before any teardown */
2380
+ static onDestroy = new Signal();
2381
+ /** Signal emitted when browser tab visibility changes (true = visible, false = hidden) */
2382
+ static onVisibilityChange = new Signal();
2383
+ /** Signal emitted when browser window gains/loses focus (true = focused, false = blurred) */
2384
+ static onFocusChange = new Signal();
2392
2385
  /** Design resolution width (original config width) */
2393
- designWidth;
2386
+ static _designWidth;
2394
2387
  /** Design resolution height (original config height) */
2395
- designHeight;
2388
+ static _designHeight;
2396
2389
  /** Current scale mode */
2397
- scaleMode;
2390
+ static _scaleMode;
2398
2391
  /** Current scale factor */
2399
- _scale = 1;
2400
- _isRunning = false;
2401
- _resizeHandler;
2402
- constructor(config) {
2403
- _Game._instance = this;
2404
- this.designWidth = config.width;
2405
- this.designHeight = config.height;
2406
- this.scaleMode = config.scaleMode ?? "letterbox";
2407
- this.app = new PIXI10.Application({
2408
- width: config.width,
2409
- height: config.height,
2410
- backgroundColor: config.backgroundColor ?? 0,
2411
- antialias: config.antialias ?? true,
2412
- resolution: config.resolution ?? window.devicePixelRatio,
2413
- autoDensity: config.autoDensity ?? true
2414
- });
2415
- this.app.stage.sortableChildren = true;
2416
- this.scenes = new SceneManager();
2417
- this.input = new InputManager();
2418
- this.assets = new AssetManager();
2419
- this.sound = new SoundManager();
2420
- this.particles = new ParticleManager();
2421
- this.tween = new TweenManager();
2422
- this.ui = new UIManager();
2423
- this.spine = new SpineManager();
2424
- this.toast = new ToastManager(this.app.stage);
2425
- this.alert = new AlertManager(this.app.stage);
2426
- this.modal = new ModalManager(this.app.stage);
2427
- this._resizeHandler = () => this.resizeToWindow();
2428
- if (config.autoResize !== false) {
2429
- window.addEventListener("resize", this._resizeHandler);
2430
- }
2431
- this.app.ticker.add(this.update, this);
2432
- this._isRunning = true;
2392
+ static _scale = 1;
2393
+ static _isRunning = false;
2394
+ static _resizeHandler;
2395
+ static _visibilityHandler;
2396
+ static _focusHandler;
2397
+ static _blurHandler;
2398
+ static _initialized = false;
2399
+ /** Logger instance for consistent logging across the engine */
2400
+ static logger = Logger;
2401
+ static get scenes() {
2402
+ return _Engine._scenes;
2433
2403
  }
2434
- update(delta) {
2435
- this.onUpdate.emit(delta);
2436
- this.input.update();
2437
- this.scenes.update(delta);
2438
- this.particles.update(delta);
2439
- this.input.postUpdate();
2404
+ static get input() {
2405
+ return _Engine._input;
2406
+ }
2407
+ static get assets() {
2408
+ return _Engine._assets;
2409
+ }
2410
+ static get sound() {
2411
+ return _Engine._sound;
2412
+ }
2413
+ static get particles() {
2414
+ return _Engine._particles;
2415
+ }
2416
+ static get tween() {
2417
+ return _Engine._tween;
2418
+ }
2419
+ static get ui() {
2420
+ return _Engine._ui;
2421
+ }
2422
+ static get spine() {
2423
+ return _Engine._spine;
2424
+ }
2425
+ static get toast() {
2426
+ return _Engine._toast;
2440
2427
  }
2441
- get view() {
2442
- return this.app.view;
2428
+ static get alert() {
2429
+ return _Engine._alert;
2443
2430
  }
2444
- get stage() {
2445
- return this.app.stage;
2431
+ static get modal() {
2432
+ return _Engine._modal;
2433
+ }
2434
+ static get app() {
2435
+ return _Engine._app;
2436
+ }
2437
+ static get view() {
2438
+ return _Engine._app.view;
2439
+ }
2440
+ static get stage() {
2441
+ return _Engine._app.stage;
2446
2442
  }
2447
2443
  /** Returns design resolution dimensions (use for positioning game elements) */
2448
- get screen() {
2449
- return new PIXI10.Rectangle(0, 0, this.designWidth, this.designHeight);
2444
+ static get screen() {
2445
+ return new PIXI10.Rectangle(0, 0, _Engine._designWidth, _Engine._designHeight);
2450
2446
  }
2451
2447
  /** Returns actual renderer/viewport dimensions */
2452
- get viewport() {
2453
- return this.app.screen;
2448
+ static get viewport() {
2449
+ return _Engine._app.screen;
2454
2450
  }
2455
- get isRunning() {
2456
- return this._isRunning;
2451
+ static get isRunning() {
2452
+ return _Engine._isRunning;
2457
2453
  }
2458
- pause() {
2459
- this.app.ticker.stop();
2460
- this._isRunning = false;
2461
- this.onPause.emit();
2454
+ /** Design resolution width (original config width) */
2455
+ static get designWidth() {
2456
+ return _Engine._designWidth;
2462
2457
  }
2463
- resume() {
2464
- this.app.ticker.start();
2465
- this._isRunning = true;
2466
- this.onResume.emit();
2458
+ /** Design resolution height (original config height) */
2459
+ static get designHeight() {
2460
+ return _Engine._designHeight;
2461
+ }
2462
+ /** Current scale mode */
2463
+ static get scaleMode() {
2464
+ return _Engine._scaleMode;
2465
+ }
2466
+ static set scaleMode(value) {
2467
+ _Engine._scaleMode = value;
2467
2468
  }
2468
2469
  /** Get current scale factor */
2469
- get scale() {
2470
- return this._scale;
2470
+ static get scale() {
2471
+ return _Engine._scale;
2472
+ }
2473
+ /** Initialize the Engine. Throws if already initialized. */
2474
+ static init(config) {
2475
+ if (_Engine._initialized) {
2476
+ throw new Error("Engine already initialized. Call destroy() first to reinitialize.");
2477
+ }
2478
+ _Engine._initialized = true;
2479
+ _Engine._designWidth = config.width;
2480
+ _Engine._designHeight = config.height;
2481
+ _Engine._scaleMode = config.scaleMode ?? "letterbox";
2482
+ _Engine._app = new PIXI10.Application({
2483
+ width: config.width,
2484
+ height: config.height,
2485
+ backgroundColor: config.backgroundColor ?? 0,
2486
+ antialias: config.antialias ?? true,
2487
+ resolution: config.resolution ?? window.devicePixelRatio,
2488
+ autoDensity: config.autoDensity ?? true
2489
+ });
2490
+ _Engine._app.stage.sortableChildren = true;
2491
+ _Engine._scenes = new SceneManager();
2492
+ _Engine._input = new InputManager();
2493
+ _Engine._assets = new AssetManager();
2494
+ _Engine._sound = new SoundManager();
2495
+ _Engine._particles = new ParticleManager();
2496
+ _Engine._tween = new TweenManager();
2497
+ _Engine._ui = new UIManager();
2498
+ _Engine._spine = new SpineManager();
2499
+ _Engine._toast = new ToastManager(_Engine._app.stage);
2500
+ _Engine._alert = new AlertManager(_Engine._app.stage);
2501
+ _Engine._modal = new ModalManager(_Engine._app.stage);
2502
+ _Engine._resizeHandler = () => _Engine.resizeToWindow();
2503
+ if (config.autoResize !== false) {
2504
+ window.addEventListener("resize", _Engine._resizeHandler);
2505
+ }
2506
+ _Engine._app.ticker.add(_Engine.update);
2507
+ _Engine._isRunning = true;
2508
+ _Engine._visibilityHandler = () => {
2509
+ _Engine.onVisibilityChange.emit(!document.hidden);
2510
+ };
2511
+ _Engine._focusHandler = () => _Engine.onFocusChange.emit(true);
2512
+ _Engine._blurHandler = () => _Engine.onFocusChange.emit(false);
2513
+ document.addEventListener("visibilitychange", _Engine._visibilityHandler);
2514
+ window.addEventListener("focus", _Engine._focusHandler);
2515
+ window.addEventListener("blur", _Engine._blurHandler);
2516
+ _Engine.onReady.emit();
2517
+ }
2518
+ static update(delta) {
2519
+ _Engine.onUpdate.emit(delta);
2520
+ _Engine._input.update();
2521
+ _Engine._scenes.update(delta);
2522
+ _Engine._particles.update(delta);
2523
+ _Engine._input.postUpdate();
2524
+ }
2525
+ static pause() {
2526
+ _Engine._app.ticker.stop();
2527
+ _Engine._isRunning = false;
2528
+ _Engine.onPause.emit();
2529
+ }
2530
+ static resume() {
2531
+ _Engine._app.ticker.start();
2532
+ _Engine._isRunning = true;
2533
+ _Engine.onResume.emit();
2471
2534
  }
2472
2535
  /**
2473
2536
  * Get bounds in stage coordinates that cover the full viewport (including letterbox areas).
2474
2537
  * Use this for fullscreen overlays, backdrops, modals that need to cover the entire screen.
2475
2538
  */
2476
- getFullscreenBounds() {
2477
- const scale = this._scale || 1;
2478
- const stagePos = this.app.stage.position;
2479
- const resolution = this.app.renderer.resolution || 1;
2480
- const rendererWidth = this.app.renderer.width / resolution;
2481
- const rendererHeight = this.app.renderer.height / resolution;
2539
+ static getFullscreenBounds() {
2540
+ const scale = _Engine._scale || 1;
2541
+ const stagePos = _Engine._app.stage.position;
2542
+ const resolution = _Engine._app.renderer.resolution || 1;
2543
+ const rendererWidth = _Engine._app.renderer.width / resolution;
2544
+ const rendererHeight = _Engine._app.renderer.height / resolution;
2482
2545
  return {
2483
2546
  x: -stagePos.x / scale,
2484
2547
  y: -stagePos.y / scale,
@@ -2489,75 +2552,76 @@ var Game = class _Game {
2489
2552
  /**
2490
2553
  * Resize to fit window while maintaining aspect ratio based on scaleMode
2491
2554
  */
2492
- resizeToWindow() {
2493
- const parent = this.app.view.parentElement;
2555
+ static resizeToWindow() {
2556
+ const parent = _Engine._app.view.parentElement;
2494
2557
  const windowWidth = parent?.clientWidth ?? window.innerWidth;
2495
2558
  const windowHeight = parent?.clientHeight ?? window.innerHeight;
2496
- this.resize(windowWidth, windowHeight);
2559
+ _Engine.resize(windowWidth, windowHeight);
2497
2560
  }
2498
2561
  /**
2499
- * Resize game to fit target dimensions while maintaining aspect ratio
2562
+ * Resize engine to fit target dimensions while maintaining aspect ratio
2500
2563
  */
2501
- resize(targetWidth, targetHeight) {
2502
- if (this.scaleMode === "none") {
2503
- this.app.renderer.resize(targetWidth, targetHeight);
2504
- this._scale = 1;
2505
- this.app.stage.scale.set(1);
2506
- this.app.stage.position.set(0, 0);
2507
- this.propagateResize(targetWidth, targetHeight);
2508
- this.onResize.emit({ width: targetWidth, height: targetHeight, scale: 1 });
2564
+ static resize(targetWidth, targetHeight) {
2565
+ if (_Engine._scaleMode === "none") {
2566
+ _Engine._app.renderer.resize(targetWidth, targetHeight);
2567
+ _Engine._scale = 1;
2568
+ _Engine._app.stage.scale.set(1);
2569
+ _Engine._app.stage.position.set(0, 0);
2570
+ _Engine.onResize.emit({ width: targetWidth, height: targetHeight, scale: 1 });
2509
2571
  return;
2510
2572
  }
2511
- const designRatio = this.designWidth / this.designHeight;
2573
+ const designRatio = _Engine._designWidth / _Engine._designHeight;
2512
2574
  const targetRatio = targetWidth / targetHeight;
2513
2575
  let scale;
2514
- if (this.scaleMode === "letterbox") {
2515
- scale = targetRatio > designRatio ? targetHeight / this.designHeight : targetWidth / this.designWidth;
2576
+ if (_Engine._scaleMode === "letterbox") {
2577
+ scale = targetRatio > designRatio ? targetHeight / _Engine._designHeight : targetWidth / _Engine._designWidth;
2516
2578
  } else {
2517
- scale = targetRatio > designRatio ? targetWidth / this.designWidth : targetHeight / this.designHeight;
2518
- }
2519
- this._scale = scale;
2520
- this.app.renderer.resize(targetWidth, targetHeight);
2521
- this.app.stage.scale.set(scale);
2522
- this.app.stage.position.set(
2523
- (targetWidth - this.designWidth * scale) / 2,
2524
- (targetHeight - this.designHeight * scale) / 2
2579
+ scale = targetRatio > designRatio ? targetWidth / _Engine._designWidth : targetHeight / _Engine._designHeight;
2580
+ }
2581
+ _Engine._scale = scale;
2582
+ _Engine._app.renderer.resize(targetWidth, targetHeight);
2583
+ _Engine._app.stage.scale.set(scale);
2584
+ _Engine._app.stage.position.set(
2585
+ (targetWidth - _Engine._designWidth * scale) / 2,
2586
+ (targetHeight - _Engine._designHeight * scale) / 2
2525
2587
  );
2526
- this.propagateResize(targetWidth, targetHeight);
2527
- this.onResize.emit({ width: targetWidth, height: targetHeight, scale });
2528
- }
2529
- /**
2530
- * Propagate resize to all managers that need viewport dimensions
2531
- * Note: Toast/BaseModal auto-resize via Game.instance.onResize signal
2532
- */
2533
- propagateResize(_width, _height) {
2534
- }
2535
- destroy() {
2536
- window.removeEventListener("resize", this._resizeHandler);
2537
- this.app.ticker.remove(this.update, this);
2538
- this.input.destroy();
2539
- this.scenes.destroy();
2540
- this.sound.destroy();
2541
- this.particles.destroy();
2542
- this.tween.destroy();
2543
- this.toast.destroy();
2544
- this.alert.destroy();
2545
- this.modal.destroy();
2546
- this.onPause.clear();
2547
- this.onResume.clear();
2548
- this.onResize.clear();
2549
- this.app.destroy(true, { children: true, texture: true });
2550
- this._isRunning = false;
2551
- _Game._instance = null;
2588
+ _Engine.onResize.emit({ width: targetWidth, height: targetHeight, scale });
2589
+ }
2590
+ static destroy() {
2591
+ if (!_Engine._initialized) return;
2592
+ _Engine.onDestroy.emit();
2593
+ document.removeEventListener("visibilitychange", _Engine._visibilityHandler);
2594
+ window.removeEventListener("focus", _Engine._focusHandler);
2595
+ window.removeEventListener("blur", _Engine._blurHandler);
2596
+ window.removeEventListener("resize", _Engine._resizeHandler);
2597
+ _Engine._app.ticker.remove(_Engine.update);
2598
+ _Engine._input.destroy();
2599
+ _Engine._scenes.destroy();
2600
+ _Engine._sound.destroy();
2601
+ _Engine._particles.destroy();
2602
+ _Engine._tween.destroy();
2603
+ _Engine._toast.destroy();
2604
+ _Engine._alert.destroy();
2605
+ _Engine._modal.destroy();
2606
+ _Engine.onPause.clear();
2607
+ _Engine.onResume.clear();
2608
+ _Engine.onResize.clear();
2609
+ _Engine.onReady.clear();
2610
+ _Engine.onDestroy.clear();
2611
+ _Engine.onVisibilityChange.clear();
2612
+ _Engine.onFocusChange.clear();
2613
+ _Engine._app.destroy(true, { children: true, texture: true });
2614
+ _Engine._isRunning = false;
2615
+ _Engine._initialized = false;
2552
2616
  }
2553
2617
  };
2554
2618
 
2555
2619
  // src/scenes/scene.ts
2556
2620
  var PIXI11 = __toESM(require("pixi.js"), 1);
2557
2621
  var Scene = class extends PIXI11.Container {
2558
- /** Get game instance */
2559
- get game() {
2560
- return Game.instance;
2622
+ /** Get engine class for static access */
2623
+ get engine() {
2624
+ return Engine;
2561
2625
  }
2562
2626
  /** Called when scene is paused (another scene pushed on top) */
2563
2627
  onPause() {
@@ -2785,7 +2849,7 @@ var import_pixi_spine3 = require("pixi-spine");
2785
2849
  BaseModal,
2786
2850
  COUNT_PRESETS,
2787
2851
  CountAnimator,
2788
- Game,
2852
+ Engine,
2789
2853
  GamepadManager,
2790
2854
  InputManager,
2791
2855
  KeyboardManager,