fix(audio): 모바일 백그라운드 시 오디오 완전 정지

- AudioService: pauseAll()/resumeAll() 메서드 추가
- _isPaused 플래그로 백그라운드 시 새로운 재생 요청 차단
- playBgm/playSfx에서 일시정지 상태 체크
- game_play_screen: pauseAll() 사용으로 BGM+SFX 동시 정지
- 포그라운드 복귀 시 resumeAll() 호출 후 화면 재로드
This commit is contained in:
JiWoong Sul
2025-12-30 17:49:49 +09:00
parent a8d818917f
commit 8fd2f71a2f
2 changed files with 60 additions and 26 deletions

View File

@@ -96,8 +96,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
// 전투 이벤트 추적 (마지막 처리된 이벤트 수)
int _lastProcessedEventCount = 0;
// 오디오 상태 추적
bool _wasInCombat = false;
// 오디오 상태 추적 (TaskType 기반)
bool _wasInBattleTask = false;
// 사운드 볼륨 상태 (모바일 설정 UI용)
double _bgmVolume = 0.7;
@@ -116,8 +116,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
// 전투 이벤트 처리 (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<GamePlayScreen>
}
/// 초기 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<GamePlayScreen>
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<GamePlayScreen>
} 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<GamePlayScreen>
_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<GamePlayScreen>
// 저장
_saveGameState();
// 모바일: 게임 일시정지 + 사운드 정지
// 모바일: 게임 일시정지 + 전체 오디오 정지
if (isMobile) {
widget.controller.pause(saveOnStop: false);
widget.audioService?.stopBgm();
widget.audioService?.pauseAll();
}
}
// 모바일: 앱이 포그라운드로 돌아올 때 전체 재로드
if (appState == AppLifecycleState.resumed && isMobile) {
widget.audioService?.resumeAll();
_reloadGameScreen();
}
}