feat: deepen battle atmosphere and portrait eras
This commit is contained in:
@@ -57,6 +57,8 @@ export type MusicDuckOptions = {
|
||||
releaseMs?: number;
|
||||
};
|
||||
|
||||
type SemanticCueGroup = 'turn' | 'operation' | 'objective';
|
||||
|
||||
type MusicDuckState = {
|
||||
multiplier: number;
|
||||
timer?: number;
|
||||
@@ -97,6 +99,19 @@ export class SoundDirector {
|
||||
active: false,
|
||||
endsAt: 0
|
||||
};
|
||||
private readonly semanticCueCooldownMs: Record<SemanticCueGroup, number> = {
|
||||
turn: 900,
|
||||
operation: 650,
|
||||
objective: 1200
|
||||
};
|
||||
private readonly semanticCuePlayedAt = new Map<SemanticCueGroup, number>();
|
||||
private lastPlayedSemanticCue?: { group: SemanticCueGroup; key: string; at: number };
|
||||
private lastSuppressedSemanticCue?: {
|
||||
group: SemanticCueGroup;
|
||||
key: string;
|
||||
at: number;
|
||||
remainingMs: number;
|
||||
};
|
||||
private lastEffect?: {
|
||||
key: string;
|
||||
trackKey: string;
|
||||
@@ -379,6 +394,46 @@ export class SoundDirector {
|
||||
});
|
||||
}
|
||||
|
||||
playAllyTurnCue() {
|
||||
return this.playSemanticCue('turn', 'ally-turn', {
|
||||
volume: 0.32,
|
||||
stopAfterMs: 2100,
|
||||
duck: { multiplier: 0.62, attackMs: 36, holdMs: 1050, releaseMs: 420 }
|
||||
});
|
||||
}
|
||||
|
||||
playEnemyTurnCue() {
|
||||
return this.playSemanticCue('turn', 'enemy-turn', {
|
||||
volume: 0.3,
|
||||
stopAfterMs: 2400,
|
||||
duck: { multiplier: 0.56, attackMs: 36, holdMs: 1350, releaseMs: 480 }
|
||||
});
|
||||
}
|
||||
|
||||
playOperationAlert() {
|
||||
return this.playSemanticCue('operation', 'operation-alert', {
|
||||
volume: 0.34,
|
||||
stopAfterMs: 2100,
|
||||
duck: { multiplier: 0.48, attackMs: 28, holdMs: 820, releaseMs: 430 }
|
||||
});
|
||||
}
|
||||
|
||||
playObjectiveAchieved() {
|
||||
return this.playSemanticCue('objective', 'objective-success', {
|
||||
volume: 0.34,
|
||||
stopAfterMs: 1700,
|
||||
duck: { multiplier: 0.42, attackMs: 48, holdMs: 900, releaseMs: 480 }
|
||||
});
|
||||
}
|
||||
|
||||
playObjectiveFailed() {
|
||||
return this.playSemanticCue('objective', 'objective-failure', {
|
||||
volume: 0.32,
|
||||
stopAfterMs: 1600,
|
||||
duck: { multiplier: 0.36, attackMs: 48, holdMs: 900, releaseMs: 520 }
|
||||
});
|
||||
}
|
||||
|
||||
playStrategyPulse() {
|
||||
this.playEffect('strategy-cast', { volume: 0.44, rate: 0.96, stopAfterMs: 960 });
|
||||
this.playTone(460, 0.08, 0.012, 'sine');
|
||||
@@ -442,6 +497,37 @@ export class SoundDirector {
|
||||
return true;
|
||||
}
|
||||
|
||||
private playSemanticCue(
|
||||
group: SemanticCueGroup,
|
||||
key: string,
|
||||
options: PlayEffectOptions & { duck: MusicDuckOptions }
|
||||
) {
|
||||
if (!this.started || this.muted || !this.effectTracks[key]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const now = performance.now();
|
||||
const lastPlayedAt = this.semanticCuePlayedAt.get(group);
|
||||
const cooldownMs = this.semanticCueCooldownMs[group];
|
||||
if (lastPlayedAt !== undefined && now - lastPlayedAt < cooldownMs) {
|
||||
this.lastSuppressedSemanticCue = {
|
||||
group,
|
||||
key,
|
||||
at: now,
|
||||
remainingMs: Math.ceil(cooldownMs - (now - lastPlayedAt))
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
this.duckMusic(options.duck);
|
||||
const played = this.playEffect(key, options);
|
||||
if (played) {
|
||||
this.semanticCuePlayedAt.set(group, now);
|
||||
this.lastPlayedSemanticCue = { group, key, at: now };
|
||||
}
|
||||
return played;
|
||||
}
|
||||
|
||||
getDebugState() {
|
||||
const music = this.loopChannelDebugState(this.musicChannel);
|
||||
const ambience = this.loopChannelDebugState(this.ambienceChannel);
|
||||
@@ -465,6 +551,12 @@ export class SoundDirector {
|
||||
endsAt: this.musicDuck.endsAt,
|
||||
last: this.musicDuck.last ? { ...this.musicDuck.last } : null
|
||||
},
|
||||
semanticCues: {
|
||||
cooldownMs: { ...this.semanticCueCooldownMs },
|
||||
playedAt: Object.fromEntries(this.semanticCuePlayedAt),
|
||||
lastPlayed: this.lastPlayedSemanticCue ? { ...this.lastPlayedSemanticCue } : null,
|
||||
lastSuppressed: this.lastSuppressedSemanticCue ? { ...this.lastSuppressedSemanticCue } : null
|
||||
},
|
||||
effectPools: Object.fromEntries(
|
||||
Object.entries(this.effectPools).map(([key, trackKeys]) => [key, [...trackKeys]])
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user