Feat: Implement Enemy Attack Animation

This commit is contained in:
Horoli 2025-12-07 19:18:40 +09:00
parent 9a7498af35
commit 78267b7e47
2 changed files with 82 additions and 6 deletions

View File

@ -42,10 +42,13 @@ class _BattleScreenState extends State<BattleScreen> {
final GlobalKey<ShakeWidgetState> _shakeKey = GlobalKey<ShakeWidgetState>(); final GlobalKey<ShakeWidgetState> _shakeKey = GlobalKey<ShakeWidgetState>();
final GlobalKey<BattleAnimationWidgetState> _playerAnimKey = final GlobalKey<BattleAnimationWidgetState> _playerAnimKey =
GlobalKey<BattleAnimationWidgetState>(); GlobalKey<BattleAnimationWidgetState>();
final GlobalKey<BattleAnimationWidgetState> _enemyAnimKey =
GlobalKey<BattleAnimationWidgetState>(); // Added Enemy Anim Key
final GlobalKey<ExplosionWidgetState> _explosionKey = final GlobalKey<ExplosionWidgetState> _explosionKey =
GlobalKey<ExplosionWidgetState>(); GlobalKey<ExplosionWidgetState>();
bool _showLogs = false; bool _showLogs = false;
bool _isPlayerAttacking = false; // Player Attack Animation State bool _isPlayerAttacking = false; // Player Attack Animation State
bool _isEnemyAttacking = false; // Enemy Attack Animation State
@override @override
void initState() { void initState() {
@ -287,8 +290,60 @@ class _BattleScreenState extends State<BattleScreen> {
} }
}); });
} }
} else if (event.type == ActionType.attack &&
event.target == EffectTarget.player &&
event.feedbackType == null) {
// 2. Enemy Attack Animation Trigger
final RenderBox? playerBox =
_playerKey.currentContext?.findRenderObject() as RenderBox?;
final RenderBox? enemyBox =
_enemyKey.currentContext?.findRenderObject() as RenderBox?;
if (playerBox != null && enemyBox != null) {
final playerPos = playerBox.localToGlobal(Offset.zero);
final enemyPos = enemyBox.localToGlobal(Offset.zero);
// Enemy moves TO Player
final offset = playerPos - enemyPos;
// Start Animation: Hide Stats
setState(() {
_isEnemyAttacking = true;
});
_enemyAnimKey.currentState
?.animateAttack(offset, () {
showEffect(); // Show Effect at Impact!
// Shake and Explosion ONLY for Risky (Enemy can also do risky attacks)
if (event.risk == RiskLevel.risky) {
_shakeKey.currentState?.shake();
// Explosion on Player
RenderBox? stackBox =
_stackKey.currentContext?.findRenderObject()
as RenderBox?;
if (stackBox != null) {
Offset localPlayerPos = stackBox.globalToLocal(playerPos);
localPlayerPos += Offset(
playerBox.size.width / 2,
playerBox.size.height / 2,
);
_explosionKey.currentState?.explode(localPlayerPos);
}
}
}, event.risk)
.then((_) {
// End Animation: Show Stats
if (mounted) {
setState(() {
_isEnemyAttacking = false;
});
}
});
}
} else { } else {
// Not a player attack, show immediately // Not a player/enemy attack movement, show immediately
showEffect(); showEffect();
} }
}); });
@ -444,11 +499,15 @@ class _BattleScreenState extends State<BattleScreen> {
Positioned( Positioned(
top: 0, top: 0,
right: 0, right: 0,
child: CharacterStatusCard( child: BattleAnimationWidget(
character: battleProvider.enemy, key: _enemyAnimKey,
isPlayer: false, child: CharacterStatusCard(
isTurn: !battleProvider.isPlayerTurn, character: battleProvider.enemy,
key: _enemyKey, isPlayer: false,
isTurn: !battleProvider.isPlayerTurn,
key: _enemyKey,
hideStats: _isEnemyAttacking,
),
), ),
), ),
// Player (Bottom Left) // Player (Bottom Left)

View File

@ -0,0 +1,17 @@
# 62. Implement Enemy Attack Animation
## 1. 목표 (Goal)
- 플레이어뿐만 아니라 적(Enemy)이 공격할 때도 플레이어 쪽으로 돌진하는 애니메이션을 추가하여 전투의 역동성을 높입니다.
## 2. 구현 계획 (Implementation Plan)
1. **`BattleScreen` 수정:**
- `GlobalKey<BattleAnimationWidgetState> _enemyAnimKey`를 추가합니다.
- 적 캐릭터 UI(`CharacterStatusCard`)를 `BattleAnimationWidget`으로 감쌉니다.
2. **애니메이션 트리거 로직 (`_addFloatingEffect`):**
- 기존 플레이어 공격 감지 로직과 유사하게, `event.type == ActionType.attack`이고 `event.target == EffectTarget.player`인 경우를 감지합니다.
- 적 위치에서 플레이어 위치로의 오프셋(`playerPos - enemyPos`)을 계산하여 `_enemyAnimKey`로 애니메이션을 실행합니다.
- 공격 모션 중에는 적의 스탯 정보(HP바 등)를 일시적으로 숨기는 로직(`_isEnemyAttacking`)도 추가합니다.
## 3. 기대 효과 (Expected Outcome)
- 적의 턴에도 시각적인 움직임이 발생하여 전투가 더 생동감 있게 느껴짐.
- 플레이어와 적의 상호작용이 명확해짐.