feat(ui): 도움말 다이얼로그 및 UI 개선

- HelpDialog 추가
- 게임 화면에 통계/도움말 버튼 추가
- CombatLog에 디버프 이벤트 표시
- AudioService mp3 확장자 지원
- 설정 텍스트 l10n 추가
This commit is contained in:
JiWoong Sul
2025-12-30 15:58:40 +09:00
parent d64b9654a3
commit 18af93824b
10 changed files with 1028 additions and 32 deletions

View File

@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart' show debugPrint, kIsWeb;
import 'package:just_audio/just_audio.dart';
import 'package:askiineverdie/src/core/storage/settings_repository.dart';
@@ -5,6 +6,7 @@ import 'package:askiineverdie/src/core/storage/settings_repository.dart';
/// 게임 오디오 서비스
///
/// BGM과 SFX를 관리하며, 설정과 연동하여 볼륨을 조절합니다.
/// 웹/WASM 환경에서는 제한적으로 작동할 수 있습니다.
class AudioService {
AudioService({SettingsRepository? settingsRepository})
: _settingsRepository = settingsRepository ?? SettingsRepository();
@@ -28,48 +30,58 @@ class AudioService {
// 초기화 여부
bool _initialized = false;
// 초기화 실패 여부 (WASM 등에서 오디오 지원 안됨)
bool _initFailed = false;
/// 서비스 초기화
Future<void> init() async {
if (_initialized) return;
if (_initialized || _initFailed) return;
// 설정에서 볼륨 불러오기
_bgmVolume = await _settingsRepository.loadBgmVolume();
_sfxVolume = await _settingsRepository.loadSfxVolume();
try {
// 설정에서 볼륨 불러오기
_bgmVolume = await _settingsRepository.loadBgmVolume();
_sfxVolume = await _settingsRepository.loadSfxVolume();
// BGM 플레이어 초기화
_bgmPlayer = AudioPlayer();
await _bgmPlayer!.setLoopMode(LoopMode.one);
await _bgmPlayer!.setVolume(_bgmVolume);
// BGM 플레이어 초기화
_bgmPlayer = AudioPlayer();
await _bgmPlayer!.setLoopMode(LoopMode.one);
await _bgmPlayer!.setVolume(_bgmVolume);
// SFX 플레이어 풀 초기화
for (var i = 0; i < _maxSfxPlayers; i++) {
final player = AudioPlayer();
await player.setVolume(_sfxVolume);
_sfxPlayers.add(player);
// SFX 플레이어 풀 초기화
for (var i = 0; i < _maxSfxPlayers; i++) {
final player = AudioPlayer();
await player.setVolume(_sfxVolume);
_sfxPlayers.add(player);
}
_initialized = true;
if (kIsWeb) {
debugPrint('[AudioService] Initialized on Web platform');
}
} catch (e) {
_initFailed = true;
debugPrint('[AudioService] Init failed (likely WASM): $e');
}
_initialized = true;
}
/// BGM 재생
///
/// [name]은 assets/audio/bgm/ 폴더 내 파일명 (확장자 제외)
/// 예: playBgm('battle') → assets/audio/bgm/battle.wav 또는 battle.mp3
/// 예: playBgm('battle') → assets/audio/bgm/battle.mp3
Future<void> playBgm(String name) async {
if (_initFailed) return; // 초기화 실패 시 무시
if (!_initialized) await init();
if (_initFailed || !_initialized) return;
if (_currentBgm == name) return; // 이미 재생 중
try {
_currentBgm = name;
// WAV 먼저 시도, 실패하면 MP3 시도
try {
await _bgmPlayer!.setAsset('assets/audio/bgm/$name.wav');
} catch (_) {
await _bgmPlayer!.setAsset('assets/audio/bgm/$name.mp3');
}
await _bgmPlayer!.setAsset('assets/audio/bgm/$name.mp3');
await _bgmPlayer!.play();
debugPrint('[AudioService] Playing BGM: $name');
} catch (e) {
// 파일이 없으면 무시 (개발 중 에셋 미추가 상태)
debugPrint('[AudioService] Failed to play BGM $name: $e');
_currentBgm = null;
}
}
@@ -99,10 +111,13 @@ class AudioService {
/// SFX 재생
///
/// [name]은 assets/audio/sfx/ 폴더 내 파일명 (확장자 제외)
/// 예: playSfx('attack') → assets/audio/sfx/attack.wav 또는 attack.mp3
/// 예: playSfx('attack') → assets/audio/sfx/attack.mp3
Future<void> playSfx(String name) async {
if (_initFailed) return; // 초기화 실패 시 무시
if (!_initialized) await init();
if (_initFailed || !_initialized) return;
if (_sfxVolume == 0) return; // 볼륨이 0이면 재생 안함
if (_sfxPlayers.isEmpty) return;
// 사용 가능한 플레이어 찾기
AudioPlayer? availablePlayer;
@@ -117,16 +132,12 @@ class AudioService {
availablePlayer ??= _sfxPlayers.first;
try {
// WAV 먼저 시도, 실패하면 MP3 시도
try {
await availablePlayer.setAsset('assets/audio/sfx/$name.wav');
} catch (_) {
await availablePlayer.setAsset('assets/audio/sfx/$name.mp3');
}
await availablePlayer.setAsset('assets/audio/sfx/$name.mp3');
await availablePlayer.seek(Duration.zero);
await availablePlayer.play();
} catch (e) {
// 파일이 없으면 무시
debugPrint('[AudioService] Failed to play SFX $name: $e');
}
}
@@ -172,6 +183,9 @@ class AudioService {
/// BGM 타입 열거형
enum BgmType {
/// 타이틀 화면 BGM
title,
/// 마을/상점 BGM
town,