feat(ui): 도움말 다이얼로그 및 UI 개선
- HelpDialog 추가 - 게임 화면에 통계/도움말 버튼 추가 - CombatLog에 디버프 이벤트 표시 - AudioService mp3 확장자 지원 - 설정 텍스트 l10n 추가
This commit is contained in:
@@ -32,7 +32,10 @@ import 'package:askiineverdie/src/features/game/widgets/task_progress_panel.dart
|
||||
import 'package:askiineverdie/src/features/game/widgets/active_buff_panel.dart';
|
||||
import 'package:askiineverdie/src/features/game/layouts/mobile_carousel_layout.dart';
|
||||
import 'package:askiineverdie/src/features/settings/settings_screen.dart';
|
||||
import 'package:askiineverdie/src/features/game/widgets/statistics_dialog.dart';
|
||||
import 'package:askiineverdie/src/features/game/widgets/help_dialog.dart';
|
||||
import 'package:askiineverdie/src/core/storage/settings_repository.dart';
|
||||
import 'package:askiineverdie/src/core/audio/audio_service.dart';
|
||||
|
||||
/// 게임 진행 화면 (Main.dfm 기반 3패널 레이아웃)
|
||||
///
|
||||
@@ -41,6 +44,7 @@ class GamePlayScreen extends StatefulWidget {
|
||||
const GamePlayScreen({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.audioService,
|
||||
this.forceCarouselLayout = false,
|
||||
this.forceDesktopLayout = false,
|
||||
this.onThemeModeChange,
|
||||
@@ -49,6 +53,9 @@ class GamePlayScreen extends StatefulWidget {
|
||||
|
||||
final GameSessionController controller;
|
||||
|
||||
/// 오디오 서비스 (BGM/SFX 재생)
|
||||
final AudioService? audioService;
|
||||
|
||||
/// 테스트 모드: 웹에서도 모바일 캐로셀 레이아웃 강제 사용
|
||||
final bool forceCarouselLayout;
|
||||
|
||||
@@ -89,6 +96,13 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
// 전투 이벤트 추적 (마지막 처리된 이벤트 수)
|
||||
int _lastProcessedEventCount = 0;
|
||||
|
||||
// 오디오 상태 추적
|
||||
bool _wasInCombat = false;
|
||||
|
||||
// 사운드 볼륨 상태 (모바일 설정 UI용)
|
||||
double _bgmVolume = 0.7;
|
||||
double _sfxVolume = 0.8;
|
||||
|
||||
void _checkSpecialEvents(GameState state) {
|
||||
// Phase 8: 태스크 변경 시 로그 추가
|
||||
final currentCaption = state.progress.currentTask.caption;
|
||||
@@ -102,6 +116,9 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
// 전투 이벤트 처리 (Combat Events)
|
||||
_processCombatEvents(state);
|
||||
|
||||
// 오디오: 전투 상태 변경 시 BGM 전환
|
||||
_updateBgmForCombatState(state);
|
||||
|
||||
// 레벨업 감지
|
||||
if (state.traits.level > _lastLevel && _lastLevel > 0) {
|
||||
_specialAnimation = AsciiAnimationType.levelUp;
|
||||
@@ -110,6 +127,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
'${game_l10n.uiLevelUp} Lv.${state.traits.level}',
|
||||
CombatLogType.levelUp,
|
||||
);
|
||||
// 오디오: 레벨업 SFX
|
||||
widget.audioService?.playSfx('level_up');
|
||||
_resetSpecialAnimationAfterFrame();
|
||||
|
||||
// Phase 9: Act 변경 감지 (레벨 기반)
|
||||
@@ -147,6 +166,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
CombatLogType.questComplete,
|
||||
);
|
||||
}
|
||||
// 오디오: 퀘스트 완료 SFX
|
||||
widget.audioService?.playSfx('quest_complete');
|
||||
_resetSpecialAnimationAfterFrame();
|
||||
}
|
||||
_lastQuestCount = state.progress.questCount;
|
||||
@@ -192,11 +213,74 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
for (final event in newEvents) {
|
||||
final (message, type) = _formatCombatEvent(event);
|
||||
_addCombatLog(message, type);
|
||||
|
||||
// 오디오: 전투 이벤트에 따른 SFX 재생
|
||||
_playCombatEventSfx(event);
|
||||
}
|
||||
|
||||
_lastProcessedEventCount = events.length;
|
||||
}
|
||||
|
||||
/// 전투 상태에 따른 BGM 전환
|
||||
void _updateBgmForCombatState(GameState state) {
|
||||
final audio = widget.audioService;
|
||||
if (audio == null) return;
|
||||
|
||||
final combat = state.progress.currentCombat;
|
||||
final isInCombat = combat != null && combat.isActive;
|
||||
|
||||
if (isInCombat && !_wasInCombat) {
|
||||
// 전투 시작: 보스 여부에 따라 BGM 선택
|
||||
// 몬스터 레벨이 플레이어보다 5 이상 높으면 보스로 간주
|
||||
final monsterLevel = state.progress.currentTask.monsterLevel ?? 0;
|
||||
final playerLevel = state.traits.level;
|
||||
final isBoss = monsterLevel >= playerLevel + 5;
|
||||
|
||||
if (isBoss) {
|
||||
audio.playBgm('boss');
|
||||
} else {
|
||||
audio.playBgm('battle');
|
||||
}
|
||||
} else if (!isInCombat && _wasInCombat) {
|
||||
// 전투 종료: 마을 BGM으로 복귀
|
||||
audio.playBgm('town');
|
||||
}
|
||||
|
||||
_wasInCombat = isInCombat;
|
||||
}
|
||||
|
||||
/// 전투 이벤트에 따른 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');
|
||||
case CombatEventType.playerSkill:
|
||||
audio.playSfx('skill');
|
||||
case CombatEventType.playerHeal:
|
||||
case CombatEventType.playerPotion:
|
||||
audio.playSfx('item');
|
||||
case CombatEventType.potionDrop:
|
||||
audio.playSfx('item');
|
||||
case CombatEventType.playerBuff:
|
||||
case CombatEventType.playerDebuff:
|
||||
audio.playSfx('skill');
|
||||
case CombatEventType.dotTick:
|
||||
// DOT 틱은 SFX 없음 (너무 자주 발생)
|
||||
break;
|
||||
case CombatEventType.playerEvade:
|
||||
case CombatEventType.monsterEvade:
|
||||
case CombatEventType.playerBlock:
|
||||
case CombatEventType.playerParry:
|
||||
// 회피/방어는 별도 SFX 없음
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// 전투 이벤트를 메시지와 타입으로 변환
|
||||
(String, CombatLogType) _formatCombatEvent(CombatEvent event) {
|
||||
final target = event.targetName ?? '';
|
||||
@@ -256,6 +340,10 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
game_l10n.combatBuffActivated(skillName),
|
||||
CombatLogType.buff,
|
||||
),
|
||||
CombatEventType.playerDebuff => (
|
||||
game_l10n.combatDebuffApplied(skillName, target),
|
||||
CombatLogType.debuff,
|
||||
),
|
||||
CombatEventType.dotTick => (
|
||||
game_l10n.combatDotTick(skillName, event.damage),
|
||||
CombatLogType.dotTick,
|
||||
@@ -361,6 +449,29 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
_lastQuestCount = state.progress.questCount;
|
||||
_lastPlotStageCount = state.progress.plotStageCount;
|
||||
_lastAct = getActForLevel(state.traits.level);
|
||||
|
||||
// 초기 전투 상태 확인
|
||||
final combat = state.progress.currentCombat;
|
||||
_wasInCombat = combat != null && combat.isActive;
|
||||
}
|
||||
|
||||
// 누적 통계 로드
|
||||
widget.controller.loadCumulativeStats();
|
||||
|
||||
// 초기 BGM 재생 (마을 테마)
|
||||
widget.audioService?.playBgm('town');
|
||||
|
||||
// 오디오 볼륨 초기화
|
||||
_initAudioVolumes();
|
||||
}
|
||||
|
||||
/// 오디오 볼륨 초기화 (설정에서 로드)
|
||||
Future<void> _initAudioVolumes() async {
|
||||
final audio = widget.audioService;
|
||||
if (audio != null) {
|
||||
_bgmVolume = audio.bgmVolume;
|
||||
_sfxVolume = audio.sfxVolume;
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,6 +576,15 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
return platform == TargetPlatform.iOS || platform == TargetPlatform.android;
|
||||
}
|
||||
|
||||
/// 통계 다이얼로그 표시
|
||||
void _showStatisticsDialog(BuildContext context) {
|
||||
StatisticsDialog.show(
|
||||
context,
|
||||
session: widget.controller.sessionStats,
|
||||
cumulative: widget.controller.cumulativeStats,
|
||||
);
|
||||
}
|
||||
|
||||
/// 설정 화면 표시
|
||||
void _showSettingsScreen(BuildContext context) {
|
||||
final settingsRepo = SettingsRepository();
|
||||
@@ -614,6 +734,17 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
},
|
||||
currentThemeMode: widget.currentThemeMode,
|
||||
onThemeModeChange: widget.onThemeModeChange,
|
||||
// 사운드 설정
|
||||
bgmVolume: _bgmVolume,
|
||||
sfxVolume: _sfxVolume,
|
||||
onBgmVolumeChange: (volume) {
|
||||
setState(() => _bgmVolume = volume);
|
||||
widget.audioService?.setBgmVolume(volume);
|
||||
},
|
||||
onSfxVolumeChange: (volume) {
|
||||
setState(() => _sfxVolume = volume);
|
||||
widget.audioService?.setSfxVolume(volume);
|
||||
},
|
||||
),
|
||||
// 사망 오버레이
|
||||
if (state.isDead && state.deathInfo != null)
|
||||
@@ -666,6 +797,18 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
onPressed: () => widget.controller.loop?.cheatCompletePlot(),
|
||||
),
|
||||
],
|
||||
// 통계 버튼
|
||||
IconButton(
|
||||
icon: const Icon(Icons.bar_chart),
|
||||
tooltip: game_l10n.uiStatistics,
|
||||
onPressed: () => _showStatisticsDialog(context),
|
||||
),
|
||||
// 도움말 버튼
|
||||
IconButton(
|
||||
icon: const Icon(Icons.help_outline),
|
||||
tooltip: game_l10n.uiHelp,
|
||||
onPressed: () => HelpDialog.show(context),
|
||||
),
|
||||
// 설정 버튼
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
|
||||
Reference in New Issue
Block a user