feat(audio): 화면들 채널별 SFX API 적용

- game_play_screen: playPlayerSfx/playMonsterSfx 분리 사용
- settings_screen: 오디오 설정 UI 개선
This commit is contained in:
JiWoong Sul
2025-12-31 01:33:18 +09:00
parent 764a8353fb
commit 72676485d3
2 changed files with 43 additions and 14 deletions

View File

@@ -129,8 +129,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
'${game_l10n.uiLevelUp} Lv.${state.traits.level}', '${game_l10n.uiLevelUp} Lv.${state.traits.level}',
CombatLogType.levelUp, CombatLogType.levelUp,
); );
// 오디오: 레벨업 SFX // 오디오: 레벨업 SFX (플레이어 채널)
widget.audioService?.playSfx('level_up'); widget.audioService?.playPlayerSfx('level_up');
_resetSpecialAnimationAfterFrame(); _resetSpecialAnimationAfterFrame();
// Phase 9: Act 변경 감지 (레벨 기반) // Phase 9: Act 변경 감지 (레벨 기반)
@@ -168,8 +168,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
CombatLogType.questComplete, CombatLogType.questComplete,
); );
} }
// 오디오: 퀘스트 완료 SFX // 오디오: 퀘스트 완료 SFX (플레이어 채널)
widget.audioService?.playSfx('quest_complete'); widget.audioService?.playPlayerSfx('quest_complete');
_resetSpecialAnimationAfterFrame(); _resetSpecialAnimationAfterFrame();
} }
_lastQuestCount = state.progress.questCount; _lastQuestCount = state.progress.questCount;
@@ -283,26 +283,33 @@ class _GamePlayScreenState extends State<GamePlayScreen>
_wasInBattleTask = isInBattleTask; _wasInBattleTask = isInBattleTask;
} }
/// 전투 이벤트에 따른 SFX 재생 /// 전투 이벤트 SFX 재생 (채널 분리)
///
/// 플레이어 이펙트와 몬스터 이펙트를 별도 채널에서 재생하여
/// 사운드 충돌을 방지하고 완료를 보장합니다.
void _playCombatEventSfx(CombatEvent event) { void _playCombatEventSfx(CombatEvent event) {
final audio = widget.audioService; final audio = widget.audioService;
if (audio == null) return; if (audio == null) return;
switch (event.type) { switch (event.type) {
// 플레이어 채널: 플레이어가 발생시키는 이펙트
case CombatEventType.playerAttack: case CombatEventType.playerAttack:
audio.playSfx('attack'); audio.playPlayerSfx('attack');
case CombatEventType.monsterAttack:
audio.playSfx('hit');
case CombatEventType.playerSkill: case CombatEventType.playerSkill:
audio.playSfx('skill'); audio.playPlayerSfx('skill');
case CombatEventType.playerHeal: case CombatEventType.playerHeal:
case CombatEventType.playerPotion: case CombatEventType.playerPotion:
audio.playSfx('item');
case CombatEventType.potionDrop: case CombatEventType.potionDrop:
audio.playSfx('item'); audio.playPlayerSfx('item');
case CombatEventType.playerBuff: case CombatEventType.playerBuff:
case CombatEventType.playerDebuff: case CombatEventType.playerDebuff:
audio.playSfx('skill'); audio.playPlayerSfx('skill');
// 몬스터 채널: 몬스터가 발생시키는 이펙트 (플레이어 피격)
case CombatEventType.monsterAttack:
audio.playMonsterSfx('hit');
// SFX 없음
case CombatEventType.dotTick: case CombatEventType.dotTick:
// DOT 틱은 SFX 없음 (너무 자주 발생) // DOT 틱은 SFX 없음 (너무 자주 발생)
break; break;
@@ -685,6 +692,14 @@ class _GamePlayScreenState extends State<GamePlayScreen>
); );
} }
}, },
onBgmVolumeChange: (volume) {
setState(() => _bgmVolume = volume);
widget.audioService?.setBgmVolume(volume);
},
onSfxVolumeChange: (volume) {
setState(() => _sfxVolume = volume);
widget.audioService?.setSfxVolume(volume);
},
); );
} }

View File

@@ -13,6 +13,8 @@ class SettingsScreen extends StatefulWidget {
required this.currentThemeMode, required this.currentThemeMode,
required this.onThemeModeChange, required this.onThemeModeChange,
this.onLocaleChange, this.onLocaleChange,
this.onBgmVolumeChange,
this.onSfxVolumeChange,
}); });
final SettingsRepository settingsRepository; final SettingsRepository settingsRepository;
@@ -20,6 +22,12 @@ class SettingsScreen extends StatefulWidget {
final void Function(ThemeMode mode) onThemeModeChange; final void Function(ThemeMode mode) onThemeModeChange;
final void Function(String locale)? onLocaleChange; final void Function(String locale)? onLocaleChange;
/// BGM 볼륨 변경 콜백 (AudioService 연동용)
final void Function(double volume)? onBgmVolumeChange;
/// SFX 볼륨 변경 콜백 (AudioService 연동용)
final void Function(double volume)? onSfxVolumeChange;
@override @override
State<SettingsScreen> createState() => _SettingsScreenState(); State<SettingsScreen> createState() => _SettingsScreenState();
@@ -30,6 +38,8 @@ class SettingsScreen extends StatefulWidget {
required ThemeMode currentThemeMode, required ThemeMode currentThemeMode,
required void Function(ThemeMode mode) onThemeModeChange, required void Function(ThemeMode mode) onThemeModeChange,
void Function(String locale)? onLocaleChange, void Function(String locale)? onLocaleChange,
void Function(double volume)? onBgmVolumeChange,
void Function(double volume)? onSfxVolumeChange,
}) { }) {
return showModalBottomSheet<void>( return showModalBottomSheet<void>(
context: context, context: context,
@@ -45,6 +55,8 @@ class SettingsScreen extends StatefulWidget {
currentThemeMode: currentThemeMode, currentThemeMode: currentThemeMode,
onThemeModeChange: onThemeModeChange, onThemeModeChange: onThemeModeChange,
onLocaleChange: onLocaleChange, onLocaleChange: onLocaleChange,
onBgmVolumeChange: onBgmVolumeChange,
onSfxVolumeChange: onSfxVolumeChange,
), ),
), ),
); );
@@ -147,6 +159,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
onChanged: (value) { onChanged: (value) {
setState(() => _bgmVolume = value); setState(() => _bgmVolume = value);
widget.settingsRepository.saveBgmVolume(value); widget.settingsRepository.saveBgmVolume(value);
widget.onBgmVolumeChange?.call(value);
}, },
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -157,6 +170,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
onChanged: (value) { onChanged: (value) {
setState(() => _sfxVolume = value); setState(() => _sfxVolume = value);
widget.settingsRepository.saveSfxVolume(value); widget.settingsRepository.saveSfxVolume(value);
widget.onSfxVolumeChange?.call(value);
}, },
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -241,7 +255,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
Icon( Icon(
icon, icon,
color: isSelected color: isSelected
? theme.colorScheme.primary ? theme.colorScheme.onPrimaryContainer
: theme.colorScheme.onSurface, : theme.colorScheme.onSurface,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -251,7 +265,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
fontSize: 12, fontSize: 12,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected color: isSelected
? theme.colorScheme.primary ? theme.colorScheme.onPrimaryContainer
: theme.colorScheme.onSurface, : theme.colorScheme.onSurface,
), ),
), ),