feat(story): Phase 9 스토리/엔딩 시스템 구현

- story_data.dart: 5개 Act 스토리 텍스트 및 ASCII 아트
- story_service.dart: Act 전환/보스 조우/엔딩 이벤트 관리
- cinematic_view.dart: 풀스크린 시네마틱 UI (페이드, 스킵)
- game_play_screen.dart: 레벨 기반 Act 전환 시 시네마틱 재생
This commit is contained in:
JiWoong Sul
2025-12-17 18:38:08 +09:00
parent 8cbef3475b
commit abcb89d334
4 changed files with 902 additions and 0 deletions

View File

@@ -1,12 +1,15 @@
import 'package:flutter/material.dart';
import 'package:askiineverdie/data/story_data.dart';
import 'package:askiineverdie/l10n/app_localizations.dart';
import 'package:askiineverdie/src/core/animation/ascii_animation_type.dart';
import 'package:askiineverdie/src/core/engine/story_service.dart';
import 'package:askiineverdie/src/core/l10n/game_data_l10n.dart';
import 'package:askiineverdie/src/core/model/game_state.dart';
import 'package:askiineverdie/src/core/notification/notification_service.dart';
import 'package:askiineverdie/src/core/util/pq_logic.dart' as pq_logic;
import 'package:askiineverdie/src/features/game/game_session_controller.dart';
import 'package:askiineverdie/src/features/game/widgets/cinematic_view.dart';
import 'package:askiineverdie/src/features/game/widgets/notification_overlay.dart';
import 'package:askiineverdie/src/features/game/widgets/stats_panel.dart';
import 'package:askiineverdie/src/features/game/widgets/task_progress_panel.dart';
@@ -30,6 +33,11 @@ class _GamePlayScreenState extends State<GamePlayScreen>
// Phase 8: 알림 서비스 (Notification Service)
late final NotificationService _notificationService;
// Phase 9: 스토리 서비스 (Story Service)
late final StoryService _storyService;
StoryAct _lastAct = StoryAct.prologue;
bool _showingCinematic = false;
// 이전 상태 추적 (레벨업/퀘스트/Act 완료 감지용)
int _lastLevel = 0;
int _lastQuestCount = 0;
@@ -41,6 +49,13 @@ class _GamePlayScreenState extends State<GamePlayScreen>
_specialAnimation = AsciiAnimationType.levelUp;
_notificationService.showLevelUp(state.traits.level);
_resetSpecialAnimationAfterFrame();
// Phase 9: Act 변경 감지 (레벨 기반)
final newAct = getActForLevel(state.traits.level);
if (newAct != _lastAct && !_showingCinematic) {
_lastAct = newAct;
_showCinematicForAct(newAct);
}
}
_lastLevel = state.traits.level;
@@ -68,6 +83,25 @@ class _GamePlayScreenState extends State<GamePlayScreen>
_lastPlotStageCount = state.progress.plotStageCount;
}
/// Phase 9: Act 시네마틱 표시 (Show Act Cinematic)
Future<void> _showCinematicForAct(StoryAct act) async {
if (_showingCinematic) return;
_showingCinematic = true;
// 게임 일시 정지
await widget.controller.pause(saveOnStop: false);
if (mounted) {
await showActCinematic(context, act);
}
// 게임 재개
if (mounted) {
await widget.controller.resume();
}
_showingCinematic = false;
}
void _resetSpecialAnimationAfterFrame() {
// 다음 프레임에서 리셋 (AsciiAnimationCard가 값을 받은 후)
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -83,6 +117,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
void initState() {
super.initState();
_notificationService = NotificationService();
_storyService = StoryService();
widget.controller.addListener(_onControllerChanged);
WidgetsBinding.instance.addObserver(this);
@@ -92,12 +127,14 @@ class _GamePlayScreenState extends State<GamePlayScreen>
_lastLevel = state.traits.level;
_lastQuestCount = state.progress.questCount;
_lastPlotStageCount = state.progress.plotStageCount;
_lastAct = getActForLevel(state.traits.level);
}
}
@override
void dispose() {
_notificationService.dispose();
_storyService.dispose();
WidgetsBinding.instance.removeObserver(this);
widget.controller.removeListener(_onControllerChanged);
super.dispose();