feat(game): 상황별 BGM 재생 로직 추가

- Act/엘리트/보스별 동적 BGM 선택
This commit is contained in:
JiWoong Sul
2026-01-08 18:18:14 +09:00
parent 929b8a7f96
commit d71f065745

View File

@@ -13,6 +13,7 @@ import 'package:asciineverdie/src/core/engine/story_service.dart';
import 'package:asciineverdie/src/core/model/combat_event.dart'; import 'package:asciineverdie/src/core/model/combat_event.dart';
import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart'; import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart';
import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/monster_grade.dart';
import 'package:asciineverdie/src/core/model/skill.dart'; import 'package:asciineverdie/src/core/model/skill.dart';
import 'package:asciineverdie/src/core/notification/notification_service.dart'; import 'package:asciineverdie/src/core/notification/notification_service.dart';
import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic; import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic;
@@ -99,6 +100,10 @@ class _GamePlayScreenState extends State<GamePlayScreen>
// 오디오 상태 추적 (TaskType 기반) // 오디오 상태 추적 (TaskType 기반)
bool _wasInBattleTask = false; bool _wasInBattleTask = false;
// 사망/엔딩 상태 추적 (BGM 전환용)
bool _wasDead = false;
bool _wasComplete = false;
// 사운드 볼륨 상태 (모바일 설정 UI용) // 사운드 볼륨 상태 (모바일 설정 UI용)
double _bgmVolume = 0.7; double _bgmVolume = 0.7;
double _sfxVolume = 0.8; double _sfxVolume = 0.8;
@@ -177,6 +182,38 @@ class _GamePlayScreenState extends State<GamePlayScreen>
_resetSpecialAnimationAfterFrame(); _resetSpecialAnimationAfterFrame();
} }
_lastPlotStageCount = state.progress.plotStageCount; _lastPlotStageCount = state.progress.plotStageCount;
// 사망/엔딩 BGM 전환 (Death/Ending BGM Transition)
_updateDeathEndingBgm(state);
}
/// 사망/엔딩 BGM 전환 처리
void _updateDeathEndingBgm(GameState state) {
final audio = widget.audioService;
if (audio == null) return;
final isDead = state.isDead;
final isComplete = widget.controller.isComplete;
// 엔딩 BGM (게임 클리어 시)
if (isComplete && !_wasComplete) {
audio.playBgm('ending');
_wasComplete = true;
return;
}
// 사망 BGM (isDead 상태 진입 시)
if (isDead && !_wasDead) {
audio.playBgm('death');
_wasDead = true;
return;
}
// 부활 시 사망 상태 리셋 (다음 사망 감지 가능하도록)
if (!isDead && _wasDead) {
_wasDead = false;
// 부활 후 BGM은 _updateBgmForTaskType에서 처리됨
}
} }
/// Phase 8: 전투 로그 추가 (Add Combat Log Entry) /// Phase 8: 전투 로그 추가 (Add Combat Log Entry)
@@ -227,16 +264,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
final isInBattleTask = taskType == TaskType.kill; final isInBattleTask = taskType == TaskType.kill;
if (isInBattleTask) { if (isInBattleTask) {
// 전투 태스크: 보스 여부에 따라 BGM 선택 audio.playBgm(_getBattleBgm(state));
final monsterLevel = state.progress.currentTask.monsterLevel ?? 0;
final playerLevel = state.traits.level;
final isBoss = monsterLevel >= playerLevel + 5;
if (isBoss) {
audio.playBgm('boss');
} else {
audio.playBgm('battle');
}
} else { } else {
// 비전투 태스크: 마을 BGM // 비전투 태스크: 마을 BGM
audio.playBgm('town'); audio.playBgm('town');
@@ -245,6 +273,48 @@ class _GamePlayScreenState extends State<GamePlayScreen>
_wasInBattleTask = isInBattleTask; _wasInBattleTask = isInBattleTask;
} }
/// 전투 BGM 결정 (몬스터 등급 + 레벨 + Act 고려)
///
/// 우선순위:
/// 1. MonsterGrade.boss → 'act_boss'
/// 2. 레벨 기반 보스 (monsterLevel >= playerLevel + 5) → 'boss'
/// 3. MonsterGrade.elite → 'elite'
/// 4. Act별 일반 전투 → 'battle', 'battle_act4', 'battle_act5'
String _getBattleBgm(GameState state) {
final task = state.progress.currentTask;
final monsterGrade = task.monsterGrade;
final monsterLevel = task.monsterLevel ?? 0;
final playerLevel = state.traits.level;
// 1. 등급 보스 (3% 확률로 등장하는 특수 보스)
if (monsterGrade == MonsterGrade.boss) {
return 'act_boss';
}
// 2. 레벨 기반 보스 (강적)
if (monsterLevel >= playerLevel + 5) {
return 'boss';
}
// 3. 엘리트 몬스터 (12% 확률)
if (monsterGrade == MonsterGrade.elite) {
return 'elite';
}
// 4. 일반 전투 (Act별 분기)
return _getBattleBgmForLevel(playerLevel);
}
/// 레벨에 따른 전투 BGM 파일명 반환 (Act별 분기)
String _getBattleBgmForLevel(int playerLevel) {
final act = getActForLevel(playerLevel);
return switch (act) {
StoryAct.act4 => 'battle_act4',
StoryAct.act5 => 'battle_act5',
_ => 'battle',
};
}
/// TaskType 기반 BGM 전환 (애니메이션과 동기화) /// TaskType 기반 BGM 전환 (애니메이션과 동기화)
/// ///
/// 애니메이션은 TaskType으로 결정되므로, BGM도 동일한 기준 사용 /// 애니메이션은 TaskType으로 결정되므로, BGM도 동일한 기준 사용
@@ -258,11 +328,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
// 전투 태스크 상태 결정 // 전투 태스크 상태 결정
if (isInBattleTask) { if (isInBattleTask) {
// 전투 태스크: 보스 여부에 따라 BGM 선택 final expectedBgm = _getBattleBgm(state);
final monsterLevel = state.progress.currentTask.monsterLevel ?? 0;
final playerLevel = state.traits.level;
final isBoss = monsterLevel >= playerLevel + 5;
final expectedBgm = isBoss ? 'boss' : 'battle';
// 전환 시점이거나 현재 BGM이 일치하지 않으면 재생 // 전환 시점이거나 현재 BGM이 일치하지 않으면 재생
if (!_wasInBattleTask || audio.currentBgm != expectedBgm) { if (!_wasInBattleTask || audio.currentBgm != expectedBgm) {
@@ -408,11 +474,14 @@ class _GamePlayScreenState extends State<GamePlayScreen>
// 게임 일시 정지 // 게임 일시 정지
await widget.controller.pause(saveOnStop: false); await widget.controller.pause(saveOnStop: false);
// 시네마틱 BGM 재생
widget.audioService?.playBgm('act_cinemetic');
if (mounted) { if (mounted) {
await showActCinematic(context, act); await showActCinematic(context, act);
} }
// 게임 재개 // 게임 재개 (BGM은 _updateBgmForTaskType에서 복원됨)
if (mounted) { if (mounted) {
await widget.controller.resume(); await widget.controller.resume();
} }