feat(audio): 화면들 채널별 SFX API 적용
- game_play_screen: playPlayerSfx/playMonsterSfx 분리 사용 - settings_screen: 오디오 설정 UI 개선
This commit is contained in:
@@ -129,8 +129,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
'${game_l10n.uiLevelUp} Lv.${state.traits.level}',
|
||||
CombatLogType.levelUp,
|
||||
);
|
||||
// 오디오: 레벨업 SFX
|
||||
widget.audioService?.playSfx('level_up');
|
||||
// 오디오: 레벨업 SFX (플레이어 채널)
|
||||
widget.audioService?.playPlayerSfx('level_up');
|
||||
_resetSpecialAnimationAfterFrame();
|
||||
|
||||
// Phase 9: Act 변경 감지 (레벨 기반)
|
||||
@@ -168,8 +168,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
CombatLogType.questComplete,
|
||||
);
|
||||
}
|
||||
// 오디오: 퀘스트 완료 SFX
|
||||
widget.audioService?.playSfx('quest_complete');
|
||||
// 오디오: 퀘스트 완료 SFX (플레이어 채널)
|
||||
widget.audioService?.playPlayerSfx('quest_complete');
|
||||
_resetSpecialAnimationAfterFrame();
|
||||
}
|
||||
_lastQuestCount = state.progress.questCount;
|
||||
@@ -283,26 +283,33 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
_wasInBattleTask = isInBattleTask;
|
||||
}
|
||||
|
||||
/// 전투 이벤트에 따른 SFX 재생
|
||||
/// 전투 이벤트별 SFX 재생 (채널 분리)
|
||||
///
|
||||
/// 플레이어 이펙트와 몬스터 이펙트를 별도 채널에서 재생하여
|
||||
/// 사운드 충돌을 방지하고 완료를 보장합니다.
|
||||
void _playCombatEventSfx(CombatEvent event) {
|
||||
final audio = widget.audioService;
|
||||
if (audio == null) return;
|
||||
|
||||
switch (event.type) {
|
||||
// 플레이어 채널: 플레이어가 발생시키는 이펙트
|
||||
case CombatEventType.playerAttack:
|
||||
audio.playSfx('attack');
|
||||
case CombatEventType.monsterAttack:
|
||||
audio.playSfx('hit');
|
||||
audio.playPlayerSfx('attack');
|
||||
case CombatEventType.playerSkill:
|
||||
audio.playSfx('skill');
|
||||
audio.playPlayerSfx('skill');
|
||||
case CombatEventType.playerHeal:
|
||||
case CombatEventType.playerPotion:
|
||||
audio.playSfx('item');
|
||||
case CombatEventType.potionDrop:
|
||||
audio.playSfx('item');
|
||||
audio.playPlayerSfx('item');
|
||||
case CombatEventType.playerBuff:
|
||||
case CombatEventType.playerDebuff:
|
||||
audio.playSfx('skill');
|
||||
audio.playPlayerSfx('skill');
|
||||
|
||||
// 몬스터 채널: 몬스터가 발생시키는 이펙트 (플레이어 피격)
|
||||
case CombatEventType.monsterAttack:
|
||||
audio.playMonsterSfx('hit');
|
||||
|
||||
// SFX 없음
|
||||
case CombatEventType.dotTick:
|
||||
// DOT 틱은 SFX 없음 (너무 자주 발생)
|
||||
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);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ class SettingsScreen extends StatefulWidget {
|
||||
required this.currentThemeMode,
|
||||
required this.onThemeModeChange,
|
||||
this.onLocaleChange,
|
||||
this.onBgmVolumeChange,
|
||||
this.onSfxVolumeChange,
|
||||
});
|
||||
|
||||
final SettingsRepository settingsRepository;
|
||||
@@ -20,6 +22,12 @@ class SettingsScreen extends StatefulWidget {
|
||||
final void Function(ThemeMode mode) onThemeModeChange;
|
||||
final void Function(String locale)? onLocaleChange;
|
||||
|
||||
/// BGM 볼륨 변경 콜백 (AudioService 연동용)
|
||||
final void Function(double volume)? onBgmVolumeChange;
|
||||
|
||||
/// SFX 볼륨 변경 콜백 (AudioService 연동용)
|
||||
final void Function(double volume)? onSfxVolumeChange;
|
||||
|
||||
@override
|
||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||
|
||||
@@ -30,6 +38,8 @@ class SettingsScreen extends StatefulWidget {
|
||||
required ThemeMode currentThemeMode,
|
||||
required void Function(ThemeMode mode) onThemeModeChange,
|
||||
void Function(String locale)? onLocaleChange,
|
||||
void Function(double volume)? onBgmVolumeChange,
|
||||
void Function(double volume)? onSfxVolumeChange,
|
||||
}) {
|
||||
return showModalBottomSheet<void>(
|
||||
context: context,
|
||||
@@ -45,6 +55,8 @@ class SettingsScreen extends StatefulWidget {
|
||||
currentThemeMode: currentThemeMode,
|
||||
onThemeModeChange: onThemeModeChange,
|
||||
onLocaleChange: onLocaleChange,
|
||||
onBgmVolumeChange: onBgmVolumeChange,
|
||||
onSfxVolumeChange: onSfxVolumeChange,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -147,6 +159,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
onChanged: (value) {
|
||||
setState(() => _bgmVolume = value);
|
||||
widget.settingsRepository.saveBgmVolume(value);
|
||||
widget.onBgmVolumeChange?.call(value);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -157,6 +170,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
onChanged: (value) {
|
||||
setState(() => _sfxVolume = value);
|
||||
widget.settingsRepository.saveSfxVolume(value);
|
||||
widget.onSfxVolumeChange?.call(value);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
@@ -241,7 +255,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Icon(
|
||||
icon,
|
||||
color: isSelected
|
||||
? theme.colorScheme.primary
|
||||
? theme.colorScheme.onPrimaryContainer
|
||||
: theme.colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -251,7 +265,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
fontSize: 12,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
color: isSelected
|
||||
? theme.colorScheme.primary
|
||||
? theme.colorScheme.onPrimaryContainer
|
||||
: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user