feat(game): 상황별 BGM 재생 로직 추가
- Act/엘리트/보스별 동적 BGM 선택
This commit is contained in:
@@ -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/l10n/game_data_l10n.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/notification/notification_service.dart';
|
||||
import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic;
|
||||
@@ -99,6 +100,10 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
// 오디오 상태 추적 (TaskType 기반)
|
||||
bool _wasInBattleTask = false;
|
||||
|
||||
// 사망/엔딩 상태 추적 (BGM 전환용)
|
||||
bool _wasDead = false;
|
||||
bool _wasComplete = false;
|
||||
|
||||
// 사운드 볼륨 상태 (모바일 설정 UI용)
|
||||
double _bgmVolume = 0.7;
|
||||
double _sfxVolume = 0.8;
|
||||
@@ -177,6 +182,38 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
_resetSpecialAnimationAfterFrame();
|
||||
}
|
||||
_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)
|
||||
@@ -227,16 +264,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
final isInBattleTask = taskType == TaskType.kill;
|
||||
|
||||
if (isInBattleTask) {
|
||||
// 전투 태스크: 보스 여부에 따라 BGM 선택
|
||||
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');
|
||||
}
|
||||
audio.playBgm(_getBattleBgm(state));
|
||||
} else {
|
||||
// 비전투 태스크: 마을 BGM
|
||||
audio.playBgm('town');
|
||||
@@ -245,6 +273,48 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
_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도 동일한 기준 사용
|
||||
@@ -258,11 +328,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
// 전투 태스크 상태 결정
|
||||
if (isInBattleTask) {
|
||||
// 전투 태스크: 보스 여부에 따라 BGM 선택
|
||||
final monsterLevel = state.progress.currentTask.monsterLevel ?? 0;
|
||||
final playerLevel = state.traits.level;
|
||||
final isBoss = monsterLevel >= playerLevel + 5;
|
||||
final expectedBgm = isBoss ? 'boss' : 'battle';
|
||||
final expectedBgm = _getBattleBgm(state);
|
||||
|
||||
// 전환 시점이거나 현재 BGM이 일치하지 않으면 재생
|
||||
if (!_wasInBattleTask || audio.currentBgm != expectedBgm) {
|
||||
@@ -408,11 +474,14 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
// 게임 일시 정지
|
||||
await widget.controller.pause(saveOnStop: false);
|
||||
|
||||
// 시네마틱 BGM 재생
|
||||
widget.audioService?.playBgm('act_cinemetic');
|
||||
|
||||
if (mounted) {
|
||||
await showActCinematic(context, act);
|
||||
}
|
||||
|
||||
// 게임 재개
|
||||
// 게임 재개 (BGM은 _updateBgmForTaskType에서 복원됨)
|
||||
if (mounted) {
|
||||
await widget.controller.resume();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user