This commit is contained in:
2025-12-16 02:13:31 +09:00
parent c029cd1e10
commit f5a7eb2db9
19 changed files with 378 additions and 87 deletions
@@ -53,8 +53,13 @@ class BattleAnimationWidgetState extends State<BattleAnimationWidget>
Future<void> animateAttack(
Offset targetOffset,
VoidCallback onImpact,
RiskLevel risk,
) async {
RiskLevel risk, {
VoidCallback? onAnimationStart,
VoidCallback? onAnimationMiddle,
VoidCallback? onAnimationEnd,
}) async {
// onAnimationStart?.call(); // Start Phase
if (risk == RiskLevel.safe || risk == RiskLevel.normal) {
// Safe & Normal: Dash/Wobble without scale
final isSafe = risk == RiskLevel.safe;
@@ -75,9 +80,13 @@ class BattleAnimationWidgetState extends State<BattleAnimationWidget>
await _translateController.forward();
if (!mounted) return;
// onAnimationMiddle?.call(); // Middle Phase
onImpact();
await _translateController.reverse();
} else {
onAnimationStart?.call(); // Start Phase
// Risky: Scale + Heavy Dash
final attackScale = context.read<SettingsProvider>().attackAnimScale;
_scaleAnimation = Tween<double>(begin: 1.0, end: attackScale).animate(
@@ -91,6 +100,8 @@ class BattleAnimationWidgetState extends State<BattleAnimationWidget>
await _scaleController.forward();
if (!mounted) return;
onAnimationMiddle?.call(); // Middle Phase
// 2. Dash to Target (Impact)
// Adjust offset to prevent complete overlap (stop slightly short) since both share the same layer stack
final adjustedOffset = targetOffset * 0.5;
@@ -106,13 +117,17 @@ class BattleAnimationWidgetState extends State<BattleAnimationWidget>
await _translateController.forward();
if (!mounted) return;
// onAnimationEnd?.call(); // End Phase (Moved before Impact)
// 3. Impact Callback (Shake)
onImpact();
// 4. Return (Reset)
_scaleController.reverse();
_translateController.reverse();
await _translateController.reverse();
}
// onAnimationEnd removed from here
}
Future<void> animateDefense(VoidCallback onImpact) async {
+31 -52
View File
@@ -12,6 +12,7 @@ class CharacterStatusCard extends StatelessWidget {
final bool isTurn;
final GlobalKey<BattleAnimationWidgetState>? animationKey;
final bool hideStats;
final String? overrideImage;
const CharacterStatusCard({
super.key,
@@ -20,6 +21,7 @@ class CharacterStatusCard extends StatelessWidget {
this.isTurn = false,
this.animationKey,
this.hideStats = false,
this.overrideImage,
});
@override
@@ -51,7 +53,7 @@ class CharacterStatusCard extends StatelessWidget {
),
),
SizedBox(
width: 100,
width: ThemeConfig.playerImageSize,
child: LinearProgressIndicator(
value: character.totalMaxHp > 0
? character.hp / character.totalMaxHp
@@ -84,7 +86,7 @@ class CharacterStatusCard extends StatelessWidget {
"${effect.type.name.toUpperCase()} (${effect.duration})",
style: const TextStyle(
color: ThemeConfig.effectText,
fontSize: 10,
fontSize: ThemeConfig.statusEffectFontSize,
fontWeight: FontWeight.bold,
),
),
@@ -92,64 +94,51 @@ class CharacterStatusCard extends StatelessWidget {
}).toList(),
),
),
// Text(
// "ATK: ${character.totalAtk}",
// style: const TextStyle(color: ThemeConfig.textColorWhite),
// ),
// Text(
// "DEF: ${character.totalDefense}",
// style: const TextStyle(color: ThemeConfig.textColorWhite),
// ),
// Text(
// "LUCK: ${character.totalLuck}",
// style: const TextStyle(color: ThemeConfig.textColorWhite),
// ),
],
),
),
const SizedBox(height: 8), // 아이콘과 정보 사이 간격
// 캐릭터 아이콘/이미지 영역 추가
const SizedBox(height: 8),
BattleAnimationWidget(
key: animationKey,
child: Container(
width: isPlayer ? 100 : 200, // 플레이어 100, 적 200
height: isPlayer ? 100 : 200, // 플레이어 100, 적 200
width: isPlayer
? ThemeConfig.playerImageSize
: ThemeConfig.enemyImageSize,
height: isPlayer
? ThemeConfig.playerImageSize
: ThemeConfig.enemyImageSize,
decoration: BoxDecoration(
color: isPlayer ? Colors.lightBlue : null,
// color: isPlayer ? ThemeConfig.playerImageBgColor : null,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: isPlayer
? const Icon(
Icons.person,
size: 60,
color: ThemeConfig.textColorWhite,
) // 플레이어 아이콘
: (character.image != null && character.image!.isNotEmpty)
child: (overrideImage != null ||
(character.image != null && character.image!.isNotEmpty))
? Image.asset(
character.image!,
width: 200,
height: 200,
overrideImage ?? character.image!,
width: isPlayer
? ThemeConfig.playerImageSize
: ThemeConfig.enemyImageSize,
height: isPlayer
? ThemeConfig.playerImageSize
: ThemeConfig.enemyImageSize,
fit: BoxFit.contain,
// color: Colors.white,
// colorBlendMode: BlendMode.screen,
errorBuilder: (context, error, stackTrace) {
return const Icon(
Icons.psychology,
size: 60,
Icons.error_outline,
size: ThemeConfig.characterIconSize,
color: ThemeConfig.textColorWhite,
);
},
)
: const Icon(
Icons.psychology,
size: 60,
: Icon(
isPlayer ? Icons.person : Icons.psychology,
size: ThemeConfig.characterIconSize,
color: ThemeConfig.textColorWhite,
), // 적 이미지
),
),
),
),
// const SizedBox(height: 8), // 아이콘과 정보 사이 간격
if (!isPlayer && !hideStats)
Consumer<BattleProvider>(
builder: (context, provider, child) {
@@ -166,14 +155,6 @@ class CharacterStatusCard extends StatelessWidget {
),
child: Column(
children: [
// Text(
// "INTENT",
// style: TextStyle(
// color: ThemeConfig.enemyIntentBorder,
// fontSize: 10,
// fontWeight: FontWeight.bold,
// ),
// ),
Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -181,20 +162,18 @@ class CharacterStatusCard extends StatelessWidget {
intent.type == EnemyActionType.attack
? Icons.flash_on
: Icons.shield,
color: ThemeConfig.rarityRare, // Yellow
size: 16,
color: ThemeConfig.rarityRare,
size: ThemeConfig.intentIconSize,
),
const SizedBox(width: 4),
Flexible(
// Use Flexible to allow text to shrink
child: FittedBox(
fit:
BoxFit.scaleDown, // Shrink text if too long
fit: BoxFit.scaleDown,
child: Text(
intent.description,
style: const TextStyle(
color: ThemeConfig.textColorWhite,
fontSize: 12,
fontSize: ThemeConfig.intentFontSize,
),
),
),