feat: refactor audio management by introducing setup functions for music and sound effects, enhancing modularity and maintainability
All checks were successful
Build and Publish Docker Image / Build and Validate (pull_request) Successful in 10s
All checks were successful
Build and Publish Docker Image / Build and Validate (pull_request) Successful in 10s
This commit is contained in:
parent
2213f64e60
commit
5a24d6a2af
6 changed files with 375 additions and 80 deletions
|
|
@ -8,15 +8,23 @@ import type { Sequence } from '../core/Music.ts';
|
||||||
export class Music extends Component {
|
export class Music extends Component {
|
||||||
sequences: Map<string, Sequence>;
|
sequences: Map<string, Sequence>;
|
||||||
currentSequence: Sequence | null;
|
currentSequence: Sequence | null;
|
||||||
|
activeSequences: Set<Sequence>;
|
||||||
volume: number;
|
volume: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
private sequenceChain: string[];
|
||||||
|
private currentChainIndex: number;
|
||||||
|
private sequenceVolumes: Map<Sequence, number>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(ComponentType.MUSIC);
|
super(ComponentType.MUSIC);
|
||||||
this.sequences = new Map();
|
this.sequences = new Map();
|
||||||
this.currentSequence = null;
|
this.currentSequence = null;
|
||||||
|
this.activeSequences = new Set();
|
||||||
this.volume = 0.5;
|
this.volume = 0.5;
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
|
this.sequenceChain = [];
|
||||||
|
this.currentChainIndex = 0;
|
||||||
|
this.sequenceVolumes = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,10 +57,91 @@ export class Music extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play multiple sequences simultaneously (polyphony).
|
||||||
|
* @param sequenceConfigs - Array of configs with name, optional delay in beats, and optional loop
|
||||||
|
*/
|
||||||
|
playSequences(sequenceConfigs: Array<{ name: string; delay?: number; loop?: boolean }>): void {
|
||||||
|
if (!this.enabled || sequenceConfigs.length === 0) return;
|
||||||
|
|
||||||
|
const firstSeq = this.sequences.get(sequenceConfigs[0].name);
|
||||||
|
if (!firstSeq || !firstSeq.ac) return;
|
||||||
|
|
||||||
|
const ac = firstSeq.ac;
|
||||||
|
const when = ac.currentTime;
|
||||||
|
const tempo = firstSeq.tempo || 120;
|
||||||
|
|
||||||
|
sequenceConfigs.forEach((config) => {
|
||||||
|
const sequence = this.sequences.get(config.name);
|
||||||
|
if (!sequence) return;
|
||||||
|
|
||||||
|
if (config.loop !== undefined) {
|
||||||
|
sequence.loop = config.loop;
|
||||||
|
}
|
||||||
|
if (sequence.gain) {
|
||||||
|
sequence.gain.gain.value = this.volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
const delaySeconds = config.delay ? (60 / tempo) * config.delay : 0;
|
||||||
|
sequence.play(when + delaySeconds);
|
||||||
|
this.activeSequences.add(sequence);
|
||||||
|
|
||||||
|
if (!this.currentSequence) {
|
||||||
|
this.currentSequence = sequence;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chain multiple sequences together in order (sequential playback).
|
||||||
|
* @param sequenceNames - Array of sequence names to play in order
|
||||||
|
*/
|
||||||
|
chainSequences(sequenceNames: string[]): void {
|
||||||
|
if (!this.enabled || sequenceNames.length === 0) return;
|
||||||
|
|
||||||
|
this.stop();
|
||||||
|
this.sequenceChain = sequenceNames;
|
||||||
|
this.currentChainIndex = 0;
|
||||||
|
|
||||||
|
this.playNextInChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play the next sequence in the chain.
|
||||||
|
*/
|
||||||
|
private playNextInChain(): void {
|
||||||
|
if (!this.enabled || this.sequenceChain.length === 0) return;
|
||||||
|
|
||||||
|
const seqName = this.sequenceChain[this.currentChainIndex];
|
||||||
|
const sequence = this.sequences.get(seqName);
|
||||||
|
if (!sequence) return;
|
||||||
|
|
||||||
|
this.currentSequence = sequence;
|
||||||
|
sequence.loop = false;
|
||||||
|
if (sequence.gain) {
|
||||||
|
sequence.gain.gain.value = this.volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence.play();
|
||||||
|
if (sequence.osc) {
|
||||||
|
const nextIndex = (this.currentChainIndex + 1) % this.sequenceChain.length;
|
||||||
|
sequence.osc.onended = () => {
|
||||||
|
if (this.enabled) {
|
||||||
|
this.currentChainIndex = nextIndex;
|
||||||
|
this.playNextInChain();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop current playback.
|
* Stop current playback.
|
||||||
*/
|
*/
|
||||||
stop(): void {
|
stop(): void {
|
||||||
|
this.activeSequences.forEach((seq) => {
|
||||||
|
seq.stop();
|
||||||
|
});
|
||||||
|
this.activeSequences.clear();
|
||||||
if (this.currentSequence) {
|
if (this.currentSequence) {
|
||||||
this.currentSequence.stop();
|
this.currentSequence.stop();
|
||||||
this.currentSequence = null;
|
this.currentSequence = null;
|
||||||
|
|
@ -85,4 +174,61 @@ export class Music extends Component {
|
||||||
this.stop();
|
this.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause all active sequences by setting their gain to 0.
|
||||||
|
*/
|
||||||
|
pause(): void {
|
||||||
|
this.activeSequences.forEach((seq) => {
|
||||||
|
if (seq.gain) {
|
||||||
|
const currentVolume = seq.gain.gain.value;
|
||||||
|
this.sequenceVolumes.set(seq, currentVolume);
|
||||||
|
seq.gain.gain.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.currentSequence && this.currentSequence.gain) {
|
||||||
|
const currentVolume = this.currentSequence.gain.gain.value;
|
||||||
|
this.sequenceVolumes.set(this.currentSequence, currentVolume);
|
||||||
|
this.currentSequence.gain.gain.value = 0;
|
||||||
|
}
|
||||||
|
this.sequences.forEach((seq) => {
|
||||||
|
if (seq.gain && seq.gain.gain.value > 0) {
|
||||||
|
const currentVolume = seq.gain.gain.value;
|
||||||
|
this.sequenceVolumes.set(seq, currentVolume);
|
||||||
|
seq.gain.gain.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume all active sequences by restoring their volume.
|
||||||
|
*/
|
||||||
|
resume(): void {
|
||||||
|
this.activeSequences.forEach((seq) => {
|
||||||
|
if (seq.gain) {
|
||||||
|
const savedVolume = this.sequenceVolumes.get(seq);
|
||||||
|
if (savedVolume !== undefined) {
|
||||||
|
seq.gain.gain.value = savedVolume;
|
||||||
|
} else {
|
||||||
|
seq.gain.gain.value = this.volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.currentSequence && this.currentSequence.gain) {
|
||||||
|
const savedVolume = this.sequenceVolumes.get(this.currentSequence);
|
||||||
|
if (savedVolume !== undefined) {
|
||||||
|
this.currentSequence.gain.gain.value = savedVolume;
|
||||||
|
} else {
|
||||||
|
this.currentSequence.gain.gain.value = this.volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.sequences.forEach((seq) => {
|
||||||
|
if (seq.gain) {
|
||||||
|
const savedVolume = this.sequenceVolumes.get(seq);
|
||||||
|
if (savedVolume !== undefined) {
|
||||||
|
seq.gain.gain.value = savedVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
167
src/config/MusicConfig.ts
Normal file
167
src/config/MusicConfig.ts
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
import { Sequence } from '../core/Music.ts';
|
||||||
|
import type { Music } from '../components/Music.ts';
|
||||||
|
import type { MusicSystem } from '../systems/MusicSystem.ts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure and setup background music.
|
||||||
|
* @param music - Music component instance
|
||||||
|
* @param audioCtx - AudioContext instance
|
||||||
|
*/
|
||||||
|
export function setupMusic(music: Music, audioCtx: AudioContext): void {
|
||||||
|
const tempo = 132;
|
||||||
|
|
||||||
|
const lead = new Sequence(audioCtx, tempo, [
|
||||||
|
'F4 e',
|
||||||
|
'Ab4 e',
|
||||||
|
'C5 e',
|
||||||
|
'F5 e',
|
||||||
|
'C5 e',
|
||||||
|
'Ab4 e',
|
||||||
|
'F4 e',
|
||||||
|
'C4 e',
|
||||||
|
'F4 e',
|
||||||
|
'Ab4 e',
|
||||||
|
'C5 e',
|
||||||
|
'F5 e',
|
||||||
|
'C5 e',
|
||||||
|
'Ab4 e',
|
||||||
|
'F4 e',
|
||||||
|
'C4 e',
|
||||||
|
'G4 e',
|
||||||
|
'Bb4 e',
|
||||||
|
'D5 e',
|
||||||
|
'G5 e',
|
||||||
|
'D5 e',
|
||||||
|
'Bb4 e',
|
||||||
|
'G4 e',
|
||||||
|
'D4 e',
|
||||||
|
'F4 e',
|
||||||
|
'Ab4 e',
|
||||||
|
'C5 e',
|
||||||
|
'F5 e',
|
||||||
|
'C5 e',
|
||||||
|
'Ab4 e',
|
||||||
|
'F4 e',
|
||||||
|
'C4 e',
|
||||||
|
]);
|
||||||
|
lead.staccato = 0.1;
|
||||||
|
lead.smoothing = 0.3;
|
||||||
|
lead.waveType = 'triangle';
|
||||||
|
lead.loop = true;
|
||||||
|
if (lead.gain) {
|
||||||
|
lead.gain.gain.value = 0.8;
|
||||||
|
}
|
||||||
|
music.addSequence('lead', lead);
|
||||||
|
|
||||||
|
const harmony = new Sequence(audioCtx, tempo, [
|
||||||
|
'C4 e',
|
||||||
|
'Eb4 e',
|
||||||
|
'F4 e',
|
||||||
|
'Ab4 e',
|
||||||
|
'F4 e',
|
||||||
|
'Eb4 e',
|
||||||
|
'C4 e',
|
||||||
|
'Ab3 e',
|
||||||
|
'C4 e',
|
||||||
|
'Eb4 e',
|
||||||
|
'F4 e',
|
||||||
|
'Ab4 e',
|
||||||
|
'F4 e',
|
||||||
|
'Eb4 e',
|
||||||
|
'C4 e',
|
||||||
|
'Ab3 e',
|
||||||
|
'D4 e',
|
||||||
|
'F4 e',
|
||||||
|
'G4 e',
|
||||||
|
'Bb4 e',
|
||||||
|
'G4 e',
|
||||||
|
'F4 e',
|
||||||
|
'D4 e',
|
||||||
|
'Bb3 e',
|
||||||
|
'C4 e',
|
||||||
|
'Eb4 e',
|
||||||
|
'F4 e',
|
||||||
|
'Ab4 e',
|
||||||
|
'F4 e',
|
||||||
|
'Eb4 e',
|
||||||
|
'C4 e',
|
||||||
|
'Ab3 e',
|
||||||
|
]);
|
||||||
|
harmony.staccato = 0.15;
|
||||||
|
harmony.smoothing = 0.4;
|
||||||
|
harmony.waveType = 'triangle';
|
||||||
|
harmony.loop = true;
|
||||||
|
if (harmony.gain) {
|
||||||
|
harmony.gain.gain.value = 0.6;
|
||||||
|
}
|
||||||
|
music.addSequence('harmony', harmony);
|
||||||
|
|
||||||
|
const bass = new Sequence(audioCtx, tempo, [
|
||||||
|
'F2 q',
|
||||||
|
'C3 q',
|
||||||
|
'F2 q',
|
||||||
|
'C3 q',
|
||||||
|
'G2 q',
|
||||||
|
'D3 q',
|
||||||
|
'G2 q',
|
||||||
|
'D3 q',
|
||||||
|
'F2 q',
|
||||||
|
'C3 q',
|
||||||
|
'F2 q',
|
||||||
|
'C3 q',
|
||||||
|
]);
|
||||||
|
bass.staccato = 0.05;
|
||||||
|
bass.smoothing = 0.5;
|
||||||
|
bass.waveType = 'triangle';
|
||||||
|
bass.loop = true;
|
||||||
|
if (bass.gain) {
|
||||||
|
bass.gain.gain.value = 0.7;
|
||||||
|
}
|
||||||
|
if (bass.bass) {
|
||||||
|
bass.bass.gain.value = 4;
|
||||||
|
bass.bass.frequency.value = 80;
|
||||||
|
}
|
||||||
|
music.addSequence('bass', bass);
|
||||||
|
|
||||||
|
music.playSequences([
|
||||||
|
{ name: 'lead', loop: true },
|
||||||
|
{ name: 'harmony', loop: true },
|
||||||
|
{ name: 'bass', loop: true },
|
||||||
|
]);
|
||||||
|
music.setVolume(0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup music event handlers for canvas interaction.
|
||||||
|
* @param music - Music component instance
|
||||||
|
* @param musicSystem - MusicSystem instance
|
||||||
|
* @param canvas - Canvas element
|
||||||
|
*/
|
||||||
|
export function setupMusicHandlers(
|
||||||
|
music: Music,
|
||||||
|
musicSystem: MusicSystem,
|
||||||
|
canvas: HTMLCanvasElement
|
||||||
|
): void {
|
||||||
|
canvas.addEventListener('click', () => {
|
||||||
|
musicSystem.resumeAudioContext();
|
||||||
|
if (music.enabled && music.activeSequences.size === 0) {
|
||||||
|
music.playSequences([
|
||||||
|
{ name: 'lead', loop: true },
|
||||||
|
{ name: 'harmony', loop: true },
|
||||||
|
{ name: 'bass', loop: true },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
canvas.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
musicSystem.resumeAudioContext();
|
||||||
|
if (music.enabled) {
|
||||||
|
music.playSequences([
|
||||||
|
{ name: 'lead', loop: true },
|
||||||
|
{ name: 'harmony', loop: true },
|
||||||
|
{ name: 'bass', loop: true },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
35
src/config/SFXConfig.ts
Normal file
35
src/config/SFXConfig.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Sequence } from '../core/Music.ts';
|
||||||
|
import type { SoundEffects } from '../components/SoundEffects.ts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure and setup sound effects.
|
||||||
|
* @param sfx - SoundEffects component instance
|
||||||
|
* @param ac - AudioContext instance
|
||||||
|
*/
|
||||||
|
export function setupSFX(sfx: SoundEffects, ac: AudioContext): void {
|
||||||
|
const attackSound = new Sequence(ac, 120, ['C5 s']);
|
||||||
|
attackSound.staccato = 0.8;
|
||||||
|
sfx.addSound('attack', attackSound);
|
||||||
|
|
||||||
|
const absorbSound = new Sequence(ac, 120, ['G4 e']);
|
||||||
|
absorbSound.staccato = 0.5;
|
||||||
|
sfx.addSound('absorb', absorbSound);
|
||||||
|
|
||||||
|
const skillSound = new Sequence(ac, 120, ['A4 e']);
|
||||||
|
skillSound.staccato = 0.6;
|
||||||
|
sfx.addSound('skill', skillSound);
|
||||||
|
|
||||||
|
const damageSound = new Sequence(ac, 120, ['F4 s']);
|
||||||
|
damageSound.staccato = 0.8;
|
||||||
|
sfx.addSound('damage', damageSound);
|
||||||
|
|
||||||
|
const shootSound = new Sequence(ac, 120, ['C5 s']);
|
||||||
|
shootSound.staccato = 0.9;
|
||||||
|
sfx.addSound('shoot', shootSound);
|
||||||
|
|
||||||
|
const impactSound = new Sequence(ac, 120, ['G4 s']);
|
||||||
|
impactSound.staccato = 0.7;
|
||||||
|
sfx.addSound('impact', impactSound);
|
||||||
|
|
||||||
|
sfx.setVolume(0.02);
|
||||||
|
}
|
||||||
|
|
@ -143,7 +143,7 @@ export class Engine {
|
||||||
| undefined;
|
| undefined;
|
||||||
const gameState = menuSystem ? menuSystem.getGameState() : GameState.PLAYING;
|
const gameState = menuSystem ? menuSystem.getGameState() : GameState.PLAYING;
|
||||||
const isPaused = [GameState.PAUSED, GameState.START, GameState.GAME_OVER].includes(gameState);
|
const isPaused = [GameState.PAUSED, GameState.START, GameState.GAME_OVER].includes(gameState);
|
||||||
const unskippedSystems = [SystemName.MENU, SystemName.UI, SystemName.RENDER];
|
const unskippedSystems = [SystemName.MENU, SystemName.UI, SystemName.RENDER, SystemName.MUSIC];
|
||||||
|
|
||||||
this.systems.forEach((system) => {
|
this.systems.forEach((system) => {
|
||||||
if (isPaused && !unskippedSystems.includes(system.name as SystemName)) {
|
if (isPaused && !unskippedSystems.includes(system.name as SystemName)) {
|
||||||
|
|
|
||||||
84
src/main.ts
84
src/main.ts
|
|
@ -36,8 +36,9 @@ import { Music } from './components/Music.ts';
|
||||||
import { SoundEffects } from './components/SoundEffects.ts';
|
import { SoundEffects } from './components/SoundEffects.ts';
|
||||||
|
|
||||||
import { EntityType, ComponentType } from './core/Constants.ts';
|
import { EntityType, ComponentType } from './core/Constants.ts';
|
||||||
import { Sequence } from './core/Music.ts';
|
|
||||||
import type { Entity } from './core/Entity.ts';
|
import type { Entity } from './core/Entity.ts';
|
||||||
|
import { setupMusic, setupMusicHandlers } from './config/MusicConfig.ts';
|
||||||
|
import { setupSFX } from './config/SFXConfig.ts';
|
||||||
|
|
||||||
const canvas = document.getElementById('game-canvas') as HTMLCanvasElement;
|
const canvas = document.getElementById('game-canvas') as HTMLCanvasElement;
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
|
|
@ -156,90 +157,19 @@ if (!canvas) {
|
||||||
if (musicSystem) {
|
if (musicSystem) {
|
||||||
const musicEntity = engine.createEntity();
|
const musicEntity = engine.createEntity();
|
||||||
const music = new Music();
|
const music = new Music();
|
||||||
const ac = musicSystem.getAudioContext();
|
const audioCtx = musicSystem.getAudioContext();
|
||||||
|
|
||||||
const bgMusic = new Sequence(ac, 140, [
|
setupMusic(music, audioCtx);
|
||||||
'C4 e',
|
|
||||||
'E4 e',
|
|
||||||
'G4 q',
|
|
||||||
'C5 e',
|
|
||||||
'G4 e',
|
|
||||||
'E4 q',
|
|
||||||
'A3 e',
|
|
||||||
'C4 e',
|
|
||||||
'E4 q',
|
|
||||||
'A4 e',
|
|
||||||
'E4 e',
|
|
||||||
'C4 q',
|
|
||||||
'F3 e',
|
|
||||||
'A3 e',
|
|
||||||
'C4 q',
|
|
||||||
'F4 e',
|
|
||||||
'C4 e',
|
|
||||||
'A3 q',
|
|
||||||
'G3 e',
|
|
||||||
'B3 e',
|
|
||||||
'D4 q',
|
|
||||||
'G4 e',
|
|
||||||
'D4 e',
|
|
||||||
'B3 q',
|
|
||||||
]);
|
|
||||||
bgMusic.loop = true;
|
|
||||||
bgMusic.staccato = 0.2;
|
|
||||||
bgMusic.smoothing = 0.4;
|
|
||||||
bgMusic.waveType = 'triangle';
|
|
||||||
music.addSequence('background', bgMusic);
|
|
||||||
music.setVolume(0.02);
|
|
||||||
musicEntity.addComponent(music);
|
musicEntity.addComponent(music);
|
||||||
|
setupMusicHandlers(music, musicSystem, canvas);
|
||||||
canvas.addEventListener('click', () => {
|
|
||||||
musicSystem.resumeAudioContext();
|
|
||||||
if (music.enabled && !music.currentSequence) {
|
|
||||||
music.playSequence('background');
|
|
||||||
}
|
|
||||||
canvas.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
musicSystem.resumeAudioContext();
|
|
||||||
if (music.enabled) {
|
|
||||||
music.playSequence('background');
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
const sfxSystem = engine.systems.find((s) => s.name === 'SoundEffectsSystem') as
|
const sfxSystem = engine.systems.find((s) => s.name === 'SoundEffectsSystem') as
|
||||||
| SoundEffectsSystem
|
| SoundEffectsSystem
|
||||||
| undefined;
|
| undefined;
|
||||||
if (sfxSystem) {
|
if (sfxSystem) {
|
||||||
const sfxEntity = engine.createEntity();
|
const sfxEntity = engine.createEntity();
|
||||||
const sfx = new SoundEffects(ac);
|
const sfx = new SoundEffects(audioCtx);
|
||||||
|
setupSFX(sfx, audioCtx);
|
||||||
const attackSound = new Sequence(ac, 120, ['C5 s']);
|
|
||||||
attackSound.staccato = 0.8;
|
|
||||||
sfx.addSound('attack', attackSound);
|
|
||||||
|
|
||||||
const absorbSound = new Sequence(ac, 120, ['G4 e']);
|
|
||||||
absorbSound.staccato = 0.5;
|
|
||||||
sfx.addSound('absorb', absorbSound);
|
|
||||||
|
|
||||||
const skillSound = new Sequence(ac, 120, ['A4 e']);
|
|
||||||
skillSound.staccato = 0.6;
|
|
||||||
sfx.addSound('skill', skillSound);
|
|
||||||
|
|
||||||
const damageSound = new Sequence(ac, 120, ['F4 s']);
|
|
||||||
damageSound.staccato = 0.8;
|
|
||||||
sfx.addSound('damage', damageSound);
|
|
||||||
|
|
||||||
const shootSound = new Sequence(ac, 120, ['C5 s']);
|
|
||||||
shootSound.staccato = 0.9;
|
|
||||||
sfx.addSound('shoot', shootSound);
|
|
||||||
|
|
||||||
const impactSound = new Sequence(ac, 120, ['G4 s']);
|
|
||||||
impactSound.staccato = 0.7;
|
|
||||||
sfx.addSound('impact', impactSound);
|
|
||||||
|
|
||||||
sfx.setVolume(0.02);
|
|
||||||
|
|
||||||
sfxEntity.addComponent(sfx);
|
sfxEntity.addComponent(sfx);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,23 @@
|
||||||
import { System } from '../core/System.ts';
|
import { System } from '../core/System.ts';
|
||||||
import { SystemName, ComponentType } from '../core/Constants.ts';
|
import { SystemName, ComponentType, GameState } from '../core/Constants.ts';
|
||||||
import type { Entity } from '../core/Entity.ts';
|
import type { Entity } from '../core/Entity.ts';
|
||||||
import type { Engine } from '../core/Engine.ts';
|
import type { Engine } from '../core/Engine.ts';
|
||||||
import type { Music } from '../components/Music.ts';
|
import type { Music } from '../components/Music.ts';
|
||||||
|
import type { MenuSystem } from './MenuSystem.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System responsible for managing background music playback.
|
* System responsible for managing background music playback.
|
||||||
*/
|
*/
|
||||||
export class MusicSystem extends System {
|
export class MusicSystem extends System {
|
||||||
private audioContext: AudioContext | null;
|
private audioContext: AudioContext | null;
|
||||||
|
private wasPaused: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(SystemName.MUSIC);
|
super(SystemName.MUSIC);
|
||||||
this.requiredComponents = [ComponentType.MUSIC];
|
this.requiredComponents = [ComponentType.MUSIC];
|
||||||
this.priority = 5;
|
this.priority = 5;
|
||||||
this.audioContext = null;
|
this.audioContext = null;
|
||||||
|
this.wasPaused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,9 +28,15 @@ export class MusicSystem extends System {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process music entities - currently just ensures audio context exists.
|
* Process music entities - ensures audio context exists and handles pause/resume.
|
||||||
*/
|
*/
|
||||||
process(_deltaTime: number, entities: Entity[]): void {
|
process(_deltaTime: number, entities: Entity[]): void {
|
||||||
|
const menuSystem = this.engine.systems.find((s) => s.name === SystemName.MENU) as
|
||||||
|
| MenuSystem
|
||||||
|
| undefined;
|
||||||
|
const gameState = menuSystem ? menuSystem.getGameState() : GameState.PLAYING;
|
||||||
|
const isPaused = gameState === GameState.PAUSED;
|
||||||
|
|
||||||
entities.forEach((entity) => {
|
entities.forEach((entity) => {
|
||||||
const music = entity.getComponent<Music>(ComponentType.MUSIC);
|
const music = entity.getComponent<Music>(ComponentType.MUSIC);
|
||||||
if (!music) return;
|
if (!music) return;
|
||||||
|
|
@ -35,6 +44,14 @@ export class MusicSystem extends System {
|
||||||
if (!this.audioContext) {
|
if (!this.audioContext) {
|
||||||
this.audioContext = new AudioContext();
|
this.audioContext = new AudioContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPaused && !this.wasPaused) {
|
||||||
|
music.pause();
|
||||||
|
this.wasPaused = true;
|
||||||
|
} else if (!isPaused && this.wasPaused) {
|
||||||
|
music.resume();
|
||||||
|
this.wasPaused = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue