feat(animation): 공격 속도 기반 동적 애니메이션 페이즈

- CombatEvent에 attackDelayMs 필드 추가
- ProgressService에서 전투 이벤트에 공격 속도 전달
- AsciiAnimationCard에서 공격 속도 기반 페이즈 프레임 수 계산
- 200ms tick 기준으로 동적 프레임 수 (최소 2, 최대 10)
This commit is contained in:
JiWoong Sul
2025-12-26 18:10:43 +09:00
parent c55530d3be
commit 6e56420a07
3 changed files with 37 additions and 1 deletions

View File

@@ -1118,6 +1118,7 @@ class ProgressService {
skillName: selectedSkill.name,
damage: skillResult.result.damage,
targetName: monsterStats.name,
attackDelayMs: playerStats.attackDelayMs,
),
);
} else if (selectedSkill != null && selectedSkill.isDot) {
@@ -1144,6 +1145,7 @@ class ProgressService {
skillName: selectedSkill.name,
damage: skillResult.result.damage,
targetName: monsterStats.name,
attackDelayMs: playerStats.attackDelayMs,
),
);
} else if (selectedSkill != null && selectedSkill.isHeal) {
@@ -1206,6 +1208,7 @@ class ProgressService {
damage: result.damage,
targetName: monsterStats.name,
isCritical: result.isCritical,
attackDelayMs: playerStats.attackDelayMs,
),
);
}
@@ -1257,6 +1260,7 @@ class ProgressService {
timestamp: timestamp,
damage: result.damage,
attackerName: monsterStats.name,
attackDelayMs: monsterStats.attackDelayMs,
),
);
}

View File

@@ -49,6 +49,7 @@ class CombatEvent {
this.isCritical = false,
this.skillName,
this.targetName,
this.attackDelayMs,
});
/// 이벤트 타입
@@ -72,12 +73,17 @@ class CombatEvent {
/// 대상 이름 (몬스터 또는 플레이어)
final String? targetName;
/// 공격자의 공격 속도 (ms)
/// 애니메이션 페이즈 지속 시간 계산에 사용
final int? attackDelayMs;
/// 플레이어 공격 이벤트 생성
factory CombatEvent.playerAttack({
required int timestamp,
required int damage,
required String targetName,
bool isCritical = false,
int? attackDelayMs,
}) {
return CombatEvent(
type: CombatEventType.playerAttack,
@@ -85,6 +91,7 @@ class CombatEvent {
damage: damage,
targetName: targetName,
isCritical: isCritical,
attackDelayMs: attackDelayMs,
);
}
@@ -93,12 +100,14 @@ class CombatEvent {
required int timestamp,
required int damage,
required String attackerName,
int? attackDelayMs,
}) {
return CombatEvent(
type: CombatEventType.monsterAttack,
timestamp: timestamp,
damage: damage,
targetName: attackerName,
attackDelayMs: attackDelayMs,
);
}
@@ -161,6 +170,7 @@ class CombatEvent {
required int damage,
required String targetName,
bool isCritical = false,
int? attackDelayMs,
}) {
return CombatEvent(
type: CombatEventType.playerSkill,
@@ -169,6 +179,7 @@ class CombatEvent {
damage: damage,
targetName: targetName,
isCritical: isCritical,
attackDelayMs: attackDelayMs,
);
}

View File

@@ -128,6 +128,10 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
bool _showParryEffect = false;
bool _showSkillEffect = false;
// 공격 속도 기반 동적 페이즈 프레임 수 (Phase 6)
int _eventDrivenPhaseFrames = 0;
bool _isEventDrivenPhase = false;
// 특수 애니메이션 프레임 수는 ascii_animation_type.dart의
// specialAnimationFrameCounts 상수 사용
@@ -318,6 +322,15 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
// 페이즈 인덱스 동기화
_phaseIndex = _battlePhaseSequence.indexWhere((p) => p.$1 == targetPhase);
if (_phaseIndex < 0) _phaseIndex = 0;
// 공격 속도에 따른 동적 페이즈 프레임 수 계산 (Phase 6)
// 200ms tick 기준으로 프레임 수 계산 (최소 2, 최대 10)
if (event.attackDelayMs != null && event.attackDelayMs! > 0) {
_eventDrivenPhaseFrames = (event.attackDelayMs! ~/ 200).clamp(2, 10);
_isEventDrivenPhase = true;
} else {
_isEventDrivenPhase = false;
}
});
}
@@ -434,8 +447,14 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
_phaseFrameCount++;
final currentPhase = _battlePhaseSequence[_phaseIndex];
// 현재 페이즈의 프레임 수 결정 (Phase 6)
// 이벤트 기반 페이즈일 경우 공격 속도에 따른 동적 프레임 수 사용
final targetFrames = _isEventDrivenPhase
? _eventDrivenPhaseFrames
: currentPhase.$2;
// 현재 페이즈의 프레임 수를 초과하면 다음 페이즈로
if (_phaseFrameCount >= currentPhase.$2) {
if (_phaseFrameCount >= targetFrames) {
_phaseIndex = (_phaseIndex + 1) % _battlePhaseSequence.length;
_phaseFrameCount = 0;
_battleSubFrame = 0;
@@ -444,6 +463,8 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
_showBlockEffect = false;
_showParryEffect = false;
_showSkillEffect = false;
// 이벤트 기반 페이즈 종료
_isEventDrivenPhase = false;
} else {
_battleSubFrame++;
}