Replace game audio with Pixabay tracks

This commit is contained in:
2026-06-22 12:22:09 +09:00
parent 381b2cf14c
commit 89f2251d0b
21 changed files with 112 additions and 7 deletions

View File

@@ -1,21 +1,33 @@
type AudioTrackMap = Record<string, string>;
type PlayEffectOptions = {
volume?: number;
rate?: number;
stopAfterMs?: number;
};
class SoundDirector {
private context?: AudioContext;
private master?: GainNode;
private started = false;
private muted = false;
private musicTracks: AudioTrackMap = {};
private effectTracks: AudioTrackMap = {};
private music?: HTMLAudioElement;
private currentMusicKey?: string;
private pendingMusicKey?: string;
private musicFadeTimer?: number;
private readonly musicVolume = 0.14;
private readonly musicVolume = 0.22;
private readonly effectVolume = 0.42;
registerMusicTracks(tracks: AudioTrackMap) {
this.musicTracks = tracks;
}
registerEffectTracks(tracks: AudioTrackMap) {
this.effectTracks = tracks;
}
start() {
if (this.started) {
this.resume();
@@ -70,10 +82,48 @@ class SoundDirector {
}
playSelect() {
if (this.playEffect('ui-select', { volume: 0.24, stopAfterMs: 420 })) {
return;
}
this.playTone(300, 0.07, 0.04, 'sine');
window.setTimeout(() => this.playTone(450, 0.08, 0.028, 'sine'), 70);
}
playEffect(key: string, options: PlayEffectOptions = {}) {
if (!this.started || this.muted) {
return false;
}
const src = this.effectTracks[key];
if (!src) {
return false;
}
const effect = new Audio(src);
effect.preload = 'auto';
effect.volume = options.volume ?? this.effectVolume;
effect.playbackRate = options.rate ?? 1;
effect.muted = this.muted;
effect.addEventListener(
'ended',
() => {
effect.src = '';
},
{ once: true }
);
if (options.stopAfterMs) {
window.setTimeout(() => {
effect.pause();
effect.src = '';
}, options.stopAfterMs);
}
void effect.play().catch(() => undefined);
return true;
}
playMusic(key?: string) {
if (!key) {
return;

View File

@@ -1,8 +1,14 @@
import battlePrepUrl from '../../assets/audio/bgm/battle-prep.wav';
import militiaThemeUrl from '../../assets/audio/bgm/militia-theme.wav';
import oathThemeUrl from '../../assets/audio/bgm/oath-theme.wav';
import storyDarkUrl from '../../assets/audio/bgm/story-dark.wav';
import titleThemeUrl from '../../assets/audio/bgm/title-theme.wav';
import battlePrepUrl from '../../assets/audio/bgm/battle-prep.mp3';
import militiaThemeUrl from '../../assets/audio/bgm/militia-theme.mp3';
import oathThemeUrl from '../../assets/audio/bgm/oath-theme.mp3';
import storyDarkUrl from '../../assets/audio/bgm/story-dark.mp3';
import titleThemeUrl from '../../assets/audio/bgm/title-theme.mp3';
import cartRollUrl from '../../assets/audio/sfx/cart-roll.mp3';
import combatImpactUrl from '../../assets/audio/sfx/combat-impact.mp3';
import expGainUrl from '../../assets/audio/sfx/exp-gain.mp3';
import strategyCastUrl from '../../assets/audio/sfx/strategy-cast.mp3';
import swordSlashUrl from '../../assets/audio/sfx/sword-slash.mp3';
import uiSelectUrl from '../../assets/audio/sfx/ui-select.mp3';
export const musicTracks = {
'title-theme': titleThemeUrl,
@@ -12,4 +18,14 @@ export const musicTracks = {
'battle-prep': battlePrepUrl
} as const;
export const effectTracks = {
'ui-select': uiSelectUrl,
'sword-slash': swordSlashUrl,
'combat-impact': combatImpactUrl,
'strategy-cast': strategyCastUrl,
'cart-roll': cartRollUrl,
'exp-gain': expGainUrl
} as const;
export type MusicTrackKey = keyof typeof musicTracks;
export type EffectTrackKey = keyof typeof effectTracks;

View File

@@ -1270,6 +1270,10 @@ export class BattleScene extends Phaser.Scene {
await this.animateCombatGauge(defenderStatus.hpFill, result.previousDefenderHp / result.defender.maxHp, result.defender.hp / result.defender.maxHp, 420);
defenderStatus.hpText.setText(`${result.defender.hp} / ${result.defender.maxHp}`);
if (result.characterGrowth.amount > 0) {
soundDirector.playEffect('exp-gain', { volume: result.characterGrowth.leveled ? 0.44 : 0.28, stopAfterMs: 1000 });
}
await this.animateCombatGauge(
attackerStatus.expFill,
result.characterGrowth.previousExp / result.characterGrowth.next,
@@ -1400,6 +1404,7 @@ export class BattleScene extends Phaser.Scene {
) {
const direction = result.attacker.faction === 'ally' ? 1 : -1;
if (result.action === 'attack') {
soundDirector.playEffect('sword-slash', { volume: 0.52, stopAfterMs: 700 });
this.tweens.add({
targets: attackerSprite,
x: attackerX + direction * 84,
@@ -1418,6 +1423,11 @@ export class BattleScene extends Phaser.Scene {
return;
}
soundDirector.playEffect(result.action === 'item' ? 'cart-roll' : 'strategy-cast', {
volume: result.action === 'item' ? 0.34 : 0.44,
stopAfterMs: result.action === 'item' ? 720 : 950
});
const projectile =
result.action === 'item'
? this.createCombatCart(attackerX + direction * 78, groundY - 10, depth)
@@ -1462,6 +1472,8 @@ export class BattleScene extends Phaser.Scene {
}
private showCombatImpact(result: CombatResult, x: number, y: number, depth: number) {
soundDirector.playEffect('combat-impact', { volume: result.action === 'strategy' ? 0.3 : 0.48, stopAfterMs: 650 });
const impact = this.trackCombatObject(this.add.star(x, y, 8, 12, 42, result.action === 'strategy' ? 0x6cc5ff : 0xffdf7b, 0.92));
impact.setDepth(depth);
this.tweens.add({