feat(animation): Act 기반 몬스터 사이즈 시스템 추가

- Act 진행에 따른 몬스터 사이즈 확률 조정
- 보스: Act별 고정 사이즈 (소/중/대)
- 일반/엘리트: Act별 확률 랜덤
- TaskInfo에 monsterSize 필드 추가
- 애니메이션 패널에서 Act 기반 사이즈 사용
This commit is contained in:
JiWoong Sul
2026-01-15 18:01:31 +09:00
parent 23f15f41d3
commit ac76060222
10 changed files with 118 additions and 8 deletions

View File

@@ -734,6 +734,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
characterLevel: state.traits.level,
monsterLevel: state.progress.currentTask.monsterLevel,
monsterGrade: state.progress.currentTask.monsterGrade,
monsterSize: state.progress.currentTask.monsterSize,
latestCombatEvent:
state.progress.currentCombat?.recentEvents.lastOrNull,
raceId: state.traits.raceId,

View File

@@ -48,6 +48,9 @@ class GameSessionController extends ChangeNotifier {
GameState? _state;
String? _error;
// 배속 저장 (pause/resume 시 유지)
int _savedSpeedMultiplier = 1;
// 자동 부활 (Auto-Resurrection) 상태
bool _autoResurrect = false;
@@ -89,7 +92,8 @@ class GameSessionController extends ChangeNotifier {
bool isNewGame = true,
}) async {
// 기존 배속 보존 (부활/재개 시 유지)
final previousSpeed = _loop?.speedMultiplier ?? 1;
// _loop가 있으면 현재 배속 사용, 없으면 저장된 배속 사용
final previousSpeed = _loop?.speedMultiplier ?? _savedSpeedMultiplier;
await _stopLoop(saveOnStop: false);
@@ -284,6 +288,12 @@ class GameSessionController extends ChangeNotifier {
Future<void>? _stopLoop({required bool saveOnStop}) {
final loop = _loop;
final sub = _subscription;
// 배속 저장 (resume 시 복원용)
if (loop != null) {
_savedSpeedMultiplier = loop.speedMultiplier;
}
_loop = null;
_subscription = null;

View File

@@ -730,6 +730,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
characterLevel: state.traits.level,
monsterLevel: state.progress.currentTask.monsterLevel,
monsterGrade: state.progress.currentTask.monsterGrade,
monsterSize: state.progress.currentTask.monsterSize,
latestCombatEvent:
state.progress.currentCombat?.recentEvents.lastOrNull,
raceId: state.traits.raceId,

View File

@@ -47,6 +47,7 @@ class AsciiAnimationCard extends StatefulWidget {
this.characterLevel,
this.monsterLevel,
this.monsterGrade,
this.monsterSize,
this.isPaused = false,
this.isInCombat = true,
this.monsterDied = false,
@@ -85,12 +86,15 @@ class AsciiAnimationCard extends StatefulWidget {
/// 캐릭터 레벨
final int? characterLevel;
/// 몬스터 레벨 (몬스터 크기 결정용)
/// 몬스터 레벨 (전투 스탯 계산용)
final int? monsterLevel;
/// 몬스터 등급 (Normal/Elite/Boss) - 색상/접두사 표시용
final MonsterGrade? monsterGrade;
/// 몬스터 사이즈 (애니메이션 크기 결정용, Act 기반)
final MonsterSize? monsterSize;
/// 최근 전투 이벤트 (애니메이션 동기화용)
final CombatEvent? latestCombatEvent;
@@ -175,7 +179,8 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
bool _showDeathAnimation = false;
List<String>? _deathAnimationMonsterLines;
String? _lastMonsterBaseName;
int? _lastMonsterLevel; // 몬스터 레벨 캐시 (사망 시 크기 결정용)
int? _lastMonsterLevel; // 몬스터 레벨 캐시 (사망 시 스탯용)
MonsterSize? _lastMonsterSize; // 몬스터 사이즈 캐시 (사망 시 크기 결정용)
@override
void initState() {
@@ -245,6 +250,9 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
if (widget.monsterLevel != null) {
_lastMonsterLevel = widget.monsterLevel;
}
if (widget.monsterSize != null) {
_lastMonsterSize = widget.monsterSize;
}
// 새 몬스터 등장 시 사망 애니메이션 상태 리셋
// (이전 몬스터 사망 애니메이션이 끝나기 전에 새 전투 시작 시 대응)
@@ -591,7 +599,9 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
final hasShield =
widget.shieldName != null && widget.shieldName!.isNotEmpty;
final monsterCategory = getMonsterCategory(widget.monsterBaseName);
final monsterSize = getMonsterSize(widget.monsterLevel);
// Act 기반 사이즈 사용 (Phase 13), 없으면 레벨 기반 fallback
final monsterSize =
widget.monsterSize ?? getMonsterSize(widget.monsterLevel);
_battleComposer = CanvasBattleComposer(
weaponCategory: weaponCategory,
@@ -617,8 +627,10 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
if (monsterName == null) return null;
final monsterCategory = getMonsterCategory(monsterName);
// 캐시된 레벨 사용 (사망 시점에 widget.monsterLevel이 null일 수 있음)
final monsterSize = getMonsterSize(_lastMonsterLevel ?? widget.monsterLevel);
// 캐시된 사이즈 사용 (사망 시점에 widget.monsterSize가 null일 수 있음)
final monsterSize = _lastMonsterSize ??
widget.monsterSize ??
getMonsterSize(_lastMonsterLevel ?? widget.monsterLevel);
// 몬스터 Idle 프레임 가져오기
final frames = getMonsterIdleFrames(monsterCategory, monsterSize);

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
import 'package:asciineverdie/src/core/animation/ascii_animation_type.dart';
import 'package:asciineverdie/src/core/animation/monster_size.dart';
import 'package:asciineverdie/src/core/model/combat_event.dart';
import 'package:asciineverdie/src/core/model/combat_state.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
@@ -32,6 +33,7 @@ class EnhancedAnimationPanel extends StatefulWidget {
this.characterLevel,
this.monsterLevel,
this.monsterGrade,
this.monsterSize,
this.latestCombatEvent,
this.raceId,
this.weaponRarity,
@@ -52,6 +54,9 @@ class EnhancedAnimationPanel extends StatefulWidget {
/// 몬스터 등급 (Normal/Elite/Boss) - UI 색상/접두사 표시용
final MonsterGrade? monsterGrade;
/// 몬스터 사이즈 (애니메이션 크기 결정용, Act 기반)
final MonsterSize? monsterSize;
final CombatEvent? latestCombatEvent;
/// 종족 ID (Phase 4: 종족별 캐릭터 애니메이션)
@@ -225,6 +230,7 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
characterLevel: widget.characterLevel,
monsterLevel: widget.monsterLevel,
monsterGrade: widget.monsterGrade,
monsterSize: widget.monsterSize,
isPaused: widget.isPaused,
isInCombat: isInCombat,
monsterDied: _monsterDied,

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
import 'package:asciineverdie/l10n/app_localizations.dart';
import 'package:asciineverdie/src/core/animation/ascii_animation_type.dart';
import 'package:asciineverdie/src/core/animation/monster_size.dart';
import 'package:asciineverdie/src/core/model/combat_event.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/monster_grade.dart';
@@ -25,6 +26,7 @@ class TaskProgressPanel extends StatelessWidget {
this.characterLevel,
this.monsterLevel,
this.monsterGrade,
this.monsterSize,
this.latestCombatEvent,
this.raceId,
});
@@ -49,6 +51,9 @@ class TaskProgressPanel extends StatelessWidget {
/// 몬스터 등급 (Normal/Elite/Boss) - UI 색상/접두사 표시용
final MonsterGrade? monsterGrade;
/// 몬스터 사이즈 (애니메이션 크기 결정용, Act 기반)
final MonsterSize? monsterSize;
/// 최근 전투 이벤트 (애니메이션 동기화용, Phase 5)
final CombatEvent? latestCombatEvent;
@@ -80,6 +85,7 @@ class TaskProgressPanel extends StatelessWidget {
characterLevel: characterLevel,
monsterLevel: monsterLevel,
monsterGrade: monsterGrade,
monsterSize: monsterSize,
isPaused: isPaused,
latestCombatEvent: latestCombatEvent,
raceId: raceId,