feat(animation): 종족별 캐릭터 애니메이션 시스템 추가

- 21개 종족별 고유 ASCII 캐릭터 프레임 데이터 추가
  - 각 종족당 5가지 상태 애니메이션: idle, prepare, attack, hit, recover
  - 종족 특성에 맞는 시각적 차별화 (마법사 ~, 기사 ♦, 언데드 ☠ 등)
- 캐릭터 생성 화면 종족 미리보기 위젯 추가
- 프론트 화면 Hero vs Boss 애니메이션 개선
- 게임 플레이 화면 애니메이션 패널 연동 강화
This commit is contained in:
JiWoong Sul
2025-12-23 20:00:41 +09:00
parent 549851f693
commit 7219f58853
11 changed files with 1186 additions and 64 deletions

View File

@@ -485,10 +485,24 @@ class _GamePlayScreenState extends State<GamePlayScreen>
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
onTap: () {
onTap: () async {
Navigator.pop(context); // 다이얼로그 닫기
// 안전한 언어 변경: 전체 화면 재생성
final navigator = Navigator.of(this.context);
await widget.controller.pause(saveOnStop: true);
game_l10n.setGameLocale(locale);
setState(() {});
if (mounted) {
await widget.controller.resume();
navigator.pushReplacement(
MaterialPageRoute<void>(
builder: (_) => GamePlayScreen(
controller: widget.controller,
currentThemeMode: widget.currentThemeMode,
onThemeModeChange: widget.onThemeModeChange,
),
),
);
}
},
);
}
@@ -573,6 +587,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
notificationService: _notificationService,
specialAnimation: _specialAnimation,
onLanguageChange: (locale) async {
// navigator 참조를 async gap 전에 저장
final navigator = Navigator.of(context);
// 1. 현재 상태 저장
await widget.controller.pause(saveOnStop: true);
// 2. 로케일 변경
@@ -580,7 +596,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
// 3. 화면 재생성 (전체 UI 재구성)
if (context.mounted) {
await widget.controller.resume();
Navigator.of(context).pushReplacement(
navigator.pushReplacement(
MaterialPageRoute<void>(
builder: (_) => GamePlayScreen(
controller: widget.controller,
@@ -689,6 +705,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
monsterLevel: state.progress.currentTask.monsterLevel,
latestCombatEvent:
state.progress.currentCombat?.recentEvents.lastOrNull,
raceId: state.traits.raceId,
),
// 메인 3패널 영역

View File

@@ -405,6 +405,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
monsterLevel: state.progress.currentTask.monsterLevel,
latestCombatEvent:
state.progress.currentCombat?.recentEvents.lastOrNull,
raceId: state.traits.raceId,
),
// 중앙: 캐로셀 (PageView)

View File

@@ -45,6 +45,7 @@ class AsciiAnimationCard extends StatefulWidget {
this.monsterLevel,
this.isPaused = false,
this.latestCombatEvent,
this.raceId,
});
final TaskType taskType;
@@ -75,6 +76,9 @@ class AsciiAnimationCard extends StatefulWidget {
/// 최근 전투 이벤트 (애니메이션 동기화용)
final CombatEvent? latestCombatEvent;
/// 종족 ID (Phase 4: 종족별 캐릭터 애니메이션)
final String? raceId;
@override
State<AsciiAnimationCard> createState() => _AsciiAnimationCardState();
}
@@ -168,7 +172,8 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
oldWidget.monsterBaseName != widget.monsterBaseName ||
oldWidget.weaponName != widget.weaponName ||
oldWidget.shieldName != widget.shieldName ||
oldWidget.monsterLevel != widget.monsterLevel) {
oldWidget.monsterLevel != widget.monsterLevel ||
oldWidget.raceId != widget.raceId) {
_updateAnimation();
}
}
@@ -391,6 +396,7 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
hasShield: hasShield,
monsterCategory: monsterCategory,
monsterSize: monsterSize,
raceId: widget.raceId,
);
// 환경 타입 추론

View File

@@ -30,6 +30,7 @@ class EnhancedAnimationPanel extends StatefulWidget {
this.characterLevel,
this.monsterLevel,
this.latestCombatEvent,
this.raceId,
});
final ProgressState progress;
@@ -46,6 +47,9 @@ class EnhancedAnimationPanel extends StatefulWidget {
final int? monsterLevel;
final CombatEvent? latestCombatEvent;
/// 종족 ID (Phase 4: 종족별 캐릭터 애니메이션)
final String? raceId;
@override
State<EnhancedAnimationPanel> createState() => _EnhancedAnimationPanelState();
}
@@ -183,6 +187,7 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
monsterLevel: widget.monsterLevel,
isPaused: widget.isPaused,
latestCombatEvent: widget.latestCombatEvent,
raceId: widget.raceId,
),
),

View File

@@ -23,6 +23,7 @@ class TaskProgressPanel extends StatelessWidget {
this.characterLevel,
this.monsterLevel,
this.latestCombatEvent,
this.raceId,
});
final ProgressState progress;
@@ -45,6 +46,9 @@ class TaskProgressPanel extends StatelessWidget {
/// 최근 전투 이벤트 (애니메이션 동기화용, Phase 5)
final CombatEvent? latestCombatEvent;
/// 종족 ID (Phase 4: 종족별 캐릭터 애니메이션)
final String? raceId;
@override
Widget build(BuildContext context) {
return Container(
@@ -71,6 +75,7 @@ class TaskProgressPanel extends StatelessWidget {
monsterLevel: monsterLevel,
isPaused: isPaused,
latestCombatEvent: latestCombatEvent,
raceId: raceId,
),
),
const SizedBox(height: 8),