feat(ui): 게임 위젯 개선
- AsciiAnimationCard 확장 - EnhancedAnimationPanel 개선 - HpMpBar UI 개선
This commit is contained in:
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:asciineverdie/src/core/animation/ascii_animation_data.dart';
|
||||
import 'package:asciineverdie/src/shared/widgets/ascii_disintegrate_widget.dart';
|
||||
import 'package:asciineverdie/src/core/animation/ascii_animation_type.dart';
|
||||
import 'package:asciineverdie/src/core/animation/background_layer.dart';
|
||||
import 'package:asciineverdie/src/core/animation/canvas/ascii_canvas_widget.dart';
|
||||
@@ -47,6 +48,8 @@ class AsciiAnimationCard extends StatefulWidget {
|
||||
this.monsterLevel,
|
||||
this.monsterGrade,
|
||||
this.isPaused = false,
|
||||
this.isInCombat = true,
|
||||
this.monsterDied = false,
|
||||
this.latestCombatEvent,
|
||||
this.raceId,
|
||||
this.weaponRarity,
|
||||
@@ -59,6 +62,12 @@ class AsciiAnimationCard extends StatefulWidget {
|
||||
/// 일시정지 상태 (true면 애니메이션 정지)
|
||||
final bool isPaused;
|
||||
|
||||
/// 전투 활성 상태 (false면 kill 태스크여도 walking 애니메이션)
|
||||
final bool isInCombat;
|
||||
|
||||
/// 몬스터 사망 여부 (true면 분해 애니메이션 재생)
|
||||
final bool monsterDied;
|
||||
|
||||
/// 전투 중인 몬스터 기본 이름 (kill 타입일 때만 사용)
|
||||
final String? monsterBaseName;
|
||||
final AsciiColorTheme colorTheme;
|
||||
@@ -162,6 +171,11 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
// 특수 애니메이션 프레임 수는 ascii_animation_type.dart의
|
||||
// specialAnimationFrameCounts 상수 사용
|
||||
|
||||
// 몬스터 사망 분해 애니메이션 상태
|
||||
bool _showDeathAnimation = false;
|
||||
List<String>? _deathAnimationMonsterLines;
|
||||
String? _lastMonsterBaseName;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -204,12 +218,32 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
return;
|
||||
}
|
||||
|
||||
// 몬스터 사망 애니메이션 트리거
|
||||
if (!oldWidget.monsterDied && widget.monsterDied && !_showDeathAnimation) {
|
||||
// 현재 몬스터 프레임 캡처 (분해 애니메이션용)
|
||||
_deathAnimationMonsterLines = _captureMonsterFrame();
|
||||
if (_deathAnimationMonsterLines != null) {
|
||||
setState(() {
|
||||
_showDeathAnimation = true;
|
||||
});
|
||||
return; // 사망 애니메이션 중에는 다른 업데이트 무시
|
||||
}
|
||||
}
|
||||
|
||||
// 사망 애니메이션 중에는 다른 업데이트 무시
|
||||
if (_showDeathAnimation) return;
|
||||
|
||||
// 전투 이벤트 동기화 (Phase 5)
|
||||
if (widget.latestCombatEvent != null &&
|
||||
widget.latestCombatEvent!.timestamp != _lastEventTimestamp) {
|
||||
_handleCombatEvent(widget.latestCombatEvent!);
|
||||
}
|
||||
|
||||
// 몬스터 이름 저장 (사망 시 프레임 캡처용)
|
||||
if (widget.monsterBaseName != null) {
|
||||
_lastMonsterBaseName = widget.monsterBaseName;
|
||||
}
|
||||
|
||||
if (oldWidget.taskType != widget.taskType ||
|
||||
oldWidget.monsterBaseName != widget.monsterBaseName ||
|
||||
oldWidget.weaponName != widget.weaponName ||
|
||||
@@ -218,7 +252,8 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
oldWidget.raceId != widget.raceId ||
|
||||
oldWidget.weaponRarity != widget.weaponRarity ||
|
||||
oldWidget.opponentRaceId != widget.opponentRaceId ||
|
||||
oldWidget.opponentHasShield != widget.opponentHasShield) {
|
||||
oldWidget.opponentHasShield != widget.opponentHasShield ||
|
||||
oldWidget.isInCombat != widget.isInCombat) {
|
||||
_updateAnimation();
|
||||
}
|
||||
}
|
||||
@@ -505,12 +540,18 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
|
||||
switch (animationType) {
|
||||
case AsciiAnimationType.battle:
|
||||
_animationMode = AnimationMode.battle;
|
||||
_setupBattleComposer();
|
||||
_battlePhase = BattlePhase.idle;
|
||||
_battleSubFrame = 0;
|
||||
_phaseIndex = 0;
|
||||
_phaseFrameCount = 0;
|
||||
// 전투 비활성 상태면 walking 모드로 전환 (몬스터 처치 후 이동 중)
|
||||
if (!widget.isInCombat) {
|
||||
_animationMode = AnimationMode.walking;
|
||||
_walkingComposer = CanvasWalkingComposer(raceId: widget.raceId);
|
||||
} else {
|
||||
_animationMode = AnimationMode.battle;
|
||||
_setupBattleComposer();
|
||||
_battlePhase = BattlePhase.idle;
|
||||
_battleSubFrame = 0;
|
||||
_phaseIndex = 0;
|
||||
_phaseFrameCount = 0;
|
||||
}
|
||||
|
||||
case AsciiAnimationType.town:
|
||||
_animationMode = AnimationMode.town;
|
||||
@@ -556,6 +597,31 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 현재 몬스터 프레임을 텍스트 라인으로 캡처 (분해 애니메이션용)
|
||||
List<String>? _captureMonsterFrame() {
|
||||
final monsterName = _lastMonsterBaseName ?? widget.monsterBaseName;
|
||||
if (monsterName == null) return null;
|
||||
|
||||
final monsterCategory = getMonsterCategory(monsterName);
|
||||
final monsterSize = getMonsterSize(widget.monsterLevel);
|
||||
|
||||
// 몬스터 Idle 프레임 가져오기
|
||||
final frames = getMonsterIdleFrames(monsterCategory, monsterSize);
|
||||
if (frames.isEmpty) return null;
|
||||
|
||||
return frames.first;
|
||||
}
|
||||
|
||||
/// 사망 애니메이션 완료 콜백
|
||||
void _onDeathAnimationComplete() {
|
||||
setState(() {
|
||||
_showDeathAnimation = false;
|
||||
_deathAnimationMonsterLines = null;
|
||||
});
|
||||
// Walking 모드로 전환
|
||||
_updateAnimation();
|
||||
}
|
||||
|
||||
void _advanceBattleFrame() {
|
||||
_phaseFrameCount++;
|
||||
final currentPhase = _battlePhaseSequence[_phaseIndex];
|
||||
@@ -617,6 +683,7 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
isDot: _showDotEffect,
|
||||
isBlock: _showBlockEffect,
|
||||
isParry: _showParryEffect,
|
||||
hideMonster: _showDeathAnimation,
|
||||
) ??
|
||||
[AsciiLayer.empty()],
|
||||
AnimationMode.walking =>
|
||||
@@ -676,7 +743,26 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: borderEffect,
|
||||
),
|
||||
child: AsciiCanvasWidget(layers: _composeLayers()),
|
||||
child: Stack(
|
||||
children: [
|
||||
// 기본 애니메이션
|
||||
AsciiCanvasWidget(layers: _composeLayers()),
|
||||
|
||||
// 몬스터 사망 분해 애니메이션 오버레이
|
||||
// 몬스터 위치: 캔버스 60열 중 30~48열 (중앙값 41열)
|
||||
// Alignment x = (41/60) * 2 - 1 = 0.37
|
||||
if (_showDeathAnimation && _deathAnimationMonsterLines != null)
|
||||
Align(
|
||||
alignment: const Alignment(0.37, 0.0),
|
||||
child: AsciiDisintegrateWidget(
|
||||
characterLines: _deathAnimationMonsterLines!,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
textColor: widget.monsterGrade?.displayColor,
|
||||
onComplete: _onDeathAnimationComplete,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user