feat: improve tactical audiovisual clarity

This commit is contained in:
2026-07-22 20:08:23 +09:00
parent d3af88c2a9
commit 8bda36d46d
5 changed files with 663 additions and 58 deletions

View File

@@ -6,6 +6,8 @@ export type AudioCategoryMultipliers = Record<AudioCategory, number>;
export type EffectPoolMap = Record<string, readonly string[]>;
export type StrategyImpactKind = 'fire' | 'roar' | 'arcane';
export type SoundscapeOptions = {
musicKey?: string;
ambienceKey?: string;
@@ -74,6 +76,16 @@ type MusicDuckState = {
};
};
type PlayedEffectDebug = {
key: string;
trackKey: string;
poolKey: string | null;
at: number;
baseVolume: number;
volume: number;
rate: number;
};
export class SoundDirector {
private context?: AudioContext;
private master?: GainNode;
@@ -116,15 +128,8 @@ export class SoundDirector {
at: number;
remainingMs: number;
};
private lastEffect?: {
key: string;
trackKey: string;
poolKey: string | null;
at: number;
baseVolume: number;
volume: number;
rate: number;
};
private lastEffect?: PlayedEffectDebug;
private readonly recentEffects: PlayedEffectDebug[] = [];
private lastMovementStep?: { key: string; mounted: boolean; stepIndex: number; at: number; volume?: number; rate?: number };
private readonly effectVolume = 0.42;
@@ -385,6 +390,35 @@ export class SoundDirector {
}
}
playStrategyImpact(kind: StrategyImpactKind, critical: boolean) {
if (kind === 'fire') {
this.playEffect('burn-tick', {
volume: critical ? 0.34 : 0.28,
rate: critical ? 0.86 : 0.94,
stopAfterMs: 680
});
this.playTone(critical ? 150 : 185, 0.11, critical ? 0.026 : 0.018, 'sawtooth');
} else if (kind === 'roar') {
this.playEffect('combat-impact', {
volume: critical ? 0.4 : 0.3,
rate: critical ? 0.72 : 0.8,
stopAfterMs: 980
});
this.playTone(critical ? 92 : 118, 0.16, critical ? 0.03 : 0.022, 'triangle');
} else {
this.playEffect('strategy-cast', {
volume: critical ? 0.34 : 0.26,
rate: critical ? 1.12 : 1.2,
stopAfterMs: 720
});
this.playTone(critical ? 820 : 690, 0.12, critical ? 0.022 : 0.016, 'sine');
}
if (critical) {
this.playCriticalAccent();
}
}
playBattleOutcome(outcome: 'victory' | 'defeat') {
this.duckMusic({
multiplier: outcome === 'victory' ? 0.34 : 0.24,
@@ -496,7 +530,7 @@ export class SoundDirector {
effect.muted = this.muted;
this.activeEffects.add(effect);
this.effectBaseVolumes.set(effect, baseVolume);
this.lastEffect = {
const playedEffect = {
key,
trackKey: resolvedTrack.trackKey,
poolKey: resolvedTrack.poolKey,
@@ -505,6 +539,11 @@ export class SoundDirector {
volume: effect.volume,
rate: effect.playbackRate
};
this.lastEffect = playedEffect;
this.recentEffects.push(playedEffect);
if (this.recentEffects.length > 12) {
this.recentEffects.shift();
}
effect.addEventListener(
'ended',
() => {
@@ -597,6 +636,7 @@ export class SoundDirector {
),
activeEffectCount: this.activeEffects.size,
lastEffect: this.lastEffect ?? null,
recentEffects: this.recentEffects.map((effect) => ({ ...effect })),
lastMovementStep: this.lastMovementStep ?? null
};
}