kiwiengine 0.7.0 → 1.0.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/lib/asset/audio.js +25 -7
- package/lib/asset/audio.js.map +1 -1
- package/lib/dom/dom-particle.js +23 -74
- package/lib/dom/dom-particle.js.map +1 -1
- package/lib/node/core/game-object.js +1 -4
- package/lib/node/core/game-object.js.map +1 -1
- package/lib/node/core/renderable.js +6 -4
- package/lib/node/core/renderable.js.map +1 -1
- package/lib/node/core/transformable.js +11 -42
- package/lib/node/core/transformable.js.map +1 -1
- package/lib/node/ext/animated-sprite.js +1 -8
- package/lib/node/ext/animated-sprite.js.map +1 -1
- package/lib/node/ext/bitmap-text.js +4 -61
- package/lib/node/ext/bitmap-text.js.map +1 -1
- package/lib/node/ext/circle.js +1 -2
- package/lib/node/ext/circle.js.map +1 -1
- package/lib/node/ext/particle.js +15 -65
- package/lib/node/ext/particle.js.map +1 -1
- package/lib/node/ext/rectangle.js +1 -2
- package/lib/node/ext/rectangle.js.map +1 -1
- package/lib/renderer/camera.js +0 -8
- package/lib/renderer/camera.js.map +1 -1
- package/lib/renderer/renderer.js +2 -7
- package/lib/renderer/renderer.js.map +1 -1
- package/lib/types/asset/audio.d.ts +8 -0
- package/lib/types/asset/audio.d.ts.map +1 -1
- package/lib/types/dom/dom-particle.d.ts +0 -1
- package/lib/types/dom/dom-particle.d.ts.map +1 -1
- package/lib/types/node/core/game-object.d.ts +1 -3
- package/lib/types/node/core/game-object.d.ts.map +1 -1
- package/lib/types/node/core/renderable.d.ts +1 -0
- package/lib/types/node/core/renderable.d.ts.map +1 -1
- package/lib/types/node/core/transformable.d.ts.map +1 -1
- package/lib/types/node/ext/animated-sprite.d.ts.map +1 -1
- package/lib/types/node/ext/bitmap-text.d.ts.map +1 -1
- package/lib/types/node/ext/circle.d.ts.map +1 -1
- package/lib/types/node/ext/particle.d.ts +0 -1
- package/lib/types/node/ext/particle.d.ts.map +1 -1
- package/lib/types/node/ext/rectangle.d.ts.map +1 -1
- package/lib/types/renderer/camera.d.ts.map +1 -1
- package/lib/types/renderer/renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/asset/audio.ts +28 -7
- package/src/dom/dom-particle.ts +24 -91
- package/src/node/core/game-object.ts +2 -10
- package/src/node/core/renderable.ts +5 -4
- package/src/node/core/transformable.ts +11 -49
- package/src/node/ext/animated-sprite.ts +1 -10
- package/src/node/ext/bitmap-text.ts +4 -70
- package/src/node/ext/circle.ts +1 -2
- package/src/node/ext/particle.ts +16 -80
- package/src/node/ext/rectangle.ts +1 -2
- package/src/renderer/camera.ts +0 -6
- package/src/renderer/renderer.ts +2 -9
package/lib/asset/audio.js
CHANGED
|
@@ -165,6 +165,7 @@ export class Audio {
|
|
|
165
165
|
class MusicPlayer {
|
|
166
166
|
#volume = 0.7;
|
|
167
167
|
#enabled = true;
|
|
168
|
+
#temporarilyDisabled = false;
|
|
168
169
|
#currentAudio;
|
|
169
170
|
#pendingSrc;
|
|
170
171
|
constructor() {
|
|
@@ -180,8 +181,8 @@ class MusicPlayer {
|
|
|
180
181
|
}
|
|
181
182
|
else {
|
|
182
183
|
isPageVisible = true;
|
|
183
|
-
// Only resume if enabled
|
|
184
|
-
if (this.#enabled)
|
|
184
|
+
// Only resume if enabled and not temporarily disabled
|
|
185
|
+
if (this.#enabled && !this.#temporarilyDisabled)
|
|
185
186
|
this.#currentAudio?.play();
|
|
186
187
|
}
|
|
187
188
|
});
|
|
@@ -209,6 +210,18 @@ class MusicPlayer {
|
|
|
209
210
|
enable() { this.enabled = true; }
|
|
210
211
|
disable() { this.enabled = false; }
|
|
211
212
|
toggle() { this.enabled = !this.enabled; }
|
|
213
|
+
get temporarilyDisabled() { return this.#temporarilyDisabled; }
|
|
214
|
+
set temporarilyDisabled(v) {
|
|
215
|
+
this.#temporarilyDisabled = v;
|
|
216
|
+
if (v) {
|
|
217
|
+
this.pause();
|
|
218
|
+
}
|
|
219
|
+
else if (this.#enabled) {
|
|
220
|
+
this.#currentAudio?.play();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
temporaryDisable() { this.temporarilyDisabled = true; }
|
|
224
|
+
temporaryEnable() { this.temporarilyDisabled = false; }
|
|
212
225
|
get volume() { return this.#volume; }
|
|
213
226
|
set volume(volume) {
|
|
214
227
|
this.#volume = clamp01(volume);
|
|
@@ -217,9 +230,9 @@ class MusicPlayer {
|
|
|
217
230
|
this.#currentAudio.volume = this.#volume;
|
|
218
231
|
}
|
|
219
232
|
play(src) {
|
|
220
|
-
// Remember the user's intent even if music is currently disabled
|
|
233
|
+
// Remember the user's intent even if music is currently disabled or temporarily disabled
|
|
221
234
|
this.#pendingSrc = src;
|
|
222
|
-
if (!this.#enabled)
|
|
235
|
+
if (!this.#enabled || this.#temporarilyDisabled)
|
|
223
236
|
return;
|
|
224
237
|
// If it's the same track, resume instead of recreating the Audio
|
|
225
238
|
if (this.#currentAudio?.src === src) {
|
|
@@ -242,6 +255,7 @@ class MusicPlayer {
|
|
|
242
255
|
class SfxPlayer {
|
|
243
256
|
#volume = 1;
|
|
244
257
|
#enabled = true;
|
|
258
|
+
#temporarilyDisabled = false;
|
|
245
259
|
constructor() {
|
|
246
260
|
const storedVol = parseFloat(safeStorage.getItem('sfxVolume') || '');
|
|
247
261
|
this.#volume = Number.isNaN(storedVol) ? this.#volume : clamp01(storedVol);
|
|
@@ -256,19 +270,23 @@ class SfxPlayer {
|
|
|
256
270
|
enable() { this.enabled = true; }
|
|
257
271
|
disable() { this.enabled = false; }
|
|
258
272
|
toggle() { this.enabled = !this.enabled; }
|
|
273
|
+
get temporarilyDisabled() { return this.#temporarilyDisabled; }
|
|
274
|
+
set temporarilyDisabled(v) { this.#temporarilyDisabled = v; }
|
|
275
|
+
temporaryDisable() { this.temporarilyDisabled = true; }
|
|
276
|
+
temporaryEnable() { this.temporarilyDisabled = false; }
|
|
259
277
|
get volume() { return this.#volume; }
|
|
260
278
|
set volume(volume) {
|
|
261
279
|
this.#volume = clamp01(volume);
|
|
262
280
|
safeStorage.setItem('sfxVolume', this.#volume.toString());
|
|
263
281
|
}
|
|
264
282
|
play(src) {
|
|
265
|
-
// If disabled, do not play any one-shot sounds
|
|
266
|
-
if (audioContext.state !== 'running' || !this.#enabled)
|
|
283
|
+
// If disabled or temporarily disabled, do not play any one-shot sounds
|
|
284
|
+
if (audioContext.state !== 'running' || !this.#enabled || this.#temporarilyDisabled)
|
|
267
285
|
return;
|
|
268
286
|
new Audio(src, this.#volume, false);
|
|
269
287
|
}
|
|
270
288
|
playRandom(...srcs) {
|
|
271
|
-
if (!this.#enabled)
|
|
289
|
+
if (!this.#enabled || this.#temporarilyDisabled)
|
|
272
290
|
return;
|
|
273
291
|
this.play(srcs[Math.floor(Math.random() * srcs.length)]);
|
|
274
292
|
}
|
package/lib/asset/audio.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio.js","sourceRoot":"","sources":["../../src/asset/audio.ts"],"names":[],"mappings":"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","sourcesContent":["import { isMobile } from '../utils/device'\nimport { audioLoader } from './loaders/audio'\n\nexport const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)()\nwindow.addEventListener('mousedown', () => audioContext.resume())\nwindow.addEventListener('touchend', () => audioContext.resume())\n\nasync function getAvailableContext(): Promise<AudioContext> {\n if (audioContext.state === 'suspended') await audioContext.resume()\n return audioContext\n}\n\nlet isPageVisible = !document.hidden\nwindow.addEventListener('visibilitychange', () => {\n isPageVisible = !document.hidden\n})\n\ntype BasicStorage = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>\n\ntype SafeStorage = BasicStorage & {\n persistent: boolean\n}\n\nfunction createSafeStorage(): SafeStorage {\n const memory = (() => {\n const m = new Map<string, string>()\n const api: SafeStorage = {\n persistent: false,\n getItem: (k) => (m.has(k) ? m.get(k)! : null),\n setItem: (k, v) => { m.set(k, String(v)) },\n removeItem: (k) => { m.delete(k) },\n }\n return api\n })()\n\n if (typeof window === 'undefined') return memory\n\n try {\n const ls = window.localStorage\n const probeKey = '__safe_ls_probe__' + Math.random().toString(36).slice(2)\n ls.setItem(probeKey, '1')\n ls.removeItem(probeKey)\n\n const safe: SafeStorage = {\n persistent: true,\n getItem: (k) => ls.getItem(k),\n setItem: (k, v) => ls.setItem(k, v),\n removeItem: (k) => ls.removeItem(k),\n }\n return safe\n } catch {\n return memory\n }\n}\n\nconst safeStorage = createSafeStorage()\n\nfunction clamp01(n: number) {\n return Math.max(0, Math.min(1, n))\n}\n\nfunction readBool(key: string, defaultValue: boolean) {\n const v = safeStorage.getItem(key)\n if (v == null) return defaultValue\n return v === '1' || v === 'true'\n}\n\nfunction writeBool(key: string, value: boolean) {\n safeStorage.setItem(key, value ? '1' : '0')\n}\n\nexport class Audio {\n src: string\n #volume: number\n #loop: boolean\n\n #audioBuffer?: AudioBuffer\n #audioContext?: AudioContext\n #gainNode?: GainNode\n #source?: AudioBufferSourceNode\n\n #isPlaying = false\n #isPaused = false\n #startTime = 0\n #pauseTime = 0\n #offset = 0\n\n constructor(src: string, volume: number, loop: boolean) {\n this.src = src\n this.#volume = clamp01(volume)\n this.#loop = loop\n this.play()\n }\n\n get volume() { return this.#volume }\n set volume(volume: number) {\n this.#volume = clamp01(volume)\n if (this.#gainNode) this.#gainNode.gain.value = this.#volume\n }\n\n async play() {\n // On mobile, avoid starting audio while the page is hidden (often blocked / unreliable)\n if (isMobile && !isPageVisible) return\n\n if (!this.#audioBuffer) {\n if (audioLoader.checkCached(this.src)) {\n this.#audioBuffer = audioLoader.getCached(this.src)\n } else {\n console.info(`Audio not preloaded. Loading now: ${this.src}`)\n this.#audioBuffer = await audioLoader.load(this.src)\n }\n }\n if (!this.#audioBuffer) return\n\n // If already playing, restart cleanly\n if (this.#isPlaying) this.stop()\n\n // If this is not a resume, reset offset to the beginning\n if (!this.#isPaused) this.#offset = 0\n\n this.#isPlaying = true\n this.#isPaused = false\n\n if (!this.#audioContext) this.#audioContext = await getAvailableContext()\n\n // If state changed while awaiting, bail out\n if (!this.#isPlaying) return\n\n if (!this.#gainNode) {\n this.#gainNode = this.#audioContext.createGain()\n this.#gainNode.gain.value = this.#volume\n this.#gainNode.connect(this.#audioContext.destination)\n } else {\n this.#gainNode.gain.value = this.#volume\n }\n\n this.#source = this.#audioContext.createBufferSource()\n this.#source.buffer = this.#audioBuffer\n this.#source.loop = this.#loop\n this.#source.connect(this.#gainNode)\n this.#source.start(0, this.#offset)\n this.#startTime = this.#audioContext.currentTime\n\n this.#source.onended = () => {\n // Only auto-stop for one-shot sounds that were not paused and are not looping\n if (!this.#isPaused && !this.#loop) this.stop()\n }\n }\n\n #clear(): void {\n if (this.#source) {\n // stop() can throw if already stopped; ignore safely\n try { this.#source.stop() } catch { /* noop */ }\n try { this.#source.disconnect() } catch { /* noop */ }\n this.#source = undefined\n }\n }\n\n pause() {\n if (this.#isPlaying && !this.#isPaused) {\n if (this.#audioContext) {\n // Track elapsed time so we can resume from the correct offset\n this.#pauseTime = this.#audioContext.currentTime\n this.#offset += this.#pauseTime - this.#startTime\n }\n this.#isPaused = true\n this.#isPlaying = false\n this.#clear()\n }\n }\n\n stop() {\n // Full stop resets the offset; next play starts from the beginning\n this.#isPlaying = false\n this.#isPaused = false\n this.#offset = 0\n this.#clear()\n }\n}\n\nclass MusicPlayer {\n #volume = 0.7\n #enabled = true\n #currentAudio?: Audio\n #pendingSrc?: string\n\n constructor() {\n const storedVol = parseFloat(safeStorage.getItem('musicVolume') || '')\n this.#volume = Number.isNaN(storedVol) ? this.#volume : clamp01(storedVol)\n\n // Separate from volume: true/false music enable state\n this.#enabled = readBool('musicEnabled', true)\n\n if (isMobile) {\n document.addEventListener('visibilitychange', () => {\n if (document.hidden) {\n // When hidden, pause to avoid mobile audio issues\n this.pause()\n } else {\n isPageVisible = true\n // Only resume if enabled\n if (this.#enabled) this.#currentAudio?.play()\n }\n })\n }\n }\n\n get enabled() { return this.#enabled }\n set enabled(v: boolean) {\n this.#enabled = v\n writeBool('musicEnabled', v)\n\n if (!v) {\n // \"Off\" means: do not output music. Keep the state by pausing (resume later).\n this.pause()\n return\n }\n\n // When turning on, play the last requested track if any; otherwise just resume\n if (this.#pendingSrc) {\n const src = this.#pendingSrc\n this.#pendingSrc = undefined\n this.play(src)\n } else {\n this.#currentAudio?.play()\n }\n }\n\n enable() { this.enabled = true }\n disable() { this.enabled = false }\n toggle() { this.enabled = !this.enabled }\n\n get volume() { return this.#volume }\n set volume(volume: number) {\n this.#volume = clamp01(volume)\n safeStorage.setItem('musicVolume', this.#volume.toString())\n if (this.#currentAudio) this.#currentAudio.volume = this.#volume\n }\n\n play(src: string) {\n // Remember the user's intent even if music is currently disabled\n this.#pendingSrc = src\n if (!this.#enabled) return\n\n // If it's the same track, resume instead of recreating the Audio\n if (this.#currentAudio?.src === src) {\n this.#currentAudio.play()\n return\n }\n\n this.#currentAudio?.stop()\n this.#currentAudio = new Audio(src, this.#volume, true)\n }\n\n pause() {\n this.#currentAudio?.pause()\n }\n\n stop() {\n // stop() is a hard reset (unlike pause)\n this.#currentAudio?.stop()\n this.#currentAudio = undefined\n this.#pendingSrc = undefined\n }\n}\n\nclass SfxPlayer {\n #volume = 1\n #enabled = true\n\n constructor() {\n const storedVol = parseFloat(safeStorage.getItem('sfxVolume') || '')\n this.#volume = Number.isNaN(storedVol) ? this.#volume : clamp01(storedVol)\n\n // Separate from volume: true/false SFX enable state\n this.#enabled = readBool('sfxEnabled', true)\n }\n\n get enabled() { return this.#enabled }\n set enabled(v: boolean) {\n this.#enabled = v\n writeBool('sfxEnabled', v)\n }\n\n enable() { this.enabled = true }\n disable() { this.enabled = false }\n toggle() { this.enabled = !this.enabled }\n\n get volume() { return this.#volume }\n set volume(volume: number) {\n this.#volume = clamp01(volume)\n safeStorage.setItem('sfxVolume', this.#volume.toString())\n }\n\n play(src: string) {\n // If disabled, do not play any one-shot sounds\n if (audioContext.state !== 'running' || !this.#enabled) return\n new Audio(src, this.#volume, false)\n }\n\n playRandom(...srcs: string[]) {\n if (!this.#enabled) return\n this.play(srcs[Math.floor(Math.random() * srcs.length)])\n }\n}\n\nexport const musicPlayer = new MusicPlayer()\nexport const sfxPlayer = new SfxPlayer()\n"]}
|
|
1
|
+
{"version":3,"file":"audio.js","sourceRoot":"","sources":["../../src/asset/audio.ts"],"names":[],"mappings":"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","sourcesContent":["import { isMobile } from '../utils/device'\nimport { audioLoader } from './loaders/audio'\n\nexport const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)()\nwindow.addEventListener('mousedown', () => audioContext.resume())\nwindow.addEventListener('touchend', () => audioContext.resume())\n\nasync function getAvailableContext(): Promise<AudioContext> {\n if (audioContext.state === 'suspended') await audioContext.resume()\n return audioContext\n}\n\nlet isPageVisible = !document.hidden\nwindow.addEventListener('visibilitychange', () => {\n isPageVisible = !document.hidden\n})\n\ntype BasicStorage = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>\n\ntype SafeStorage = BasicStorage & {\n persistent: boolean\n}\n\nfunction createSafeStorage(): SafeStorage {\n const memory = (() => {\n const m = new Map<string, string>()\n const api: SafeStorage = {\n persistent: false,\n getItem: (k) => (m.has(k) ? m.get(k)! : null),\n setItem: (k, v) => { m.set(k, String(v)) },\n removeItem: (k) => { m.delete(k) },\n }\n return api\n })()\n\n if (typeof window === 'undefined') return memory\n\n try {\n const ls = window.localStorage\n const probeKey = '__safe_ls_probe__' + Math.random().toString(36).slice(2)\n ls.setItem(probeKey, '1')\n ls.removeItem(probeKey)\n\n const safe: SafeStorage = {\n persistent: true,\n getItem: (k) => ls.getItem(k),\n setItem: (k, v) => ls.setItem(k, v),\n removeItem: (k) => ls.removeItem(k),\n }\n return safe\n } catch {\n return memory\n }\n}\n\nconst safeStorage = createSafeStorage()\n\nfunction clamp01(n: number) {\n return Math.max(0, Math.min(1, n))\n}\n\nfunction readBool(key: string, defaultValue: boolean) {\n const v = safeStorage.getItem(key)\n if (v == null) return defaultValue\n return v === '1' || v === 'true'\n}\n\nfunction writeBool(key: string, value: boolean) {\n safeStorage.setItem(key, value ? '1' : '0')\n}\n\nexport class Audio {\n src: string\n #volume: number\n #loop: boolean\n\n #audioBuffer?: AudioBuffer\n #audioContext?: AudioContext\n #gainNode?: GainNode\n #source?: AudioBufferSourceNode\n\n #isPlaying = false\n #isPaused = false\n #startTime = 0\n #pauseTime = 0\n #offset = 0\n\n constructor(src: string, volume: number, loop: boolean) {\n this.src = src\n this.#volume = clamp01(volume)\n this.#loop = loop\n this.play()\n }\n\n get volume() { return this.#volume }\n set volume(volume: number) {\n this.#volume = clamp01(volume)\n if (this.#gainNode) this.#gainNode.gain.value = this.#volume\n }\n\n async play() {\n // On mobile, avoid starting audio while the page is hidden (often blocked / unreliable)\n if (isMobile && !isPageVisible) return\n\n if (!this.#audioBuffer) {\n if (audioLoader.checkCached(this.src)) {\n this.#audioBuffer = audioLoader.getCached(this.src)\n } else {\n console.info(`Audio not preloaded. Loading now: ${this.src}`)\n this.#audioBuffer = await audioLoader.load(this.src)\n }\n }\n if (!this.#audioBuffer) return\n\n // If already playing, restart cleanly\n if (this.#isPlaying) this.stop()\n\n // If this is not a resume, reset offset to the beginning\n if (!this.#isPaused) this.#offset = 0\n\n this.#isPlaying = true\n this.#isPaused = false\n\n if (!this.#audioContext) this.#audioContext = await getAvailableContext()\n\n // If state changed while awaiting, bail out\n if (!this.#isPlaying) return\n\n if (!this.#gainNode) {\n this.#gainNode = this.#audioContext.createGain()\n this.#gainNode.gain.value = this.#volume\n this.#gainNode.connect(this.#audioContext.destination)\n } else {\n this.#gainNode.gain.value = this.#volume\n }\n\n this.#source = this.#audioContext.createBufferSource()\n this.#source.buffer = this.#audioBuffer\n this.#source.loop = this.#loop\n this.#source.connect(this.#gainNode)\n this.#source.start(0, this.#offset)\n this.#startTime = this.#audioContext.currentTime\n\n this.#source.onended = () => {\n // Only auto-stop for one-shot sounds that were not paused and are not looping\n if (!this.#isPaused && !this.#loop) this.stop()\n }\n }\n\n #clear(): void {\n if (this.#source) {\n // stop() can throw if already stopped; ignore safely\n try { this.#source.stop() } catch { /* noop */ }\n try { this.#source.disconnect() } catch { /* noop */ }\n this.#source = undefined\n }\n }\n\n pause() {\n if (this.#isPlaying && !this.#isPaused) {\n if (this.#audioContext) {\n // Track elapsed time so we can resume from the correct offset\n this.#pauseTime = this.#audioContext.currentTime\n this.#offset += this.#pauseTime - this.#startTime\n }\n this.#isPaused = true\n this.#isPlaying = false\n this.#clear()\n }\n }\n\n stop() {\n // Full stop resets the offset; next play starts from the beginning\n this.#isPlaying = false\n this.#isPaused = false\n this.#offset = 0\n this.#clear()\n }\n}\n\nclass MusicPlayer {\n #volume = 0.7\n #enabled = true\n #temporarilyDisabled = false\n #currentAudio?: Audio\n #pendingSrc?: string\n\n constructor() {\n const storedVol = parseFloat(safeStorage.getItem('musicVolume') || '')\n this.#volume = Number.isNaN(storedVol) ? this.#volume : clamp01(storedVol)\n\n // Separate from volume: true/false music enable state\n this.#enabled = readBool('musicEnabled', true)\n\n if (isMobile) {\n document.addEventListener('visibilitychange', () => {\n if (document.hidden) {\n // When hidden, pause to avoid mobile audio issues\n this.pause()\n } else {\n isPageVisible = true\n // Only resume if enabled and not temporarily disabled\n if (this.#enabled && !this.#temporarilyDisabled) this.#currentAudio?.play()\n }\n })\n }\n }\n\n get enabled() { return this.#enabled }\n set enabled(v: boolean) {\n this.#enabled = v\n writeBool('musicEnabled', v)\n\n if (!v) {\n // \"Off\" means: do not output music. Keep the state by pausing (resume later).\n this.pause()\n return\n }\n\n // When turning on, play the last requested track if any; otherwise just resume\n if (this.#pendingSrc) {\n const src = this.#pendingSrc\n this.#pendingSrc = undefined\n this.play(src)\n } else {\n this.#currentAudio?.play()\n }\n }\n\n enable() { this.enabled = true }\n disable() { this.enabled = false }\n toggle() { this.enabled = !this.enabled }\n\n get temporarilyDisabled() { return this.#temporarilyDisabled }\n set temporarilyDisabled(v: boolean) {\n this.#temporarilyDisabled = v\n if (v) {\n this.pause()\n } else if (this.#enabled) {\n this.#currentAudio?.play()\n }\n }\n\n temporaryDisable() { this.temporarilyDisabled = true }\n temporaryEnable() { this.temporarilyDisabled = false }\n\n get volume() { return this.#volume }\n set volume(volume: number) {\n this.#volume = clamp01(volume)\n safeStorage.setItem('musicVolume', this.#volume.toString())\n if (this.#currentAudio) this.#currentAudio.volume = this.#volume\n }\n\n play(src: string) {\n // Remember the user's intent even if music is currently disabled or temporarily disabled\n this.#pendingSrc = src\n if (!this.#enabled || this.#temporarilyDisabled) return\n\n // If it's the same track, resume instead of recreating the Audio\n if (this.#currentAudio?.src === src) {\n this.#currentAudio.play()\n return\n }\n\n this.#currentAudio?.stop()\n this.#currentAudio = new Audio(src, this.#volume, true)\n }\n\n pause() {\n this.#currentAudio?.pause()\n }\n\n stop() {\n // stop() is a hard reset (unlike pause)\n this.#currentAudio?.stop()\n this.#currentAudio = undefined\n this.#pendingSrc = undefined\n }\n}\n\nclass SfxPlayer {\n #volume = 1\n #enabled = true\n #temporarilyDisabled = false\n\n constructor() {\n const storedVol = parseFloat(safeStorage.getItem('sfxVolume') || '')\n this.#volume = Number.isNaN(storedVol) ? this.#volume : clamp01(storedVol)\n\n // Separate from volume: true/false SFX enable state\n this.#enabled = readBool('sfxEnabled', true)\n }\n\n get enabled() { return this.#enabled }\n set enabled(v: boolean) {\n this.#enabled = v\n writeBool('sfxEnabled', v)\n }\n\n enable() { this.enabled = true }\n disable() { this.enabled = false }\n toggle() { this.enabled = !this.enabled }\n\n get temporarilyDisabled() { return this.#temporarilyDisabled }\n set temporarilyDisabled(v: boolean) { this.#temporarilyDisabled = v }\n\n temporaryDisable() { this.temporarilyDisabled = true }\n temporaryEnable() { this.temporarilyDisabled = false }\n\n get volume() { return this.#volume }\n set volume(volume: number) {\n this.#volume = clamp01(volume)\n safeStorage.setItem('sfxVolume', this.#volume.toString())\n }\n\n play(src: string) {\n // If disabled or temporarily disabled, do not play any one-shot sounds\n if (audioContext.state !== 'running' || !this.#enabled || this.#temporarilyDisabled) return\n new Audio(src, this.#volume, false)\n }\n\n playRandom(...srcs: string[]) {\n if (!this.#enabled || this.#temporarilyDisabled) return\n this.play(srcs[Math.floor(Math.random() * srcs.length)])\n }\n}\n\nexport const musicPlayer = new MusicPlayer()\nexport const sfxPlayer = new SfxPlayer()\n"]}
|
package/lib/dom/dom-particle.js
CHANGED
|
@@ -18,9 +18,6 @@ export class DomParticleSystem extends DomGameObject {
|
|
|
18
18
|
#texture;
|
|
19
19
|
#loadTexturePromise;
|
|
20
20
|
#particles = [];
|
|
21
|
-
// [성능 최적화] DOM 엘리먼트 풀 - 재사용으로 DOM 조작 및 GC 부담 감소
|
|
22
|
-
#elementPool = [];
|
|
23
|
-
#poolSize;
|
|
24
21
|
constructor(options) {
|
|
25
22
|
super(options);
|
|
26
23
|
this.el.style.pointerEvents = 'none';
|
|
@@ -34,7 +31,6 @@ export class DomParticleSystem extends DomGameObject {
|
|
|
34
31
|
this.#fadeRate = options.fadeRate;
|
|
35
32
|
this.#orientToVelocity = options.orientToVelocity;
|
|
36
33
|
this.#blendMode = options.blendMode;
|
|
37
|
-
this.#poolSize = options.poolSize ?? 100;
|
|
38
34
|
this.#loadTexturePromise = this.#loadTexture();
|
|
39
35
|
}
|
|
40
36
|
async #loadTexture() {
|
|
@@ -46,22 +42,18 @@ export class DomParticleSystem extends DomGameObject {
|
|
|
46
42
|
this.#texture = await domTextureLoader.load(this.#textureSrc);
|
|
47
43
|
}
|
|
48
44
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
// 풀이 비어있으면 새로 생성
|
|
64
|
-
el = document.createElement('div');
|
|
45
|
+
async burst({ x, y }) {
|
|
46
|
+
if (!this.#texture)
|
|
47
|
+
await this.#loadTexturePromise;
|
|
48
|
+
const count = random(this.#count.min, this.#count.max);
|
|
49
|
+
for (let i = 0; i < count; i++) {
|
|
50
|
+
const lifespan = random(this.#lifespan.min, this.#lifespan.max);
|
|
51
|
+
const angle = random(this.#angle.min, this.#angle.max);
|
|
52
|
+
const sin = Math.sin(angle);
|
|
53
|
+
const cos = Math.cos(angle);
|
|
54
|
+
const velocity = random(this.#velocity.min, this.#velocity.max);
|
|
55
|
+
const scale = random(this.#scale.min, this.#scale.max);
|
|
56
|
+
const el = document.createElement('div');
|
|
65
57
|
setStyle(el, {
|
|
66
58
|
position: 'absolute',
|
|
67
59
|
left: `${x}px`,
|
|
@@ -75,80 +67,37 @@ export class DomParticleSystem extends DomGameObject {
|
|
|
75
67
|
opacity: `${this.#startAlpha ?? 1}`,
|
|
76
68
|
mixBlendMode: this.#blendMode ?? 'normal',
|
|
77
69
|
});
|
|
78
|
-
this.el.appendChild(el);
|
|
79
|
-
}
|
|
80
|
-
return el;
|
|
81
|
-
}
|
|
82
|
-
// [성능 최적화] 엘리먼트를 풀로 반환 (DOM에서 제거 대신)
|
|
83
|
-
#releaseElement(el) {
|
|
84
|
-
if (this.#elementPool.length < this.#poolSize) {
|
|
85
|
-
el.style.display = 'none';
|
|
86
|
-
this.#elementPool.push(el);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
el.remove();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async burst({ x, y }) {
|
|
93
|
-
if (!this.#texture)
|
|
94
|
-
await this.#loadTexturePromise;
|
|
95
|
-
const count = random(this.#count.min, this.#count.max);
|
|
96
|
-
for (let i = 0; i < count; i++) {
|
|
97
|
-
const lifespan = random(this.#lifespan.min, this.#lifespan.max);
|
|
98
|
-
const angle = random(this.#angle.min, this.#angle.max);
|
|
99
|
-
const sin = Math.sin(angle);
|
|
100
|
-
const cos = Math.cos(angle);
|
|
101
|
-
const velocity = random(this.#velocity.min, this.#velocity.max);
|
|
102
|
-
const scale = random(this.#scale.min, this.#scale.max);
|
|
103
|
-
const el = this.#acquireElement(x, y, scale, angle);
|
|
104
70
|
this.#particles.push({
|
|
105
71
|
el,
|
|
106
|
-
active: true,
|
|
107
|
-
x,
|
|
108
|
-
y,
|
|
109
|
-
opacity: this.#startAlpha ?? 1,
|
|
110
72
|
age: 0,
|
|
111
73
|
lifespan,
|
|
112
74
|
velocityX: velocity * cos,
|
|
113
75
|
velocityY: velocity * sin,
|
|
114
76
|
fadeRate: this.#fadeRate,
|
|
115
77
|
});
|
|
78
|
+
this.el.appendChild(el);
|
|
116
79
|
}
|
|
117
80
|
}
|
|
118
81
|
update(dt) {
|
|
119
82
|
super.update(dt);
|
|
120
83
|
const ps = this.#particles;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const p = ps[readIdx];
|
|
84
|
+
for (let i = 0; i < ps.length; i++) {
|
|
85
|
+
const p = ps[i];
|
|
86
|
+
const e = p.el;
|
|
125
87
|
p.age += dt;
|
|
126
88
|
if (p.age > p.lifespan) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
89
|
+
e.remove();
|
|
90
|
+
ps.splice(i, 1);
|
|
91
|
+
i--;
|
|
130
92
|
continue;
|
|
131
93
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
setStyle(p.el, { left: `${p.x}px`, top: `${p.y}px`, opacity: `${p.opacity}` });
|
|
137
|
-
// 활성 파티클을 앞으로 이동
|
|
138
|
-
if (writeIdx !== readIdx) {
|
|
139
|
-
ps[writeIdx] = p;
|
|
140
|
-
}
|
|
141
|
-
writeIdx++;
|
|
94
|
+
const x = parseFloat(e.style.left) + p.velocityX * dt;
|
|
95
|
+
const y = parseFloat(e.style.top) + p.velocityY * dt;
|
|
96
|
+
const opacity = parseFloat(e.style.opacity) + p.fadeRate * dt;
|
|
97
|
+
setStyle(e, { left: `${x}px`, top: `${y}px`, opacity: `${opacity}` });
|
|
142
98
|
}
|
|
143
|
-
// 비활성 파티클 제거 (한 번에 배열 크기 조정)
|
|
144
|
-
ps.length = writeIdx;
|
|
145
99
|
}
|
|
146
100
|
remove() {
|
|
147
|
-
// 풀에 있는 엘리먼트도 정리
|
|
148
|
-
for (const el of this.#elementPool) {
|
|
149
|
-
el.remove();
|
|
150
|
-
}
|
|
151
|
-
this.#elementPool.length = 0;
|
|
152
101
|
domTextureLoader.release(this.#textureSrc);
|
|
153
102
|
super.remove();
|
|
154
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-particle.js","sourceRoot":"","sources":["../../src/dom/dom-particle.ts"],"names":[],"mappings":"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","sourcesContent":["import { BLEND_MODES } from 'pixi.js'\nimport { DomGameObject, DomGameObjectOptions } from './dom-game-object'\nimport { domTextureLoader } from './dom-texture-loader'\nimport { setStyle } from './dom-utils'\n\ntype RandomRange = { min: number, max: number }\n\nexport type DomParticleSystemOptions = {\n texture: string\n\n count: RandomRange\n lifespan: RandomRange\n angle: RandomRange\n velocity: RandomRange\n particleScale: RandomRange\n\n startAlpha?: number\n fadeRate: number\n orientToVelocity: boolean\n\n blendMode?: BLEND_MODES // ex) 'screen', 'multiply'\n\n // [성능 최적화] 오브젝트 풀 크기 설정 (기본값: 100)\n poolSize?: number\n} & DomGameObjectOptions\n\ninterface Particle {\n el: HTMLDivElement\n\n // [성능 최적화] 활성 상태 플래그 및 위치/투명도 캐시\n active: boolean\n x: number\n y: number\n opacity: number\n\n age: number\n lifespan: number\n\n velocityX: number\n velocityY: number\n\n fadeRate: number\n}\n\nfunction random(min: number, max: number) {\n return Math.random() * (max - min) + min\n}\n\nexport class DomParticleSystem extends DomGameObject {\n #textureSrc: string\n #count: RandomRange\n #lifespan: RandomRange\n #angle: RandomRange\n #velocity: RandomRange\n #scale: RandomRange\n #startAlpha?: number\n #fadeRate: number\n #orientToVelocity: boolean\n #blendMode?: BLEND_MODES\n\n #texture?: HTMLImageElement\n #loadTexturePromise: Promise<void>\n #particles: Particle[] = []\n\n // [성능 최적화] DOM 엘리먼트 풀 - 재사용으로 DOM 조작 및 GC 부담 감소\n #elementPool: HTMLDivElement[] = []\n #poolSize: number\n\n constructor(options: DomParticleSystemOptions) {\n super(options)\n this.el.style.pointerEvents = 'none'\n\n this.#textureSrc = options.texture\n this.#count = options.count\n this.#lifespan = options.lifespan\n this.#angle = options.angle\n this.#velocity = options.velocity\n this.#scale = options.particleScale\n this.#startAlpha = options.startAlpha\n this.#fadeRate = options.fadeRate\n this.#orientToVelocity = options.orientToVelocity\n this.#blendMode = options.blendMode\n this.#poolSize = options.poolSize ?? 100\n\n this.#loadTexturePromise = this.#loadTexture()\n }\n\n async #loadTexture() {\n if (domTextureLoader.checkCached(this.#textureSrc)) {\n this.#texture = domTextureLoader.getCached(this.#textureSrc)\n } else {\n console.info(`Dom texture not preloaded. Loading now: ${this.#textureSrc}`)\n this.#texture = await domTextureLoader.load(this.#textureSrc)\n }\n }\n\n // [성능 최적화] 풀에서 엘리먼트 가져오거나 새로 생성\n #acquireElement(x: number, y: number, scale: number, angle: number): HTMLDivElement {\n let el = this.#elementPool.pop()\n\n if (el) {\n // 풀에서 가져온 엘리먼트 재설정\n setStyle(el, {\n left: `${x}px`,\n top: `${y}px`,\n transform: `translate(-50%, -50%) scale(${scale})${this.#orientToVelocity ? ` rotate(${angle}rad)` : ''}`,\n opacity: `${this.#startAlpha ?? 1}`,\n display: 'block',\n })\n } else {\n // 풀이 비어있으면 새로 생성\n el = document.createElement('div')\n setStyle(el, {\n position: 'absolute',\n left: `${x}px`,\n top: `${y}px`,\n width: `${this.#texture!.width}px`,\n height: `${this.#texture!.height}px`,\n transform: `translate(-50%, -50%) scale(${scale})${this.#orientToVelocity ? ` rotate(${angle}rad)` : ''}`,\n backgroundImage: `url(${this.#textureSrc})`,\n backgroundSize: 'contain',\n backgroundRepeat: 'no-repeat',\n opacity: `${this.#startAlpha ?? 1}`,\n mixBlendMode: this.#blendMode ?? 'normal',\n })\n this.el.appendChild(el)\n }\n\n return el\n }\n\n // [성능 최적화] 엘리먼트를 풀로 반환 (DOM에서 제거 대신)\n #releaseElement(el: HTMLDivElement) {\n if (this.#elementPool.length < this.#poolSize) {\n el.style.display = 'none'\n this.#elementPool.push(el)\n } else {\n el.remove()\n }\n }\n\n async burst({ x, y }: { x: number; y: number }) {\n if (!this.#texture) await this.#loadTexturePromise\n\n const count = random(this.#count.min, this.#count.max)\n for (let i = 0; i < count; i++) {\n const lifespan = random(this.#lifespan.min, this.#lifespan.max)\n const angle = random(this.#angle.min, this.#angle.max)\n const sin = Math.sin(angle)\n const cos = Math.cos(angle)\n const velocity = random(this.#velocity.min, this.#velocity.max)\n const scale = random(this.#scale.min, this.#scale.max)\n\n const el = this.#acquireElement(x, y, scale, angle)\n\n this.#particles.push({\n el,\n active: true,\n x,\n y,\n opacity: this.#startAlpha ?? 1,\n age: 0,\n lifespan,\n velocityX: velocity * cos,\n velocityY: velocity * sin,\n fadeRate: this.#fadeRate,\n })\n }\n }\n\n protected override update(dt: number) {\n super.update(dt)\n\n const ps = this.#particles\n\n // [성능 최적화] swap-and-pop 패턴으로 O(n) splice 비용 제거\n let writeIdx = 0\n for (let readIdx = 0; readIdx < ps.length; readIdx++) {\n const p = ps[readIdx]\n\n p.age += dt\n\n if (p.age > p.lifespan) {\n // [성능 최적화] DOM에서 제거 대신 풀로 반환\n this.#releaseElement(p.el)\n p.active = false\n continue\n }\n\n // [성능 최적화] parseFloat 대신 캐시된 값 사용\n p.x += p.velocityX * dt\n p.y += p.velocityY * dt\n p.opacity += p.fadeRate * dt\n\n setStyle(p.el, { left: `${p.x}px`, top: `${p.y}px`, opacity: `${p.opacity}` })\n\n // 활성 파티클을 앞으로 이동\n if (writeIdx !== readIdx) {\n ps[writeIdx] = p\n }\n writeIdx++\n }\n\n // 비활성 파티클 제거 (한 번에 배열 크기 조정)\n ps.length = writeIdx\n }\n\n override remove() {\n // 풀에 있는 엘리먼트도 정리\n for (const el of this.#elementPool) {\n el.remove()\n }\n this.#elementPool.length = 0\n\n domTextureLoader.release(this.#textureSrc)\n super.remove()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dom-particle.js","sourceRoot":"","sources":["../../src/dom/dom-particle.ts"],"names":[],"mappings":"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","sourcesContent":["import { BLEND_MODES } from 'pixi.js'\nimport { DomGameObject, DomGameObjectOptions } from './dom-game-object'\nimport { domTextureLoader } from './dom-texture-loader'\nimport { setStyle } from './dom-utils'\n\ntype RandomRange = { min: number, max: number }\n\nexport type DomParticleSystemOptions = {\n texture: string\n\n count: RandomRange\n lifespan: RandomRange\n angle: RandomRange\n velocity: RandomRange\n particleScale: RandomRange\n\n startAlpha?: number\n fadeRate: number\n orientToVelocity: boolean\n\n blendMode?: BLEND_MODES // ex) 'screen', 'multiply'\n} & DomGameObjectOptions\n\ninterface Particle {\n el: HTMLDivElement\n\n age: number\n lifespan: number\n\n velocityX: number\n velocityY: number\n\n fadeRate: number\n}\n\nfunction random(min: number, max: number) {\n return Math.random() * (max - min) + min\n}\n\nexport class DomParticleSystem extends DomGameObject {\n #textureSrc: string\n #count: RandomRange\n #lifespan: RandomRange\n #angle: RandomRange\n #velocity: RandomRange\n #scale: RandomRange\n #startAlpha?: number\n #fadeRate: number\n #orientToVelocity: boolean\n #blendMode?: BLEND_MODES\n\n #texture?: HTMLImageElement\n #loadTexturePromise: Promise<void>\n #particles: Particle[] = []\n\n constructor(options: DomParticleSystemOptions) {\n super(options)\n this.el.style.pointerEvents = 'none'\n\n this.#textureSrc = options.texture\n this.#count = options.count\n this.#lifespan = options.lifespan\n this.#angle = options.angle\n this.#velocity = options.velocity\n this.#scale = options.particleScale\n this.#startAlpha = options.startAlpha\n this.#fadeRate = options.fadeRate\n this.#orientToVelocity = options.orientToVelocity\n this.#blendMode = options.blendMode\n\n this.#loadTexturePromise = this.#loadTexture()\n }\n\n async #loadTexture() {\n if (domTextureLoader.checkCached(this.#textureSrc)) {\n this.#texture = domTextureLoader.getCached(this.#textureSrc)\n } else {\n console.info(`Dom texture not preloaded. Loading now: ${this.#textureSrc}`)\n this.#texture = await domTextureLoader.load(this.#textureSrc)\n }\n }\n\n async burst({ x, y }: { x: number; y: number }) {\n if (!this.#texture) await this.#loadTexturePromise\n\n const count = random(this.#count.min, this.#count.max)\n for (let i = 0; i < count; i++) {\n const lifespan = random(this.#lifespan.min, this.#lifespan.max)\n const angle = random(this.#angle.min, this.#angle.max)\n const sin = Math.sin(angle)\n const cos = Math.cos(angle)\n const velocity = random(this.#velocity.min, this.#velocity.max)\n const scale = random(this.#scale.min, this.#scale.max)\n\n const el = document.createElement('div')\n setStyle(el, {\n position: 'absolute',\n left: `${x}px`,\n top: `${y}px`,\n width: `${this.#texture!.width}px`,\n height: `${this.#texture!.height}px`,\n transform: `translate(-50%, -50%) scale(${scale})${this.#orientToVelocity ? ` rotate(${angle}rad)` : ''}`,\n backgroundImage: `url(${this.#textureSrc})`,\n backgroundSize: 'contain',\n backgroundRepeat: 'no-repeat',\n opacity: `${this.#startAlpha ?? 1}`,\n mixBlendMode: this.#blendMode ?? 'normal',\n })\n\n this.#particles.push({\n el,\n age: 0,\n lifespan,\n velocityX: velocity * cos,\n velocityY: velocity * sin,\n fadeRate: this.#fadeRate,\n })\n\n this.el.appendChild(el)\n }\n }\n\n protected override update(dt: number) {\n super.update(dt)\n\n const ps = this.#particles\n for (let i = 0; i < ps.length; i++) {\n const p = ps[i]\n const e = p.el\n\n p.age += dt\n if (p.age > p.lifespan) {\n e.remove()\n ps.splice(i, 1)\n i--\n continue\n }\n\n const x = parseFloat(e.style.left) + p.velocityX * dt\n const y = parseFloat(e.style.top) + p.velocityY * dt\n const opacity = parseFloat(e.style.opacity) + p.fadeRate * dt\n\n setStyle(e, { left: `${x}px`, top: `${y}px`, opacity: `${opacity}` })\n }\n }\n\n override remove() {\n domTextureLoader.release(this.#textureSrc)\n super.remove()\n }\n}\n"]}
|
|
@@ -2,10 +2,7 @@ import { Container as PixiContainer } from 'pixi.js';
|
|
|
2
2
|
import { TransformableNode } from './transformable';
|
|
3
3
|
export class GameObject extends TransformableNode {
|
|
4
4
|
constructor(options) {
|
|
5
|
-
|
|
6
|
-
// useYSort 사용 시 또는 명시적으로 요청한 경우에만 정렬 활성화
|
|
7
|
-
const needsSorting = options?.sortableChildren ?? options?.useYSort ?? false;
|
|
8
|
-
super(new PixiContainer({ sortableChildren: needsSorting }), options ?? {});
|
|
5
|
+
super(new PixiContainer({ sortableChildren: true }), options ?? {});
|
|
9
6
|
}
|
|
10
7
|
}
|
|
11
8
|
//# sourceMappingURL=game-object.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-object.js","sourceRoot":"","sources":["../../../src/node/core/game-object.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,SAAS,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAA4B,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"game-object.js","sourceRoot":"","sources":["../../../src/node/core/game-object.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,SAAS,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAA4B,MAAM,iBAAiB,CAAA;AAI7E,MAAM,OAAO,UAAoC,SAAQ,iBAAmC;IAC1F,YAAY,OAA2B;QACrC,KAAK,CAAC,IAAI,aAAa,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC,CAAA;IACrE,CAAC;CACF","sourcesContent":["import { EventMap } from '@webtaku/event-emitter'\nimport { Container as PixiContainer } from 'pixi.js'\nimport { TransformableNode, TransformableNodeOptions } from './transformable'\n\nexport type GameObjectOptions = {} & TransformableNodeOptions\n\nexport class GameObject<E extends EventMap = {}> extends TransformableNode<PixiContainer, E> {\n constructor(options?: GameObjectOptions) {\n super(new PixiContainer({ sortableChildren: true }), options ?? {})\n }\n}\n"]}
|
|
@@ -39,15 +39,17 @@ export class RenderableNode extends GameNode {
|
|
|
39
39
|
this._pixiContainer.destroy({ children: true });
|
|
40
40
|
super.remove();
|
|
41
41
|
}
|
|
42
|
-
// [성능 최적화] 기존 2-pass → 1-pass로 통합
|
|
43
|
-
// 기존: _updateWorldTransform() + _resetWorldTransformDirty() 를 별도 호출
|
|
44
|
-
// 개선: 한 번의 순회에서 트랜스폼 업데이트와 dirty 리셋을 동시 처리
|
|
45
42
|
_updateWorldTransform() {
|
|
46
43
|
for (const child of this.children) {
|
|
47
44
|
if (isRenderableNode(child))
|
|
48
45
|
child._updateWorldTransform();
|
|
49
46
|
}
|
|
50
|
-
|
|
47
|
+
}
|
|
48
|
+
_resetWorldTransformDirty() {
|
|
49
|
+
for (const child of this.children) {
|
|
50
|
+
if (isRenderableNode(child))
|
|
51
|
+
child._resetWorldTransformDirty();
|
|
52
|
+
}
|
|
51
53
|
this.worldTransform.resetDirty();
|
|
52
54
|
this.worldAlpha.resetDirty();
|
|
53
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderable.js","sourceRoot":"","sources":["../../../src/node/core/renderable.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAE5C,MAAM,UAAU,gBAAgB,CAAC,CAAU;IACzC,OAAQ,CAA6C,CAAC,cAAc,KAAK,SAAS,CAAA;AACpF,CAAC;AAED,MAAM,OAAgB,cAA4D,SAAQ,QAAW;IACnG,SAAS,CAAW;IACpB,cAAc,CAAG;IAEjB,cAAc,GAAG,IAAI,cAAc,EAAE,CAAA;IACrC,UAAU,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAA;IAE/B,YAAY,aAAgB;QAC1B,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,cAAc,GAAG,aAAa,CAAA;IACrC,CAAC;IAED,IAAc,QAAQ,CAAC,QAA8B;QACnD,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAA;QAEzB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAc,QAAQ;QACpB,OAAO,IAAI,CAAC,SAAS,CAAA;IACvB,CAAC;IAEQ,GAAG,CAAC,GAAG,QAA8B;QAC5C,KAAK,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAA;QAEtB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;gBAElD,SAAS;gBACT,IAAI,IAAI,CAAC,SAAS;oBAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAA;YACrD,CAAC;QACH,CAAC;IACH,CAAC;IAEQ,MAAM;QACb,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/C,KAAK,CAAC,MAAM,EAAE,CAAA;IAChB,CAAC;IAED,
|
|
1
|
+
{"version":3,"file":"renderable.js","sourceRoot":"","sources":["../../../src/node/core/renderable.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAE5C,MAAM,UAAU,gBAAgB,CAAC,CAAU;IACzC,OAAQ,CAA6C,CAAC,cAAc,KAAK,SAAS,CAAA;AACpF,CAAC;AAED,MAAM,OAAgB,cAA4D,SAAQ,QAAW;IACnG,SAAS,CAAW;IACpB,cAAc,CAAG;IAEjB,cAAc,GAAG,IAAI,cAAc,EAAE,CAAA;IACrC,UAAU,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAA;IAE/B,YAAY,aAAgB;QAC1B,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,cAAc,GAAG,aAAa,CAAA;IACrC,CAAC;IAED,IAAc,QAAQ,CAAC,QAA8B;QACnD,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAA;QAEzB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAc,QAAQ;QACpB,OAAO,IAAI,CAAC,SAAS,CAAA;IACvB,CAAC;IAEQ,GAAG,CAAC,GAAG,QAA8B;QAC5C,KAAK,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAA;QAEtB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;gBAElD,SAAS;gBACT,IAAI,IAAI,CAAC,SAAS;oBAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAA;YACrD,CAAC;QACH,CAAC;IACH,CAAC;IAEQ,MAAM;QACb,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/C,KAAK,CAAC,MAAM,EAAE,CAAA;IAChB,CAAC;IAED,qBAAqB;QACnB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,gBAAgB,CAAC,KAAK,CAAC;gBAAE,KAAK,CAAC,qBAAqB,EAAE,CAAA;QAC5D,CAAC;IACH,CAAC;IAED,yBAAyB;QACvB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,gBAAgB,CAAC,KAAK,CAAC;gBAAE,KAAK,CAAC,yBAAyB,EAAE,CAAA;QAChE,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,CAAA;QAChC,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAA;IAC9B,CAAC;IAED,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,CAAA,CAAC,CAAC;IAC5C,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,CAAA,CAAC,CAAC;IAE9C,IAAI,KAAK,IAAI,CAAC,cAAc,CAAC,OAAO,GAAG,KAAK,CAAA,CAAC,CAAC;IAC9C,IAAI,KAAK,IAAI,CAAC,cAAc,CAAC,OAAO,GAAG,IAAI,CAAA,CAAC,CAAC;CAC9C","sourcesContent":["import { EventMap } from '@webtaku/event-emitter'\nimport { Container as PixiContainer } from 'pixi.js'\nimport { Renderer } from '../../renderer/renderer'\nimport { DirtyNumber } from './dirty-number'\nimport { GameNode } from './game-node'\nimport { WorldTransform } from './transform'\n\nexport function isRenderableNode(v: unknown): v is RenderableNode<PixiContainer, EventMap> {\n return (v as RenderableNode<PixiContainer, EventMap>).worldTransform !== undefined\n}\n\nexport abstract class RenderableNode<C extends PixiContainer, E extends EventMap> extends GameNode<E> {\n #renderer?: Renderer\n _pixiContainer: C\n\n worldTransform = new WorldTransform()\n worldAlpha = new DirtyNumber(1)\n\n constructor(pixiContainer: C) {\n super()\n this._pixiContainer = pixiContainer\n }\n\n protected set renderer(renderer: Renderer | undefined) {\n this.#renderer = renderer\n\n for (const child of this.children) {\n if (isRenderableNode(child)) {\n child.renderer = renderer\n }\n }\n }\n\n protected get renderer() {\n return this.#renderer\n }\n\n override add(...children: GameNode<EventMap>[]) {\n super.add(...children)\n\n for (const child of children) {\n if (isRenderableNode(child)) {\n this._pixiContainer.addChild(child._pixiContainer)\n\n // 렌더러 설정\n if (this.#renderer) child.renderer = this.#renderer\n }\n }\n }\n\n override remove() {\n this._pixiContainer.destroy({ children: true })\n super.remove()\n }\n\n _updateWorldTransform() {\n for (const child of this.children) {\n if (isRenderableNode(child)) child._updateWorldTransform()\n }\n }\n\n _resetWorldTransformDirty() {\n for (const child of this.children) {\n if (isRenderableNode(child)) child._resetWorldTransformDirty()\n }\n this.worldTransform.resetDirty()\n this.worldAlpha.resetDirty()\n }\n\n set tint(t) { this._pixiContainer.tint = t }\n get tint() { return this._pixiContainer.tint }\n\n hide() { this._pixiContainer.visible = false }\n show() { this._pixiContainer.visible = true }\n}\n"]}
|
|
@@ -5,10 +5,6 @@ export class TransformableNode extends RenderableNode {
|
|
|
5
5
|
alpha = 1;
|
|
6
6
|
#layer;
|
|
7
7
|
#useYSort = false;
|
|
8
|
-
// [성능 최적화] useYSort용 이전 y값 캐시 - y가 변할 때만 drawOrder 업데이트
|
|
9
|
-
#prevY = NaN;
|
|
10
|
-
// [성능 최적화] 이전 alpha 캐시 - 변경 시에만 Pixi에 반영
|
|
11
|
-
#prevAlpha = NaN;
|
|
12
8
|
constructor(pixiContainer, options) {
|
|
13
9
|
super(pixiContainer);
|
|
14
10
|
if (options.x !== undefined)
|
|
@@ -51,50 +47,23 @@ export class TransformableNode extends RenderableNode {
|
|
|
51
47
|
}
|
|
52
48
|
const pc = this._pixiContainer;
|
|
53
49
|
const renderer = this.renderer;
|
|
54
|
-
const wt = this.worldTransform;
|
|
55
50
|
// 레이어 상에 있는 경우, 독립적으로 업데이트
|
|
56
51
|
if (this.#layer && renderer) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (wt.scaleX.dirty || wt.scaleY.dirty) {
|
|
63
|
-
pc.scale.set(wt.scaleX.v, wt.scaleY.v);
|
|
64
|
-
}
|
|
65
|
-
if (wt.rotation.dirty) {
|
|
66
|
-
pc.rotation = wt.rotation.v;
|
|
67
|
-
}
|
|
68
|
-
if (this.worldAlpha.dirty) {
|
|
69
|
-
pc.alpha = this.worldAlpha.v;
|
|
70
|
-
}
|
|
52
|
+
const wt = this.worldTransform;
|
|
53
|
+
pc.position.set(wt.x.v, wt.y.v);
|
|
54
|
+
pc.scale.set(wt.scaleX.v, wt.scaleY.v);
|
|
55
|
+
pc.rotation = wt.rotation.v;
|
|
56
|
+
pc.alpha = this.worldAlpha.v;
|
|
71
57
|
}
|
|
72
58
|
else {
|
|
73
59
|
const lt = this.localTransform;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (wt.x.dirty || wt.y.dirty) {
|
|
77
|
-
pc.position.set(lt.x, lt.y);
|
|
78
|
-
}
|
|
79
|
-
// [성능 최적화] useYSort: y가 실제로 변경된 경우에만 drawOrder 업데이트
|
|
80
|
-
// 기존: 매 프레임 zIndex 설정 → 부모 컨테이너 정렬 비용 발생
|
|
81
|
-
// 개선: y 변경 시에만 zIndex 업데이트
|
|
82
|
-
if (this.#useYSort && lt.y !== this.#prevY) {
|
|
60
|
+
pc.position.set(lt.x, lt.y);
|
|
61
|
+
if (this.#useYSort)
|
|
83
62
|
this.drawOrder = lt.y;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
pc.scale.set(lt.scaleX, lt.scaleY);
|
|
89
|
-
}
|
|
90
|
-
if (wt.rotation.dirty) {
|
|
91
|
-
pc.rotation = lt.rotation;
|
|
92
|
-
}
|
|
93
|
-
// [성능 최적화] alpha 변경 시에만 업데이트
|
|
94
|
-
if (this.alpha !== this.#prevAlpha) {
|
|
95
|
-
pc.alpha = this.alpha;
|
|
96
|
-
this.#prevAlpha = this.alpha;
|
|
97
|
-
}
|
|
63
|
+
pc.pivot.set(lt.pivotX, lt.pivotY);
|
|
64
|
+
pc.scale.set(lt.scaleX, lt.scaleY);
|
|
65
|
+
pc.rotation = lt.rotation;
|
|
66
|
+
pc.alpha = this.alpha;
|
|
98
67
|
}
|
|
99
68
|
super._updateWorldTransform();
|
|
100
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformable.js","sourceRoot":"","sources":["../../../src/node/core/transformable.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAkB5C,MAAM,OAAgB,iBAA+D,SAAQ,cAAoB;IACrG,cAAc,GAAG,IAAI,cAAc,EAAE,CAAA;IAE/C,KAAK,GAAG,CAAC,CAAA;IACT,MAAM,CAAS;IACf,SAAS,GAAG,KAAK,CAAA;IAEjB,wDAAwD;IACxD,MAAM,GAAG,GAAG,CAAA;IAEZ,yCAAyC;IACzC,UAAU,GAAG,GAAG,CAAA;IAEhB,YAAY,aAAgB,EAAE,OAAiC;QAC7D,KAAK,CAAC,aAAa,CAAC,CAAA;QAEpB,IAAI,OAAO,CAAC,CAAC,KAAK,SAAS;YAAE,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAA;QAC/C,IAAI,OAAO,CAAC,CAAC,KAAK,SAAS;YAAE,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAA;QAC/C,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAC3D,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC9D,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;QACpE,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAC3D,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAA;QAEvE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAA;QAC3B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAA;IAC5C,CAAC;IAED,IAAuB,QAAQ,CAAC,QAA8B;QAC5D,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;QAEzB,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC5B,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QACzC,CAAC;IACH,CAAC;IAED,IAAuB,QAAQ;QAC7B,OAAO,KAAK,CAAC,QAAQ,CAAA;IACvB,CAAC;IAEQ,qBAAqB;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;QAC1B,IAAI,MAAM,IAAI,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,cAAc,CAAC,CAAA;YACtE,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAA;QACtD,CAAC;QAED,MAAM,EAAE,GAAG,IAAI,CAAC,cAAc,CAAA;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;QAC9B,MAAM,EAAE,GAAG,IAAI,CAAC,cAAc,CAAA;QAE9B,2BAA2B;QAC3B,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC5B,+CAA+C;YAC/C,mDAAmD;YACnD,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;gBAC7B,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACjC,CAAC;YACD,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBACvC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;YACxC,CAAC;YACD,IAAI,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACtB,EAAE,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;YAC7B,CAAC;YACD,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;gBAC1B,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAA;YAC9B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,GAAG,IAAI,CAAC,cAAc,CAAA;YAE9B,gCAAgC;YAChC,oCAAoC;YACpC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;gBAC7B,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;YAC7B,CAAC;YAED,oDAAoD;YACpD,yCAAyC;YACzC,2BAA2B;YAC3B,IAAI,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC3C,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC,CAAA;gBACrB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAA;YACpB,CAAC;YAED,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBACvC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAA;gBAClC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAA;YACpC,CAAC;YACD,IAAI,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACtB,EAAE,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAA;YAC3B,CAAC;YAED,6BAA6B;YAC7B,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACnC,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;gBACrB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAA;YAC9B,CAAC;QACH,CAAC;QAED,KAAK,CAAC,qBAAqB,EAAE,CAAA;IAC/B,CAAC;IAED,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,CAAA,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC,CAAA,CAAC,CAAC;IAExC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,CAAA,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC,CAAA,CAAC,CAAC;IAExC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAA,CAAC,CAAC;IAC/E,IAAI,KAAK,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAA,CAAC,CAAC;IAEjD,IAAI,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAA,CAAC,CAAC;IAChD,IAAI,MAAM,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAA,CAAC,CAAC;IAElD,IAAI,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAA,CAAC,CAAC;IAChD,IAAI,MAAM,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAA,CAAC,CAAC;IAElD,IAAI,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAA,CAAC,CAAC;IAChD,IAAI,MAAM,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAA,CAAC,CAAC;IAElD,IAAI,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAA,CAAC,CAAC;IAChD,IAAI,MAAM,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAA,CAAC,CAAC;IAElD,IAAI,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,QAAQ,GAAG,CAAC,CAAA,CAAC,CAAC;IACpD,IAAI,QAAQ,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAA,CAAC,CAAC;IAEtD,IAAI,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAA,CAAC,CAAC;IACnD,IAAI,SAAS,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAA,CAAC,CAAC;CACtD","sourcesContent":["import { EventMap } from '@webtaku/event-emitter'\nimport { Container as PixiContainer } from 'pixi.js'\nimport { Renderer } from '../../renderer/renderer'\nimport { isRenderableNode, RenderableNode } from './renderable'\nimport { LocalTransform } from './transform'\n\nexport type TransformableNodeOptions = {\n x?: number\n y?: number\n scale?: number\n scaleX?: number\n scaleY?: number\n pivotX?: number\n pivotY?: number\n rotation?: number\n drawOrder?: number\n\n alpha?: number\n layer?: string\n useYSort?: boolean\n}\n\nexport abstract class TransformableNode<C extends PixiContainer, E extends EventMap> extends RenderableNode<C, E> {\n protected localTransform = new LocalTransform()\n\n alpha = 1\n #layer?: string\n #useYSort = false\n\n // [성능 최적화] useYSort용 이전 y값 캐시 - y가 변할 때만 drawOrder 업데이트\n #prevY = NaN\n\n // [성능 최적화] 이전 alpha 캐시 - 변경 시에만 Pixi에 반영\n #prevAlpha = NaN\n\n constructor(pixiContainer: C, options: TransformableNodeOptions) {\n super(pixiContainer)\n\n if (options.x !== undefined) this.x = options.x\n if (options.y !== undefined) this.y = options.y\n if (options.scale !== undefined) this.scale = options.scale\n if (options.scaleX !== undefined) this.scaleX = options.scaleX\n if (options.scaleY !== undefined) this.scaleY = options.scaleY\n if (options.pivotX !== undefined) this.pivotX = options.pivotX\n if (options.pivotY !== undefined) this.pivotY = options.pivotY\n if (options.rotation !== undefined) this.rotation = options.rotation\n if (options.alpha !== undefined) this.alpha = options.alpha\n if (options.drawOrder !== undefined) this.drawOrder = options.drawOrder\n\n this.#layer = options.layer\n this.#useYSort = options.useYSort ?? false\n }\n\n protected override set renderer(renderer: Renderer | undefined) {\n super.renderer = renderer\n\n if (this.#layer && renderer) {\n renderer._addToLayer(this, this.#layer)\n }\n }\n\n protected override get renderer() {\n return super.renderer\n }\n\n override _updateWorldTransform() {\n const parent = this.parent\n if (parent && isRenderableNode(parent)) {\n this.worldTransform.update(parent.worldTransform, this.localTransform)\n this.worldAlpha.v = parent.worldAlpha.v * this.alpha\n }\n\n const pc = this._pixiContainer\n const renderer = this.renderer\n const wt = this.worldTransform\n\n // 레이어 상에 있는 경우, 독립적으로 업데이트\n if (this.#layer && renderer) {\n // [성능 최적화] dirty 체크 - 값이 변경된 경우에만 Pixi 속성 업데이트\n // Pixi 내부에서도 dirty 체크를 하지만, 함수 호출 자체를 줄이는 것이 더 효율적\n if (wt.x.dirty || wt.y.dirty) {\n pc.position.set(wt.x.v, wt.y.v)\n }\n if (wt.scaleX.dirty || wt.scaleY.dirty) {\n pc.scale.set(wt.scaleX.v, wt.scaleY.v)\n }\n if (wt.rotation.dirty) {\n pc.rotation = wt.rotation.v\n }\n if (this.worldAlpha.dirty) {\n pc.alpha = this.worldAlpha.v\n }\n } else {\n const lt = this.localTransform\n\n // [성능 최적화] 로컬 트랜스폼도 dirty 체크 적용\n // DOM 쪽(dom-game-object.ts)과 동일한 패턴\n if (wt.x.dirty || wt.y.dirty) {\n pc.position.set(lt.x, lt.y)\n }\n\n // [성능 최적화] useYSort: y가 실제로 변경된 경우에만 drawOrder 업데이트\n // 기존: 매 프레임 zIndex 설정 → 부모 컨테이너 정렬 비용 발생\n // 개선: y 변경 시에만 zIndex 업데이트\n if (this.#useYSort && lt.y !== this.#prevY) {\n this.drawOrder = lt.y\n this.#prevY = lt.y\n }\n\n if (wt.scaleX.dirty || wt.scaleY.dirty) {\n pc.pivot.set(lt.pivotX, lt.pivotY)\n pc.scale.set(lt.scaleX, lt.scaleY)\n }\n if (wt.rotation.dirty) {\n pc.rotation = lt.rotation\n }\n\n // [성능 최적화] alpha 변경 시에만 업데이트\n if (this.alpha !== this.#prevAlpha) {\n pc.alpha = this.alpha\n this.#prevAlpha = this.alpha\n }\n }\n\n super._updateWorldTransform()\n }\n\n set x(v) { this.localTransform.x = v }\n get x() { return this.localTransform.x }\n\n set y(v) { this.localTransform.y = v }\n get y() { return this.localTransform.y }\n\n set scale(v) { this.localTransform.scaleX = v; this.localTransform.scaleY = v }\n get scale() { return this.localTransform.scaleX }\n\n set scaleX(v) { this.localTransform.scaleX = v }\n get scaleX() { return this.localTransform.scaleX }\n\n set scaleY(v) { this.localTransform.scaleY = v }\n get scaleY() { return this.localTransform.scaleY }\n\n set pivotX(v) { this.localTransform.pivotX = v }\n get pivotX() { return this.localTransform.pivotX }\n\n set pivotY(v) { this.localTransform.pivotY = v }\n get pivotY() { return this.localTransform.pivotY }\n\n set rotation(v) { this.localTransform.rotation = v }\n get rotation() { return this.localTransform.rotation }\n\n set drawOrder(v) { this._pixiContainer.zIndex = v }\n get drawOrder() { return this._pixiContainer.zIndex }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"transformable.js","sourceRoot":"","sources":["../../../src/node/core/transformable.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventMap } from '@webtaku/event-emitter'\nimport { Container as PixiContainer } from 'pixi.js'\nimport { Renderer } from '../../renderer/renderer'\nimport { isRenderableNode, RenderableNode } from './renderable'\nimport { LocalTransform } from './transform'\n\nexport type TransformableNodeOptions = {\n x?: number\n y?: number\n scale?: number\n scaleX?: number\n scaleY?: number\n pivotX?: number\n pivotY?: number\n rotation?: number\n drawOrder?: number\n\n alpha?: number\n layer?: string\n useYSort?: boolean\n}\n\nexport abstract class TransformableNode<C extends PixiContainer, E extends EventMap> extends RenderableNode<C, E> {\n protected localTransform = new LocalTransform()\n\n alpha = 1\n #layer?: string\n #useYSort = false\n\n constructor(pixiContainer: C, options: TransformableNodeOptions) {\n super(pixiContainer)\n\n if (options.x !== undefined) this.x = options.x\n if (options.y !== undefined) this.y = options.y\n if (options.scale !== undefined) this.scale = options.scale\n if (options.scaleX !== undefined) this.scaleX = options.scaleX\n if (options.scaleY !== undefined) this.scaleY = options.scaleY\n if (options.pivotX !== undefined) this.pivotX = options.pivotX\n if (options.pivotY !== undefined) this.pivotY = options.pivotY\n if (options.rotation !== undefined) this.rotation = options.rotation\n if (options.alpha !== undefined) this.alpha = options.alpha\n if (options.drawOrder !== undefined) this.drawOrder = options.drawOrder\n\n this.#layer = options.layer\n this.#useYSort = options.useYSort ?? false\n }\n\n protected override set renderer(renderer: Renderer | undefined) {\n super.renderer = renderer\n\n if (this.#layer && renderer) {\n renderer._addToLayer(this, this.#layer)\n }\n }\n\n protected override get renderer() {\n return super.renderer\n }\n\n override _updateWorldTransform() {\n const parent = this.parent\n if (parent && isRenderableNode(parent)) {\n this.worldTransform.update(parent.worldTransform, this.localTransform)\n this.worldAlpha.v = parent.worldAlpha.v * this.alpha\n }\n\n const pc = this._pixiContainer\n const renderer = this.renderer\n\n // 레이어 상에 있는 경우, 독립적으로 업데이트\n if (this.#layer && renderer) {\n const wt = this.worldTransform\n pc.position.set(wt.x.v, wt.y.v)\n pc.scale.set(wt.scaleX.v, wt.scaleY.v)\n pc.rotation = wt.rotation.v\n pc.alpha = this.worldAlpha.v\n } else {\n const lt = this.localTransform\n pc.position.set(lt.x, lt.y)\n if (this.#useYSort) this.drawOrder = lt.y\n pc.pivot.set(lt.pivotX, lt.pivotY)\n pc.scale.set(lt.scaleX, lt.scaleY)\n pc.rotation = lt.rotation\n pc.alpha = this.alpha\n }\n\n super._updateWorldTransform()\n }\n\n set x(v) { this.localTransform.x = v }\n get x() { return this.localTransform.x }\n\n set y(v) { this.localTransform.y = v }\n get y() { return this.localTransform.y }\n\n set scale(v) { this.localTransform.scaleX = v; this.localTransform.scaleY = v }\n get scale() { return this.localTransform.scaleX }\n\n set scaleX(v) { this.localTransform.scaleX = v }\n get scaleX() { return this.localTransform.scaleX }\n\n set scaleY(v) { this.localTransform.scaleY = v }\n get scaleY() { return this.localTransform.scaleY }\n\n set pivotX(v) { this.localTransform.pivotX = v }\n get pivotX() { return this.localTransform.pivotX }\n\n set pivotY(v) { this.localTransform.pivotY = v }\n get pivotY() { return this.localTransform.pivotY }\n\n set rotation(v) { this.localTransform.rotation = v }\n get rotation() { return this.localTransform.rotation }\n\n set drawOrder(v) { this._pixiContainer.zIndex = v }\n get drawOrder() { return this._pixiContainer.zIndex }\n}\n"]}
|
|
@@ -9,8 +9,6 @@ export class AnimatedSpriteNode extends GameObject {
|
|
|
9
9
|
#sheet;
|
|
10
10
|
#sprite;
|
|
11
11
|
#baseFps = 60;
|
|
12
|
-
// [성능 최적화] 이전 worldTimeScale 캐시 - 변경 시에만 animationSpeed 업데이트
|
|
13
|
-
#prevWorldTimeScale = NaN;
|
|
14
12
|
constructor(options) {
|
|
15
13
|
super(options);
|
|
16
14
|
this.#src = options.src;
|
|
@@ -45,7 +43,6 @@ export class AnimatedSpriteNode extends GameObject {
|
|
|
45
43
|
s.loop = a.loop;
|
|
46
44
|
this.#baseFps = a.fps;
|
|
47
45
|
s.animationSpeed = (a.fps / 60) * this.worldTimeScale;
|
|
48
|
-
this.#prevWorldTimeScale = this.worldTimeScale;
|
|
49
46
|
s.play();
|
|
50
47
|
s.onLoop = () => this.emit('animationend', this.#animation);
|
|
51
48
|
s.onComplete = () => this.emit('animationend', this.#animation);
|
|
@@ -77,12 +74,8 @@ export class AnimatedSpriteNode extends GameObject {
|
|
|
77
74
|
get animation() { return this.#animation; }
|
|
78
75
|
update(dt) {
|
|
79
76
|
super.update(dt);
|
|
80
|
-
|
|
81
|
-
// 기존: 매 프레임 animationSpeed 재설정
|
|
82
|
-
// 개선: 캐시된 값과 비교하여 변경 시에만 업데이트
|
|
83
|
-
if (this.#sprite && this.worldTimeScale !== this.#prevWorldTimeScale) {
|
|
77
|
+
if (this.#sprite) {
|
|
84
78
|
this.#sprite.animationSpeed = (this.#baseFps / 60) * this.worldTimeScale;
|
|
85
|
-
this.#prevWorldTimeScale = this.worldTimeScale;
|
|
86
79
|
}
|
|
87
80
|
}
|
|
88
81
|
remove() {
|