kiwiengine 0.0.1-alpha

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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -0
  3. package/assets/logo.png +0 -0
  4. package/examples/package.json +13 -0
  5. package/examples/test-dom/index.html +24 -0
  6. package/examples/test-dom/index.ts +21 -0
  7. package/examples/tsconfig.json +22 -0
  8. package/examples/webpack.config.js +31 -0
  9. package/lib/asset/audio.js +158 -0
  10. package/lib/asset/audio.js.map +1 -0
  11. package/lib/asset/loaders/audio.js +35 -0
  12. package/lib/asset/loaders/audio.js.map +1 -0
  13. package/lib/asset/loaders/binary.js +28 -0
  14. package/lib/asset/loaders/binary.js.map +1 -0
  15. package/lib/asset/loaders/font.js +27 -0
  16. package/lib/asset/loaders/font.js.map +1 -0
  17. package/lib/asset/loaders/loader.js +37 -0
  18. package/lib/asset/loaders/loader.js.map +1 -0
  19. package/lib/asset/loaders/spritesheet.js +56 -0
  20. package/lib/asset/loaders/spritesheet.js.map +1 -0
  21. package/lib/asset/loaders/text.js +27 -0
  22. package/lib/asset/loaders/text.js.map +1 -0
  23. package/lib/asset/loaders/texture.js +38 -0
  24. package/lib/asset/loaders/texture.js.map +1 -0
  25. package/lib/asset/preload.js +69 -0
  26. package/lib/asset/preload.js.map +1 -0
  27. package/lib/game-object/game-object-physics.js +188 -0
  28. package/lib/game-object/game-object-physics.js.map +1 -0
  29. package/lib/game-object/game-object-rendering.js +35 -0
  30. package/lib/game-object/game-object-rendering.js.map +1 -0
  31. package/lib/game-object/game-object.js +162 -0
  32. package/lib/game-object/game-object.js.map +1 -0
  33. package/lib/game-object/transform.js +118 -0
  34. package/lib/game-object/transform.js.map +1 -0
  35. package/lib/game-object-ext/animated-sprite.js +117 -0
  36. package/lib/game-object-ext/animated-sprite.js.map +1 -0
  37. package/lib/game-object-ext/dom-container.js +56 -0
  38. package/lib/game-object-ext/dom-container.js.map +1 -0
  39. package/lib/game-object-ext/rect.js +30 -0
  40. package/lib/game-object-ext/rect.js.map +1 -0
  41. package/lib/game-object-ext/spine.js +206 -0
  42. package/lib/game-object-ext/spine.js.map +1 -0
  43. package/lib/game-object-ext/sprite.js +46 -0
  44. package/lib/game-object-ext/sprite.js.map +1 -0
  45. package/lib/game-object-ext/text.js +68 -0
  46. package/lib/game-object-ext/text.js.map +1 -0
  47. package/lib/game-object-ext/tiling-sprite.js +64 -0
  48. package/lib/game-object-ext/tiling-sprite.js.map +1 -0
  49. package/lib/index.js +13 -0
  50. package/lib/index.js.map +1 -0
  51. package/lib/types/asset/audio.d.ts +21 -0
  52. package/lib/types/asset/audio.d.ts.map +1 -0
  53. package/lib/types/asset/loaders/audio.d.ts +7 -0
  54. package/lib/types/asset/loaders/audio.d.ts.map +1 -0
  55. package/lib/types/asset/loaders/binary.d.ts +7 -0
  56. package/lib/types/asset/loaders/binary.d.ts.map +1 -0
  57. package/lib/types/asset/loaders/font.d.ts +7 -0
  58. package/lib/types/asset/loaders/font.d.ts.map +1 -0
  59. package/lib/types/asset/loaders/loader.d.ts +13 -0
  60. package/lib/types/asset/loaders/loader.d.ts.map +1 -0
  61. package/lib/types/asset/loaders/spritesheet.d.ts +11 -0
  62. package/lib/types/asset/loaders/spritesheet.d.ts.map +1 -0
  63. package/lib/types/asset/loaders/text.d.ts +7 -0
  64. package/lib/types/asset/loaders/text.d.ts.map +1 -0
  65. package/lib/types/asset/loaders/texture.d.ts +9 -0
  66. package/lib/types/asset/loaders/texture.d.ts.map +1 -0
  67. package/lib/types/asset/preload.d.ts +8 -0
  68. package/lib/types/asset/preload.d.ts.map +1 -0
  69. package/lib/types/game-object/game-object-physics.d.ts +42 -0
  70. package/lib/types/game-object/game-object-physics.d.ts.map +1 -0
  71. package/lib/types/game-object/game-object-rendering.d.ts +15 -0
  72. package/lib/types/game-object/game-object-rendering.d.ts.map +1 -0
  73. package/lib/types/game-object/game-object.d.ts +81 -0
  74. package/lib/types/game-object/game-object.d.ts.map +1 -0
  75. package/lib/types/game-object/transform.d.ts +43 -0
  76. package/lib/types/game-object/transform.d.ts.map +1 -0
  77. package/lib/types/game-object-ext/animated-sprite.d.ts +29 -0
  78. package/lib/types/game-object-ext/animated-sprite.d.ts.map +1 -0
  79. package/lib/types/game-object-ext/dom-container.d.ts +16 -0
  80. package/lib/types/game-object-ext/dom-container.d.ts.map +1 -0
  81. package/lib/types/game-object-ext/rect.d.ts +17 -0
  82. package/lib/types/game-object-ext/rect.d.ts.map +1 -0
  83. package/lib/types/game-object-ext/spine.d.ts +35 -0
  84. package/lib/types/game-object-ext/spine.d.ts.map +1 -0
  85. package/lib/types/game-object-ext/sprite.d.ts +14 -0
  86. package/lib/types/game-object-ext/sprite.d.ts.map +1 -0
  87. package/lib/types/game-object-ext/text.d.ts +26 -0
  88. package/lib/types/game-object-ext/text.d.ts.map +1 -0
  89. package/lib/types/game-object-ext/tiling-sprite.d.ts +20 -0
  90. package/lib/types/game-object-ext/tiling-sprite.d.ts.map +1 -0
  91. package/lib/types/index.d.ts +14 -0
  92. package/lib/types/index.d.ts.map +1 -0
  93. package/lib/types/utils/debug.d.ts +3 -0
  94. package/lib/types/utils/debug.d.ts.map +1 -0
  95. package/lib/types/utils/go.d.ts +26 -0
  96. package/lib/types/utils/go.d.ts.map +1 -0
  97. package/lib/types/world/world-debug.d.ts +11 -0
  98. package/lib/types/world/world-debug.d.ts.map +1 -0
  99. package/lib/types/world/world-physics.d.ts +16 -0
  100. package/lib/types/world/world-physics.d.ts.map +1 -0
  101. package/lib/types/world/world-rendering.d.ts +28 -0
  102. package/lib/types/world/world-rendering.d.ts.map +1 -0
  103. package/lib/types/world/world.d.ts +38 -0
  104. package/lib/types/world/world.d.ts.map +1 -0
  105. package/lib/utils/debug.js +5 -0
  106. package/lib/utils/debug.js.map +1 -0
  107. package/lib/utils/go.js +33 -0
  108. package/lib/utils/go.js.map +1 -0
  109. package/lib/world/world-debug.js +89 -0
  110. package/lib/world/world-debug.js.map +1 -0
  111. package/lib/world/world-physics.js +45 -0
  112. package/lib/world/world-physics.js.map +1 -0
  113. package/lib/world/world-rendering.js +123 -0
  114. package/lib/world/world-rendering.js.map +1 -0
  115. package/lib/world/world.js +147 -0
  116. package/lib/world/world.js.map +1 -0
  117. package/package.json +23 -0
  118. package/src/asset/audio.ts +176 -0
  119. package/src/asset/loaders/audio.ts +39 -0
  120. package/src/asset/loaders/binary.ts +32 -0
  121. package/src/asset/loaders/font.ts +27 -0
  122. package/src/asset/loaders/loader.ts +39 -0
  123. package/src/asset/loaders/spritesheet.ts +67 -0
  124. package/src/asset/loaders/text.ts +31 -0
  125. package/src/asset/loaders/texture.ts +46 -0
  126. package/src/asset/preload.ts +76 -0
  127. package/src/game-object/game-object-physics.ts +191 -0
  128. package/src/game-object/game-object-rendering.ts +27 -0
  129. package/src/game-object/game-object.ts +190 -0
  130. package/src/game-object/transform.ts +164 -0
  131. package/src/game-object-ext/animated-sprite.ts +140 -0
  132. package/src/game-object-ext/dom-container.ts +67 -0
  133. package/src/game-object-ext/rect.ts +40 -0
  134. package/src/game-object-ext/spine.ts +235 -0
  135. package/src/game-object-ext/sprite.ts +55 -0
  136. package/src/game-object-ext/text.ts +83 -0
  137. package/src/game-object-ext/tiling-sprite.ts +73 -0
  138. package/src/index.ts +14 -0
  139. package/src/utils/debug.ts +5 -0
  140. package/src/utils/go.ts +53 -0
  141. package/src/world/world-debug.ts +114 -0
  142. package/src/world/world-physics.ts +52 -0
  143. package/src/world/world-rendering.ts +145 -0
  144. package/src/world/world.ts +171 -0
  145. package/tsconfig.json +33 -0
@@ -0,0 +1,147 @@
1
+ import { GameObject } from '../game-object/game-object';
2
+ import { WorldTransform } from '../game-object/transform';
3
+ import { debugMode } from '../utils/debug';
4
+ import { WorldDebug } from './world-debug';
5
+ import { WorldPhysics } from "./world-physics";
6
+ import { WorldRendering } from "./world-rendering";
7
+ export class World extends GameObject {
8
+ container = document.createElement('div');
9
+ #containerResizeObserver;
10
+ _worldRendering = new WorldRendering();
11
+ _worldPhysics = new WorldPhysics();
12
+ #worldDebug = new WorldDebug(this.container);
13
+ #width;
14
+ #height;
15
+ #hasEverBeenConnected = false;
16
+ #destroyed = false;
17
+ #pt = new WorldTransform();
18
+ #update(dt) {
19
+ if (this.container.isConnected) {
20
+ if (!this.#hasEverBeenConnected) {
21
+ this.#hasEverBeenConnected = true;
22
+ this.#applySize();
23
+ }
24
+ this._worldPhysics.update(dt);
25
+ this._engineUpdate(dt, this.#pt);
26
+ this._worldRendering.update();
27
+ this.#worldDebug.update();
28
+ this._containerSizeDirty = false;
29
+ }
30
+ else if (this.#hasEverBeenConnected) {
31
+ this.#destroy();
32
+ return;
33
+ }
34
+ }
35
+ #lastContainerW = 0;
36
+ #lastContainerH = 0;
37
+ _containerSizeDirty = false;
38
+ #applySize() {
39
+ const rect = this.container.getBoundingClientRect();
40
+ if (rect.width === this.#lastContainerW && rect.height === this.#lastContainerH)
41
+ return;
42
+ this.#lastContainerW = rect.width;
43
+ this.#lastContainerH = rect.height;
44
+ this._containerSizeDirty = true;
45
+ if (rect.width === 0 || rect.height === 0)
46
+ return;
47
+ const canvasWidth = this.#width ?? rect.width;
48
+ const canvasHeight = this.#height ?? rect.height;
49
+ this._worldRendering.setRendererSize(rect, canvasWidth, canvasHeight);
50
+ this.#worldDebug.setMatterDebugRendererSize(rect, canvasWidth, canvasHeight, this.cameraX, this.cameraY);
51
+ this.emit('resize', this.width, this.height);
52
+ }
53
+ #destroy() {
54
+ this.#containerResizeObserver.disconnect();
55
+ this._worldRendering.destroy();
56
+ this._worldPhysics.destroy();
57
+ this.#worldDebug.destroy();
58
+ this.#destroyed = true;
59
+ }
60
+ async #init() {
61
+ await this._worldRendering.init(this.container, this.#width, this.#height);
62
+ this.#applySize();
63
+ let prevTime = 0;
64
+ let lagSeconds = 0;
65
+ let fpsCap;
66
+ const step = (timestamp) => {
67
+ if (this.#destroyed)
68
+ return;
69
+ const dt = (timestamp - prevTime) / 1000;
70
+ if (dt > 0) {
71
+ if (fpsCap !== undefined && fpsCap > 0) {
72
+ lagSeconds += dt;
73
+ const fixedStep = 1 / fpsCap;
74
+ if (lagSeconds >= fixedStep) {
75
+ this.#update(fixedStep);
76
+ if (lagSeconds >= fixedStep * 2) {
77
+ this.#update(dt);
78
+ lagSeconds = 0;
79
+ }
80
+ else {
81
+ lagSeconds -= fixedStep;
82
+ }
83
+ }
84
+ }
85
+ else {
86
+ this.#update(dt);
87
+ }
88
+ prevTime = timestamp;
89
+ }
90
+ requestAnimationFrame(step);
91
+ };
92
+ requestAnimationFrame(step);
93
+ if (debugMode) {
94
+ if (!document.hasFocus())
95
+ fpsCap = 6;
96
+ window.addEventListener('blur', () => fpsCap = 6);
97
+ window.addEventListener('focus', () => fpsCap = undefined);
98
+ window.addEventListener('pageshow', (event) => { if (event.persisted)
99
+ fpsCap = undefined; });
100
+ }
101
+ }
102
+ constructor(opts) {
103
+ super(opts);
104
+ this._setWorld(this);
105
+ this._worldRendering.addPixiChildToRoot(this._rendering._container);
106
+ this.#containerResizeObserver = new ResizeObserver(this.#applySize.bind(this));
107
+ this.#containerResizeObserver.observe(this.container);
108
+ this._worldRendering.on('positionChanged', () => this.#worldDebug.setMatterDebugRendererCamera(this.cameraX, this.cameraY));
109
+ this._worldPhysics.on('engineCreated', (engine) => this.#worldDebug.createMatterDebugRenderer(engine, this.width, this.height));
110
+ this._worldPhysics.on('collisionStart', (a, b) => this.emit('collisionStart', a, b));
111
+ ``;
112
+ if (opts) {
113
+ if (opts.width !== undefined)
114
+ this.#width = opts.width;
115
+ if (opts.height !== undefined)
116
+ this.#height = opts.height;
117
+ if (opts.backgroundColor !== undefined)
118
+ this.backgroundColor = opts.backgroundColor;
119
+ if (opts.backgroundAlpha !== undefined)
120
+ this.backgroundAlpha = opts.backgroundAlpha;
121
+ if (opts.gravity !== undefined)
122
+ this.gravity = opts.gravity;
123
+ }
124
+ this.#init();
125
+ }
126
+ get width() { return this.#width ?? this._worldRendering.renderWidth; }
127
+ set width(v) { this.#width = v; this.#applySize(); }
128
+ get height() { return this.#height ?? this._worldRendering.renderHeight; }
129
+ set height(v) { this.#height = v; this.#applySize(); }
130
+ get backgroundColor() { return this._worldRendering.backgroundColor; }
131
+ set backgroundColor(v) { this._worldRendering.backgroundColor = v; }
132
+ get backgroundAlpha() { return this._worldRendering.backgroundAlpha; }
133
+ set backgroundAlpha(v) { this._worldRendering.backgroundAlpha = v; }
134
+ get gravity() { return this._worldPhysics.gravity; }
135
+ set gravity(v) { this._worldPhysics.gravity = v; }
136
+ get cameraX() { return this._worldRendering.cameraX; }
137
+ set cameraX(v) { this._worldRendering.cameraX = v; }
138
+ get cameraY() { return this._worldRendering.cameraY; }
139
+ set cameraY(v) { this._worldRendering.cameraY = v; }
140
+ #backgroundImage;
141
+ get backgroundImage() { return this.#backgroundImage; }
142
+ set backgroundImage(image) {
143
+ this.#backgroundImage = image;
144
+ this._worldRendering.setBackgroundImage(image);
145
+ }
146
+ }
147
+ //# sourceMappingURL=world.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world.js","sourceRoot":"","sources":["../../src/world/world.ts"],"names":[],"mappings":"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","sourcesContent":["import { GameObject, GameObjectOptions } from '../game-object/game-object';\nimport { WorldTransform } from '../game-object/transform';\nimport { debugMode } from '../utils/debug';\nimport { WorldDebug } from './world-debug';\nimport { WorldPhysics } from \"./world-physics\";\nimport { WorldRendering } from \"./world-rendering\";\n\nexport type WorldOptions = {\n width?: number;\n height?: number;\n backgroundColor?: number;\n backgroundAlpha?: number;\n gravity?: number;\n} & GameObjectOptions;\n\nexport class World extends GameObject<{\n resize: (width: number, height: number) => void;\n collisionStart: (a: GameObject, b: GameObject) => void;\n}> {\n container = document.createElement('div');\n #containerResizeObserver: ResizeObserver;\n\n _worldRendering = new WorldRendering();\n _worldPhysics = new WorldPhysics();\n #worldDebug = new WorldDebug(this.container);\n\n #width?: number;\n #height?: number;\n\n #hasEverBeenConnected = false;\n #destroyed = false;\n\n #pt = new WorldTransform();\n #update(dt: number) {\n if (this.container.isConnected) {\n if (!this.#hasEverBeenConnected) {\n this.#hasEverBeenConnected = true;\n this.#applySize();\n }\n\n this._worldPhysics.update(dt);\n this._engineUpdate(dt, this.#pt);\n this._worldRendering.update();\n this.#worldDebug.update();\n\n this._containerSizeDirty = false;\n }\n\n else if (this.#hasEverBeenConnected) {\n this.#destroy();\n return;\n }\n }\n\n #lastContainerW = 0;\n #lastContainerH = 0;\n _containerSizeDirty = false;\n\n #applySize() {\n const rect = this.container.getBoundingClientRect();\n\n if (rect.width === this.#lastContainerW && rect.height === this.#lastContainerH) return;\n this.#lastContainerW = rect.width;\n this.#lastContainerH = rect.height;\n this._containerSizeDirty = true;\n\n if (rect.width === 0 || rect.height === 0) return;\n\n const canvasWidth = this.#width ?? rect.width;\n const canvasHeight = this.#height ?? rect.height;\n\n this._worldRendering.setRendererSize(rect, canvasWidth, canvasHeight);\n this.#worldDebug.setMatterDebugRendererSize(rect, canvasWidth, canvasHeight, this.cameraX, this.cameraY);\n\n this.emit('resize', this.width, this.height);\n }\n\n #destroy() {\n this.#containerResizeObserver.disconnect();\n this._worldRendering.destroy();\n this._worldPhysics.destroy();\n this.#worldDebug.destroy();\n\n this.#destroyed = true;\n }\n\n async #init() {\n await this._worldRendering.init(this.container, this.#width, this.#height);\n this.#applySize();\n\n let prevTime = 0;\n let lagSeconds = 0;\n let fpsCap: number | undefined;\n\n const step = (timestamp: number) => {\n if (this.#destroyed) return;\n\n const dt = (timestamp - prevTime) / 1000;\n if (dt > 0) {\n if (fpsCap !== undefined && fpsCap > 0) {\n lagSeconds += dt;\n const fixedStep = 1 / fpsCap;\n if (lagSeconds >= fixedStep) {\n this.#update(fixedStep);\n if (lagSeconds >= fixedStep * 2) { this.#update(dt); lagSeconds = 0; }\n else { lagSeconds -= fixedStep; }\n }\n } else {\n this.#update(dt);\n }\n prevTime = timestamp;\n }\n requestAnimationFrame(step);\n };\n requestAnimationFrame(step);\n\n if (debugMode) {\n if (!document.hasFocus()) fpsCap = 6;\n window.addEventListener('blur', () => fpsCap = 6);\n window.addEventListener('focus', () => fpsCap = undefined);\n window.addEventListener('pageshow', (event) => { if (event.persisted) fpsCap = undefined; });\n }\n }\n\n constructor(opts?: WorldOptions) {\n super(opts);\n this._setWorld(this);\n this._worldRendering.addPixiChildToRoot(this._rendering._container);\n\n this.#containerResizeObserver = new ResizeObserver(this.#applySize.bind(this));\n this.#containerResizeObserver.observe(this.container);\n\n this._worldRendering.on('positionChanged', () => this.#worldDebug.setMatterDebugRendererCamera(this.cameraX, this.cameraY));\n this._worldPhysics.on('engineCreated', (engine) => this.#worldDebug.createMatterDebugRenderer(engine, this.width, this.height));\n this._worldPhysics.on('collisionStart', (a, b) => this.emit('collisionStart', a, b));\n ``\n if (opts) {\n if (opts.width !== undefined) this.#width = opts.width;\n if (opts.height !== undefined) this.#height = opts.height;\n if (opts.backgroundColor !== undefined) this.backgroundColor = opts.backgroundColor;\n if (opts.backgroundAlpha !== undefined) this.backgroundAlpha = opts.backgroundAlpha;\n if (opts.gravity !== undefined) this.gravity = opts.gravity;\n }\n\n this.#init();\n }\n\n get width() { return this.#width ?? this._worldRendering.renderWidth; }\n set width(v: number) { this.#width = v; this.#applySize(); }\n get height() { return this.#height ?? this._worldRendering.renderHeight; }\n set height(v: number) { this.#height = v; this.#applySize(); }\n\n get backgroundColor() { return this._worldRendering.backgroundColor; }\n set backgroundColor(v: number) { this._worldRendering.backgroundColor = v; }\n get backgroundAlpha() { return this._worldRendering.backgroundAlpha; }\n set backgroundAlpha(v: number) { this._worldRendering.backgroundAlpha = v; }\n get gravity() { return this._worldPhysics.gravity; }\n set gravity(v: number) { this._worldPhysics.gravity = v; }\n\n get cameraX() { return this._worldRendering.cameraX; }\n set cameraX(v: number) { this._worldRendering.cameraX = v; }\n get cameraY() { return this._worldRendering.cameraY; }\n set cameraY(v: number) { this._worldRendering.cameraY = v; }\n\n #backgroundImage?: string;\n get backgroundImage() { return this.#backgroundImage; }\n set backgroundImage(image: string | undefined) {\n this.#backgroundImage = image;\n this._worldRendering.setBackgroundImage(image);\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "kiwiengine",
3
+ "version": "0.0.1-alpha",
4
+ "types": "./lib/types/index.d.ts",
5
+ "main": "./lib/index.js",
6
+ "devDependencies": {
7
+ "@types/jest": "^30.0.0",
8
+ "@types/matter-js": "^0.19.8",
9
+ "@types/stats.js": "^0.17.4",
10
+ "jest": "^30.0.4",
11
+ "jest-environment-jsdom": "^30.0.4",
12
+ "ts-jest": "^29.4.0",
13
+ "ts-node": "^10.9.2",
14
+ "typescript": "^5.8.3"
15
+ },
16
+ "dependencies": {
17
+ "@esotericsoftware/spine-pixi-v8": "^4.2.88",
18
+ "matter-js": "^0.20.0",
19
+ "pixi.js": "^8.11.0",
20
+ "stats.js": "^0.17.0"
21
+ },
22
+ "packageManager": "yarn@1.22.22"
23
+ }
@@ -0,0 +1,176 @@
1
+ import { audioLoader } from './loaders/audio';
2
+
3
+ const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
4
+ window.addEventListener('mousedown', () => audioContext.resume());
5
+ window.addEventListener('touchend', () => audioContext.resume());
6
+
7
+ async function getAvailableContext(): Promise<AudioContext> {
8
+ if (audioContext.state === 'suspended') await audioContext.resume();
9
+ return audioContext;
10
+ }
11
+
12
+ const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
13
+ let isPageVisible = !document.hidden;
14
+ window.addEventListener('visibilitychange', () => isPageVisible = !document.hidden);
15
+
16
+ class Audio {
17
+ src: string;
18
+ #volume: number;
19
+ #loop: boolean;
20
+
21
+ #audioBuffer?: AudioBuffer;
22
+ #audioContext?: AudioContext;
23
+ #gainNode?: GainNode;
24
+ #source?: AudioBufferSourceNode;
25
+
26
+ #isPlaying = false;
27
+ #isPaused = false;
28
+ #startTime = 0;
29
+ #pauseTime = 0;
30
+ #offset = 0;
31
+
32
+ constructor(src: string, volume: number, loop: boolean) {
33
+ this.src = src;
34
+ this.#volume = volume;
35
+ this.#loop = loop;
36
+ this.play();
37
+ }
38
+
39
+ get volume() { return this.#volume; }
40
+ set volume(volume: number) {
41
+ this.#volume = volume;
42
+ if (this.#gainNode) this.#gainNode.gain.value = Math.max(0, Math.min(1, volume));
43
+ }
44
+
45
+ async play() {
46
+ if (isMobile && !isPageVisible) return;
47
+
48
+ if (!this.#audioBuffer) {
49
+ if (!audioLoader.checkLoaded(this.src)) {
50
+ console.info(`Audio not preloaded. Loading now: ${this.src}`);
51
+ }
52
+ this.#audioBuffer = await audioLoader.load(this.src);
53
+ }
54
+ if (!this.#audioBuffer) return;
55
+
56
+ if (this.#isPlaying) this.stop();
57
+ if (!this.#isPaused) this.#offset = 0;
58
+ this.#isPlaying = true;
59
+ this.#isPaused = false;
60
+ if (!this.#audioContext) this.#audioContext = await getAvailableContext();
61
+ if (!this.#isPlaying) return;
62
+
63
+ if (!this.#gainNode) {
64
+ this.#gainNode = this.#audioContext.createGain();
65
+ this.#gainNode.gain.value = this.#volume;
66
+ this.#gainNode.connect(this.#audioContext.destination);
67
+ }
68
+
69
+ this.#source = this.#audioContext.createBufferSource();
70
+ this.#source.buffer = this.#audioBuffer;
71
+ this.#source.loop = this.#loop;
72
+ this.#source.connect(this.#gainNode);
73
+ this.#source.start(0, this.#offset);
74
+ this.#startTime = this.#audioContext.currentTime;
75
+ this.#source.onended = () => { if (!this.#isPaused && !this.#loop) this.stop(); };
76
+ }
77
+
78
+ #clear(): void {
79
+ if (this.#source) {
80
+ this.#source.stop();
81
+ this.#source.disconnect();
82
+ this.#source = undefined;
83
+ }
84
+ }
85
+
86
+ pause() {
87
+ if (this.#isPlaying && !this.#isPaused) {
88
+ if (this.#audioContext) {
89
+ this.#pauseTime = this.#audioContext.currentTime;
90
+ this.#offset += this.#pauseTime - this.#startTime;
91
+ }
92
+ this.#isPaused = true;
93
+ this.#isPlaying = false;
94
+ this.#clear();
95
+ }
96
+ }
97
+
98
+ stop() {
99
+ this.#isPlaying = false;
100
+ this.#isPaused = false;
101
+ this.#offset = 0;
102
+ this.#clear();
103
+ }
104
+ }
105
+
106
+ class MusicPlayer {
107
+ #volume = 0.7;
108
+ #currentAudio?: Audio;
109
+
110
+ constructor() {
111
+ const stored = parseFloat(localStorage.getItem('musicVolume') || '');
112
+ this.#volume = Number.isNaN(stored) ? this.#volume : stored;
113
+
114
+ if (isMobile) {
115
+ document.addEventListener('visibilitychange', () => {
116
+ if (document.hidden) this.pause();
117
+ else {
118
+ isPageVisible = true;
119
+ this.#currentAudio?.play();
120
+ }
121
+ });
122
+ }
123
+ }
124
+
125
+ get volume() {
126
+ return this.#volume;
127
+ }
128
+
129
+ set volume(volume: number) {
130
+ this.#volume = volume;
131
+ localStorage.setItem('musicVolume', volume.toString());
132
+ if (this.#currentAudio) this.#currentAudio.volume = volume;
133
+ }
134
+
135
+ play(src: string) {
136
+ if (this.#currentAudio?.src === src) return;
137
+ this.#currentAudio?.stop();
138
+ this.#currentAudio = new Audio(src, this.#volume, true);
139
+ }
140
+
141
+ pause() {
142
+ this.#currentAudio?.pause();
143
+ }
144
+
145
+ stop() {
146
+ this.#currentAudio?.stop();
147
+ this.#currentAudio = undefined;
148
+ }
149
+ }
150
+
151
+ class SfxPlayer {
152
+ #volume = 1;
153
+
154
+ constructor() {
155
+ const stored = parseFloat(localStorage.getItem('sfxVolume') || '');
156
+ this.#volume = Number.isNaN(stored) ? this.#volume : stored;
157
+ }
158
+
159
+ get volume() {
160
+ return this.#volume;
161
+ }
162
+
163
+ set volume(volume: number) {
164
+ this.#volume = volume;
165
+ localStorage.setItem('sfxVolume', volume.toString());
166
+ }
167
+
168
+ play(src: string) {
169
+ new Audio(src, this.#volume, false);
170
+ }
171
+ }
172
+
173
+ const musicPlayer = new MusicPlayer();
174
+ const sfxPlayer = new SfxPlayer();
175
+
176
+ export { audioContext, musicPlayer, sfxPlayer };
@@ -0,0 +1,39 @@
1
+ import { audioContext } from '../audio';
2
+ import { Loader } from './loader';
3
+
4
+ class AudioLoader extends Loader<AudioBuffer> {
5
+ protected override async _load(src: string) {
6
+ const loadingPromise = (async () => {
7
+ const response = await fetch(src);
8
+ if (!response.ok) {
9
+ console.error(`Failed to load audio data: ${src}`);
10
+ return;
11
+ }
12
+
13
+ const arrayBuffer = await response.arrayBuffer();
14
+
15
+ try {
16
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
17
+
18
+ this.loadingPromises.delete(src);
19
+
20
+ if (this.hasActiveRef(src)) {
21
+ if (this.loadedAssets.has(src)) {
22
+ console.error(`Audio buffer already exists: ${src}`);
23
+ } else {
24
+ this.loadedAssets.set(src, audioBuffer);
25
+ return audioBuffer;
26
+ }
27
+ }
28
+ } catch (error) {
29
+ console.error(`Failed to decode audio data: ${src}`, error);
30
+ this.loadingPromises.delete(src);
31
+ }
32
+ })();
33
+
34
+ this.loadingPromises.set(src, loadingPromise);
35
+ return await loadingPromise;
36
+ }
37
+ }
38
+
39
+ export const audioLoader = new AudioLoader();
@@ -0,0 +1,32 @@
1
+ import { Loader } from './loader';
2
+
3
+ class BinaryLoader extends Loader<Uint8Array> {
4
+ protected override async _load(src: string) {
5
+ const loadingPromise = (async () => {
6
+ const response = await fetch(src);
7
+ if (!response.ok) {
8
+ console.error(`Failed to load binary data: ${src}`);
9
+ return;
10
+ }
11
+
12
+ const arrayBuffer = await response.arrayBuffer();
13
+ const data = new Uint8Array(arrayBuffer);
14
+
15
+ this.loadingPromises.delete(src);
16
+
17
+ if (this.hasActiveRef(src)) {
18
+ if (this.loadedAssets.has(src)) {
19
+ console.error(`Binary data already exists: ${src}`);
20
+ } else {
21
+ this.loadedAssets.set(src, data);
22
+ return data;
23
+ }
24
+ }
25
+ })();
26
+
27
+ this.loadingPromises.set(src, loadingPromise);
28
+ return await loadingPromise;
29
+ }
30
+ }
31
+
32
+ export const binaryLoader = new BinaryLoader();
@@ -0,0 +1,27 @@
1
+ import { Loader } from './loader';
2
+
3
+ class FontFamilyLoader extends Loader<boolean> {
4
+ protected override async _load(fontName: string) {
5
+ const loadingPromise = (async () => {
6
+ if ('fonts' in document) {
7
+ try {
8
+ await document.fonts.load(`1em ${fontName}`);
9
+ await document.fonts.ready;
10
+ this.loadingPromises.delete(fontName);
11
+ return true;
12
+ } catch (error) {
13
+ console.error(`Failed to load font: ${fontName}`, error);
14
+ this.loadingPromises.delete(fontName);
15
+ }
16
+ } else {
17
+ console.warn(`This browser does not support the Font Loading API`);
18
+ this.loadingPromises.delete(fontName);
19
+ }
20
+ })();
21
+
22
+ this.loadingPromises.set(fontName, loadingPromise);
23
+ return await loadingPromise;
24
+ }
25
+ }
26
+
27
+ export const fontFamilyLoader = new FontFamilyLoader();
@@ -0,0 +1,39 @@
1
+ abstract class Loader<T> {
2
+ protected loadedAssets: Map<string, T> = new Map();
3
+ protected loadingPromises: Map<string, Promise<T | undefined>> = new Map();
4
+
5
+ #refCount: Map<string, number> = new Map();
6
+ #incRefCount(id: string) { this.#refCount.set(id, (this.#refCount.get(id) || 0) + 1); }
7
+
8
+ protected hasActiveRef(id: string) { return this.#refCount.get(id)! > 0; }
9
+ protected abstract _load(id: string, ...args: any[]): Promise<T | undefined>;
10
+ protected _dispose(id: string, asset: T): void { /* override to clean up */ }
11
+
12
+ checkLoaded(id: string) {
13
+ return this.loadedAssets.has(id);
14
+ }
15
+
16
+ async load(id: string, ...args: any[]) {
17
+ this.#incRefCount(id);
18
+ if (this.checkLoaded(id)) return this.loadedAssets.get(id);
19
+ if (this.loadingPromises.has(id)) return await this.loadingPromises.get(id);
20
+ return await this._load(id, ...args);
21
+ }
22
+
23
+ release(id: string) {
24
+ const refCount = this.#refCount.get(id);
25
+ if (refCount === undefined) throw new Error(`Asset not found: ${id}`);
26
+ if (refCount === 1) {
27
+ this.#refCount.delete(id);
28
+ const asset = this.loadedAssets.get(id);
29
+ if (asset) {
30
+ this._dispose(id, asset);
31
+ this.loadedAssets.delete(id);
32
+ }
33
+ } else {
34
+ this.#refCount.set(id, refCount - 1);
35
+ }
36
+ }
37
+ }
38
+
39
+ export { Loader };
@@ -0,0 +1,67 @@
1
+ import { Spritesheet, SpritesheetData } from 'pixi.js';
2
+ import { Loader } from './loader';
3
+ import { textureLoader } from './texture';
4
+
5
+ const atlasIdCache = new WeakMap<object, Map<string, string>>();
6
+ let idCounter = 0;
7
+
8
+ function getCachedId(src: string, atlas: object): string {
9
+ let innerMap = atlasIdCache.get(atlas);
10
+ if (!innerMap) {
11
+ innerMap = new Map<string, string>();
12
+ atlasIdCache.set(atlas, innerMap);
13
+ }
14
+
15
+ if (!innerMap.has(src)) {
16
+ innerMap.set(src, `${src}#${idCounter++}`);
17
+ }
18
+
19
+ return innerMap.get(src)!;
20
+ }
21
+
22
+ class SpritesheetLoader extends Loader<Spritesheet> {
23
+ #idToSrc: Map<string, string> = new Map();
24
+
25
+ protected override async _load(id: string, src: string, atlas: SpritesheetData) {
26
+ this.#idToSrc.set(id, src);
27
+
28
+ const loadingPromise = (async () => {
29
+ const texture = await textureLoader.load(src);
30
+ if (!texture) {
31
+ console.error(`Failed to load texture: ${src}`);
32
+ return;
33
+ }
34
+
35
+ const spritesheet = new Spritesheet(texture, atlas);
36
+ await spritesheet.parse();
37
+
38
+ this.loadingPromises.delete(id);
39
+
40
+ if (this.hasActiveRef(id)) {
41
+ if (this.loadedAssets.has(id)) {
42
+ textureLoader.release(src);
43
+ console.error(`Spritesheet already exists: ${src}`);
44
+ } else {
45
+ this.loadedAssets.set(id, spritesheet);
46
+ return spritesheet;
47
+ }
48
+ } else {
49
+ textureLoader.release(src);
50
+ }
51
+ })();
52
+
53
+ this.loadingPromises.set(id, loadingPromise);
54
+ return await loadingPromise;
55
+ }
56
+
57
+ protected override _dispose(id: string, spritesheet: Spritesheet) {
58
+ spritesheet.destroy();
59
+
60
+ const src = this.#idToSrc.get(id);
61
+ if (src) textureLoader.release(src);
62
+ }
63
+ }
64
+
65
+ const spritesheetLoader = new SpritesheetLoader();
66
+
67
+ export { getCachedId, spritesheetLoader };
@@ -0,0 +1,31 @@
1
+ import { Loader } from './loader';
2
+
3
+ class TextLoader extends Loader<string> {
4
+ protected override async _load(src: string) {
5
+ const loadingPromise = (async () => {
6
+ const response = await fetch(src);
7
+ if (!response.ok) {
8
+ console.error(`Failed to load text: ${src}`);
9
+ return;
10
+ }
11
+
12
+ const text = await response.text();
13
+
14
+ this.loadingPromises.delete(src);
15
+
16
+ if (this.hasActiveRef(src)) {
17
+ if (this.loadedAssets.has(src)) {
18
+ console.error(`Text already exists: ${src}`);
19
+ } else {
20
+ this.loadedAssets.set(src, text);
21
+ return text;
22
+ }
23
+ }
24
+ })();
25
+
26
+ this.loadingPromises.set(src, loadingPromise);
27
+ return await loadingPromise;
28
+ }
29
+ }
30
+
31
+ export const textLoader = new TextLoader();
@@ -0,0 +1,46 @@
1
+ import { Texture } from 'pixi.js';
2
+ import { Loader } from './loader';
3
+
4
+ class TextureLoader extends Loader<Texture> {
5
+ protected override async _load(src: string) {
6
+ const loadingPromise = new Promise<Texture | undefined>((resolve) => {
7
+ const image = new Image();
8
+ image.crossOrigin = 'anonymous';
9
+ image.src = src;
10
+
11
+ image.onload = () => {
12
+ this.loadingPromises.delete(src);
13
+
14
+ if (!this.hasActiveRef(src)) {
15
+ resolve(undefined);
16
+ return;
17
+ }
18
+
19
+ if (this.loadedAssets.has(src)) {
20
+ console.error(`Texture already loaded: ${src}`);
21
+ resolve(undefined);
22
+ return;
23
+ }
24
+
25
+ const texture = Texture.from(image);
26
+ this.loadedAssets.set(src, texture);
27
+ resolve(texture);
28
+ };
29
+
30
+ image.onerror = (error) => {
31
+ this.loadingPromises.delete(src);
32
+ console.error(`Failed to load texture: ${src}`, error);
33
+ resolve(undefined);
34
+ };
35
+ });
36
+
37
+ this.loadingPromises.set(src, loadingPromise);
38
+ return await loadingPromise;
39
+ }
40
+
41
+ protected override _dispose(src: string, texture: Texture) {
42
+ texture.destroy(true);
43
+ }
44
+ }
45
+
46
+ export const textureLoader = new TextureLoader();