diff --git a/lib/src/core/audio/audio_service.dart b/lib/src/core/audio/audio_service.dart index 7617b23..5463111 100644 --- a/lib/src/core/audio/audio_service.dart +++ b/lib/src/core/audio/audio_service.dart @@ -39,6 +39,9 @@ class AudioService { // 사용자 상호작용 발생 여부 (웹 자동재생 정책 우회용) bool _userInteracted = false; + // 오디오 일시정지 상태 (앱 백그라운드 시) + bool _isPaused = false; + /// 서비스 초기화 Future init() async { if (_initialized || _initFailed) return; @@ -83,6 +86,7 @@ class AudioService { /// 다음 SFX 재생 시 함께 시작됩니다. Future playBgm(String name) async { if (_initFailed) return; // 초기화 실패 시 무시 + if (_isPaused) return; // 일시정지 상태면 무시 if (!_initialized) await init(); if (_initFailed || !_initialized) return; if (_currentBgm == name) return; // 이미 재생 중 @@ -128,6 +132,33 @@ class AudioService { } } + /// 전체 오디오 일시정지 (앱 백그라운드 시) + /// + /// BGM을 정지하고, 새로운 재생 요청을 무시합니다. + Future pauseAll() async { + _isPaused = true; + if (!_initialized) return; + + // BGM 정지 및 상태 초기화 + await _bgmPlayer?.stop(); + _currentBgm = null; + + // 모든 SFX 정지 + for (final player in _sfxPlayers) { + await player.stop(); + } + + debugPrint('[AudioService] All audio paused'); + } + + /// 전체 오디오 재개 (앱 포그라운드 복귀 시) + /// + /// 일시정지 상태를 해제하고 이전 BGM을 재개합니다. + Future resumeAll() async { + _isPaused = false; + debugPrint('[AudioService] Audio resumed'); + } + /// SFX 재생 /// /// [name]은 assets/audio/sfx/ 폴더 내 파일명 (확장자 제외) @@ -136,6 +167,7 @@ class AudioService { /// 웹에서 대기 중인 BGM이 있으면 함께 재생 시작합니다. Future playSfx(String name) async { if (_initFailed) return; // 초기화 실패 시 무시 + if (_isPaused) return; // 일시정지 상태면 무시 if (!_initialized) await init(); if (_initFailed || !_initialized) return; if (_sfxVolume == 0) return; // 볼륨이 0이면 재생 안함 diff --git a/lib/src/features/game/game_play_screen.dart b/lib/src/features/game/game_play_screen.dart index f763683..c8c8a63 100644 --- a/lib/src/features/game/game_play_screen.dart +++ b/lib/src/features/game/game_play_screen.dart @@ -96,8 +96,8 @@ class _GamePlayScreenState extends State // 전투 이벤트 추적 (마지막 처리된 이벤트 수) int _lastProcessedEventCount = 0; - // 오디오 상태 추적 - bool _wasInCombat = false; + // 오디오 상태 추적 (TaskType 기반) + bool _wasInBattleTask = false; // 사운드 볼륨 상태 (모바일 설정 UI용) double _bgmVolume = 0.7; @@ -116,8 +116,8 @@ class _GamePlayScreenState extends State // 전투 이벤트 처리 (Combat Events) _processCombatEvents(state); - // 오디오: 전투 상태 변경 시 BGM 전환 - _updateBgmForCombatState(state); + // 오디오: TaskType 변경 시 BGM 전환 (애니메이션과 동기화) + _updateBgmForTaskType(state); // 레벨업 감지 if (state.traits.level > _lastLevel && _lastLevel > 0) { @@ -222,15 +222,17 @@ class _GamePlayScreenState extends State } /// 초기 BGM 재생 (게임 시작/로드 시) + /// + /// TaskType 기반으로 BGM 결정 (애니메이션과 동기화) void _playInitialBgm(GameState state) { final audio = widget.audioService; if (audio == null) return; - final combat = state.progress.currentCombat; - final isInCombat = combat != null && combat.isActive; + final taskType = state.progress.currentTask.type; + final isInBattleTask = taskType == TaskType.kill; - if (isInCombat) { - // 전투 중: 보스 여부에 따라 BGM 선택 + if (isInBattleTask) { + // 전투 태스크: 보스 여부에 따라 BGM 선택 final monsterLevel = state.progress.currentTask.monsterLevel ?? 0; final playerLevel = state.traits.level; final isBoss = monsterLevel >= playerLevel + 5; @@ -241,22 +243,25 @@ class _GamePlayScreenState extends State audio.playBgm('battle'); } } else { - // 비전투: 마을 BGM + // 비전투 태스크: 마을 BGM audio.playBgm('town'); } + + _wasInBattleTask = isInBattleTask; } - /// 전투 상태에 따른 BGM 전환 - void _updateBgmForCombatState(GameState state) { + /// TaskType 기반 BGM 전환 (애니메이션과 동기화) + /// + /// 애니메이션은 TaskType으로 결정되므로, BGM도 동일한 기준 사용 + void _updateBgmForTaskType(GameState state) { final audio = widget.audioService; if (audio == null) return; - final combat = state.progress.currentCombat; - final isInCombat = combat != null && combat.isActive; + final taskType = state.progress.currentTask.type; + final isInBattleTask = taskType == TaskType.kill; - if (isInCombat && !_wasInCombat) { - // 전투 시작: 보스 여부에 따라 BGM 선택 - // 몬스터 레벨이 플레이어보다 5 이상 높으면 보스로 간주 + if (isInBattleTask && !_wasInBattleTask) { + // 전투 태스크 시작: 보스 여부에 따라 BGM 선택 final monsterLevel = state.progress.currentTask.monsterLevel ?? 0; final playerLevel = state.traits.level; final isBoss = monsterLevel >= playerLevel + 5; @@ -266,12 +271,12 @@ class _GamePlayScreenState extends State } else { audio.playBgm('battle'); } - } else if (!isInCombat && _wasInCombat) { - // 전투 종료: 마을 BGM으로 복귀 + } else if (!isInBattleTask && _wasInBattleTask) { + // 전투 태스크 종료: 마을 BGM으로 복귀 audio.playBgm('town'); } - _wasInCombat = isInCombat; + _wasInBattleTask = isInBattleTask; } /// 전투 이벤트에 따른 SFX 재생 @@ -475,11 +480,7 @@ class _GamePlayScreenState extends State _lastPlotStageCount = state.progress.plotStageCount; _lastAct = getActForLevel(state.traits.level); - // 초기 전투 상태 확인 및 BGM 설정 - final combat = state.progress.currentCombat; - _wasInCombat = combat != null && combat.isActive; - - // 초기 BGM 재생 (전투 상태에 따라) + // 초기 BGM 재생 (TaskType 기반, _wasInBattleTask도 함께 설정) _playInitialBgm(state); } else { // 상태가 없으면 기본 마을 BGM @@ -528,15 +529,16 @@ class _GamePlayScreenState extends State // 저장 _saveGameState(); - // 모바일: 게임 일시정지 + 사운드 정지 + // 모바일: 게임 일시정지 + 전체 오디오 정지 if (isMobile) { widget.controller.pause(saveOnStop: false); - widget.audioService?.stopBgm(); + widget.audioService?.pauseAll(); } } // 모바일: 앱이 포그라운드로 돌아올 때 전체 재로드 if (appState == AppLifecycleState.resumed && isMobile) { + widget.audioService?.resumeAll(); _reloadGameScreen(); } }