1. 문제 상황
- 피격 몽타주를 몬스터에 할당 후, 공격을 아무리 해도 피격 반응이 제대로 나오지 않음
- 더불어 단발성으로 공격할 때는 State 업데이트가 정상 수행되나 연속적으로 공격하면 Frozen에서 굳어버리는 현상 발생
2. 해결 과정
피격 모션
float ABaseEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
Health = FMath::Clamp(Health - DamageAmount, 0.0f, MaxHealth);
if (AEnemyAIController* AIController=Cast<AEnemyAIController>(GetController()))
{
AIController->SetAIState(EAIState::Frozen);
}
//메시 유효
if (!GetMesh()) return 0.0f;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
//둘다 유효
if (AnimInstance&&HitMontage)
{
//피격 애니메이션 재생
AnimInstance->Montage_Play(HitMontage);
if (AEnemyAIController* AIController=Cast<AEnemyAIController>(GetController()))
{
AIController->SetAIState(EAIState::Attacking);
AIController->SetAttackTarget(DamageCauser);
}
}
if (Health <= 0.0f)
{
OnDeath();
}
return ActualDamage;
}
- 로그로 확인한 결과 몽타주의 경우 함수 호출은 정상 수행 중
- 공격 몽타주의 경우에는 동일하게 수행했을 때 정상 동작
- 두 몽타주를 비교하다 보니 Slot의 이름이 다른 것을 발견
- 검색해보니 몽타주의 경우 속하는 그룹을 위한 Slot이 마련되어 있어야 하는 것을 인지
ㅡ> 공격 몽타주의 경우 애니메이션 블루프린트 어딘가에 마련된 것으로 추정
- Output Pose 전에 피격 몽타주가 속한 Default Slot을 위한 노드 추가
ㅡ> 노드 설명을 읽어보니 애니메이션 몽타주를 코드에서 실행하기 위해 추가하는 노드로 보임
AI의 상태 업데이트 로직
void AEnemyAIController::HandleSenseDamage(AActor* DamageCauser)
{
SetAttackTarget(DamageCauser);
if (CurrentState==EAIState::Passive || CurrentState==EAIState::Investigating)
{
SetAIState(EAIState::Attacking);
}
}
- 몬스터 피격 시 호출되는 해당 함수가 로그로 확인한 결과 아예 호출되지 않음
- 알아본 결과 Damage Sense의 경우 TakeDamage를 재정의한 경우 Damage Sense 이벤트를 자동 호출하지 않음
- 따라서 재정의한 TakeDamage 함수에 Damage Sense를 위한 이벤트를 호출해야 함
float ABaseEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
Health = FMath::Clamp(Health - DamageAmount, 0.0f, MaxHealth);
if (AEnemyAIController* AIController=Cast<AEnemyAIController>(GetController()))
{
AIController->SetAIState(EAIState::Frozen);
}
//메시 유효
if (!GetMesh()) return 0.0f;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
//둘다 유효
if (AnimInstance&&HitMontage)
{
//피격 애니메이션 재생
AnimInstance->Montage_Play(HitMontage);
if (AEnemyAIController* AIController=Cast<AEnemyAIController>(GetController()))
{
AIController->SetAIState(EAIState::Attacking);
AIController->SetAttackTarget(DamageCauser);
}
}
//데미지 인식 이벤트 호출
if (DamageCauser)
{
UAISense_Damage::ReportDamageEvent(
GetWorld(),
this,
EventInstigator->GetPawn(),
ActualDamage,
GetActorLocation(),
DamageCauser->GetActorLocation()
);
}
if (Health <= 0.0f)
{
OnDeath();
}
return ActualDamage;
}
- UAISense_Damage::ReportDamageEvent로 Damage를 인식시킴 (Damage Sense 이벤트 호출 함수)
- 이제는 피격 모션이 재생되고 Damage를 인식하여 HandleSenseDamage도 호출
- 하지만 피격 모션이 나올 때, 잠깐 멈춰야 하지만 곧바로 공격하러 오는 에러 발생
ㅡ> State를 바로 Attacking으로 변경해서 Frozen상태가 길게 유지되지 못하고 피격 모션 호출 중에 업데이트 되는 것처럼 보임
+ 멀티스레드 환경에서 Race Condition이 발생할 가능성도 염두 - 따라서 Attacking으로 State를 변경하는 것을 Montage 재생이 끝난 뒤에 변경되도록 보장하면 해결 가능할 수도?
float ABaseEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
Health = FMath::Clamp(Health - DamageAmount, 0.0f, MaxHealth);
if (AEnemyAIController* AIController=Cast<AEnemyAIController>(GetController()))
{
AIController->SetAIState(EAIState::Frozen);
}
//메시 유효
if (!GetMesh()) return 0.0f;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
//둘다 유효
if (AnimInstance&&HitMontage)
{
//피격 애니메이션 재생
AnimInstance->Montage_Play(HitMontage);
//몽타주 끝났을 때 이벤트 바인딩
AnimInstance->OnMontageEnded.Clear();
AnimInstance->OnMontageEnded.AddDynamic(this,&ABaseEnemy::OnMontageEnded);
}
//데미지 인식 이벤트 호출
if (DamageCauser)
{
UAISense_Damage::ReportDamageEvent(
GetWorld(),
this,
EventInstigator->GetPawn(),
ActualDamage,
GetActorLocation(),
DamageCauser->GetActorLocation()
);
}
if (Health <= 0.0f)
{
OnDeath();
}
return ActualDamage;
}
//Montage가 실행이 끝나면 호출되는 함수
void ABaseEnemy::OnMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
//공격 몽타주는 끝나면 델리게이트에 Broadcast하여 종료 알림 (Task 작업고 관련)
if (Montage==AttackMontage)
{
OnAttackEnd.Broadcast();
}
//피격 모션이 끝나면 상태 Attacking으로 변경
if(Montage==HitMontage)
{
if (AEnemyAIController* EnemyController=Cast<AEnemyAIController>(GetController()))
{
EnemyController->SetAIState(EAIState::Attacking);
}
}
}
- 델리게이트를 이용해 몽타주가 실행이 끝날 때 호출될 함수를 바인딩
- 해당 함수에서 AI 상태 변경
3. 정리
완료
- 데미지 인식
- 상태 업데이트 로직
- 피격 애니메이션
예정
- 메인 메뉴 UI
참고자료
'언리얼엔진(UE)' 카테고리의 다른 글
[UE] 게임 서버 (Listen, Dedicated) (0) | 2025.03.12 |
---|---|
[UE] 몬스터 AI 구현 #10 (프로젝트 완) (0) | 2025.03.07 |
[UE] 몬스터 AI 구현 #8 (원거리 공격 투사체 발사 및 공격 이펙트) (0) | 2025.03.05 |
[UE] 몬스터 AI 구현 #7 (Damage 중복 적용 오류 해결) (0) | 2025.03.04 |
[UE] 몬스터 AI 구현 #6 (Perception에 따른 상태 변화) (0) | 2025.02.28 |