Improve battle unit feedback and detail panel
This commit is contained in:
@@ -17,6 +17,8 @@ class SoundDirector {
|
||||
private currentMusicKey?: string;
|
||||
private pendingMusicKey?: string;
|
||||
private musicFadeTimer?: number;
|
||||
private lastEffect?: { key: string; at: number; volume: number; rate: number };
|
||||
private lastMovementStep?: { key: string; mounted: boolean; stepIndex: number; at: number; volume?: number; rate?: number };
|
||||
private readonly musicVolume = 0.22;
|
||||
private readonly effectVolume = 0.42;
|
||||
|
||||
@@ -118,6 +120,14 @@ class SoundDirector {
|
||||
this.playEffect('footstep-walk', { volume: 0.2, rate: 1.35, stopAfterMs: 320 });
|
||||
}
|
||||
|
||||
playMovementStep(mounted: boolean, stepIndex: number, options: PlayEffectOptions = {}) {
|
||||
const key = mounted ? 'horse-gallop' : 'footstep-walk';
|
||||
this.lastMovementStep = { key, mounted, stepIndex, at: performance.now(), volume: options.volume, rate: options.rate };
|
||||
const played = this.playEffect(key, options);
|
||||
this.playStepPulse(mounted, stepIndex, options.volume);
|
||||
return played;
|
||||
}
|
||||
|
||||
playMeleeSwing(hit: boolean, critical: boolean) {
|
||||
this.playEffect('sword-slash', {
|
||||
volume: critical ? 0.62 : hit ? 0.52 : 0.38,
|
||||
@@ -169,6 +179,7 @@ class SoundDirector {
|
||||
effect.volume = options.volume ?? this.effectVolume;
|
||||
effect.playbackRate = options.rate ?? 1;
|
||||
effect.muted = this.muted;
|
||||
this.lastEffect = { key, at: performance.now(), volume: effect.volume, rate: effect.playbackRate };
|
||||
effect.addEventListener(
|
||||
'ended',
|
||||
() => {
|
||||
@@ -188,6 +199,18 @@ class SoundDirector {
|
||||
return true;
|
||||
}
|
||||
|
||||
getDebugState() {
|
||||
return {
|
||||
started: this.started,
|
||||
muted: this.muted,
|
||||
contextState: this.context?.state ?? null,
|
||||
currentMusicKey: this.currentMusicKey ?? null,
|
||||
pendingMusicKey: this.pendingMusicKey ?? null,
|
||||
lastEffect: this.lastEffect ?? null,
|
||||
lastMovementStep: this.lastMovementStep ?? null
|
||||
};
|
||||
}
|
||||
|
||||
playMusic(key?: string) {
|
||||
if (!key) {
|
||||
return;
|
||||
@@ -248,6 +271,51 @@ class SoundDirector {
|
||||
oscillator.stop(now + duration + 0.04);
|
||||
}
|
||||
|
||||
private playStepPulse(mounted: boolean, stepIndex: number, volume = 0.2) {
|
||||
if (!this.context || this.muted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scale = Math.max(0.35, Math.min(1.2, volume / 0.24));
|
||||
const start = this.context.currentTime;
|
||||
const primaryFrequency = mounted ? 112 + (stepIndex % 2) * 26 : 148 + (stepIndex % 2) * 34;
|
||||
const duration = mounted ? 0.09 : 0.075;
|
||||
const gainValue = (mounted ? 0.022 : 0.016) * scale;
|
||||
|
||||
this.playPercussivePulse(primaryFrequency, start, duration, gainValue, mounted ? 'square' : 'triangle');
|
||||
|
||||
if (mounted) {
|
||||
this.playPercussivePulse(82 + (stepIndex % 2) * 18, start + 0.055, 0.075, gainValue * 0.78, 'square');
|
||||
}
|
||||
}
|
||||
|
||||
private playPercussivePulse(
|
||||
frequency: number,
|
||||
start: number,
|
||||
duration: number,
|
||||
gainValue: number,
|
||||
type: OscillatorType
|
||||
) {
|
||||
if (!this.context || this.muted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oscillator = this.context.createOscillator();
|
||||
const gain = this.context.createGain();
|
||||
|
||||
oscillator.type = type;
|
||||
oscillator.frequency.setValueAtTime(frequency, start);
|
||||
oscillator.frequency.exponentialRampToValueAtTime(Math.max(40, frequency * 0.62), start + duration);
|
||||
gain.gain.setValueAtTime(0.0001, start);
|
||||
gain.gain.linearRampToValueAtTime(gainValue, start + 0.006);
|
||||
gain.gain.exponentialRampToValueAtTime(0.0001, start + duration);
|
||||
|
||||
oscillator.connect(gain);
|
||||
gain.connect(this.master ?? this.context.destination);
|
||||
oscillator.start(start);
|
||||
oscillator.stop(start + duration + 0.025);
|
||||
}
|
||||
|
||||
private fadeMusic(previous: HTMLAudioElement | undefined, next: HTMLAudioElement, targetVolume: number) {
|
||||
if (this.musicFadeTimer) {
|
||||
window.clearInterval(this.musicFadeTimer);
|
||||
|
||||
Reference in New Issue
Block a user