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}',
|
'${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);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user