1. 문제
- 한 번의 공격 모션 이후 플레이어의 HP가 0으로 떨어지는 상황 발견
- 정확한 문제를 진단하기 위해 Log 추가
- Log를 통해 확인한 결과 Damage가 여러번 적용되는 상황
2. 해결 과정
문제와 관련된 코드 일부
void AMeleeEnemy::BeginPlay()
{
Super::BeginPlay();
//애니메이션 Notify 이벤트 연결
if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance())
{
AnimInstance->OnPlayMontageNotifyBegin.AddDynamic(this,&AMeleeEnemy::HandleAttackMontageNotify);
}
}
void AMeleeEnemy::HandleAttackMontageNotify(FName NotifyName, const FBranchingPointNotifyPayload& Payload)
{
//Notify Name 확인
if (NotifyName=="Slash")
{
//범위 설정
FVector Start=GetActorLocation();
FVector End=Start+GetActorForwardVector()*AttackRange;
float Radius =50.0f;
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
TArray<FHitResult> OutHits;
//충돌 여부 검사
bool bHit=GetWorld()->SweepMultiByObjectType
(
OutHits,
Start,
End,
FQuat::Identity,
FCollisionObjectQueryParams(ECollisionChannel::ECC_Pawn),
FCollisionShape::MakeSphere(Radius),
Params
);
// 공격 범위에 걸린 액터들 중에 Player의 클래스에 해당하는게 있다면 데미지 적용
if (bHit)
{
for (auto& Hit:OutHits)
{
if (AActor* HitActor=Hit.GetActor())
{
if (ACharacter* HitCharacter=Cast<ACharacter>(HitActor))
{
if (APlayerCharacter* Player=Cast<APlayerCharacter>(HitCharacter))
{
UE_LOG(LogTemp, Warning, TEXT("Slash"));
UGameplayStatics::ApplyDamage
(
Player,
Power,
GetController(),
this,
UDamageType::StaticClass()
);
}
}
}
}
}
}
}
- 현재 공격 몽타주에 BranchingPoint로 Notify를 추가하여 해당 Notify가 호출되는 시점에 이벤트 함수가 호출되는 구조
- 이벤트의 구체적 동작은 Notify 호출 시점에 Notify가 "Slash"라는 이름인지 확인
ㅡ> Sphere 형태의 콜리전 범위를 생성하여 해당 범위에 부딪힌 액터들에 Damage 적용
의심가는 원인
- 현재 Damage 자체가 여러번 적용되고 있으니 Notify가 호출되는 시점에 바인딩된 이벤트 함수가 중복 호출될 수 있음
ㅡ> 로그로 확인결과 해당 이벤트 함수 자체의 호출 횟수는 1번이므로 제외 - 현재 콜리전 범위에 들어온 액터들을 판별하는데 사용한 함수가 SweepMultiByObjectType이니까 한 액터가 여러번 감지되었을 수 있음
ㅡ> Player 캐릭터인지 판별하여 ApplyDamage가 호출되는 부분에 로그를 추가하여 확인
ㅡ> 해당 부분 여러번 호출되는 것 확인
원인
- 충돌 여부를 검사하는데 사용한 함수는 중복 관련 처리를 해주지 않는 함수
- 이로 인해 동일한 액터가 여러번 감지되고 해당 액터에 ApplyDamage가 감지된 횟수만큼 중복 호출
- 동일한 액터인지 판별하는 부분을 추가하면 해결 가능
해결 방법
수정된 코드
void AMeleeEnemy::HandleAttackMontageNotify(FName NotifyName, const FBranchingPointNotifyPayload& Payload)
{
//Notify Name 확인
if (NotifyName=="Slash")
{
//범위 설정
FVector Start=GetActorLocation();
FVector End=Start+GetActorForwardVector()*AttackRange;
float Radius =50.0f;
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
TArray<FHitResult> OutHits;
//충돌 여부 검사
bool bHit=GetWorld()->SweepMultiByObjectType
(
OutHits,
Start,
End,
FQuat::Identity,
FCollisionObjectQueryParams(ECollisionChannel::ECC_Pawn),
FCollisionShape::MakeSphere(Radius),
Params
);
TSet<AActor*> DamagedActors; //중복 방지를 위한 Set
// 공격 범위에 걸린 액터들 중에 Player의 클래스에 해당하는게 있다면 데미지 적용
if (bHit)
{
for (auto& Hit:OutHits)
{
if (AActor* HitActor=Hit.GetActor())
{
if (!DamagedActors.Contains(HitActor)) //중복 검사, 한 액터당 한번의 Damage 적용
{
if (ACharacter* HitCharacter=Cast<ACharacter>(HitActor))
{
if (APlayerCharacter* Player=Cast<APlayerCharacter>(HitCharacter))
{
UE_LOG(LogTemp, Warning, TEXT("Slash"));
UGameplayStatics::ApplyDamage
(
Player,
Power,
GetController(),
this,
UDamageType::StaticClass()
);
DamagedActors.Add(HitActor); //중복 방지 위해 Damage를 한번 적용한 대상 Set에 추가
}
}
}
}
}
}
}
}
- TSet을 이용하여 ApplyDamage 함수가 액터당 한번씩만 호출되도록 코드 수정
- ApplyDamage를 적용하기 전에 현재 감지된 액터가 TSet에 포함된 액터인지 확인(중복 처리)
- ApplyDamage를 호출한 후엔 해당 액터를 TSet에 추가
3. 정리
- 아마 SweepSingleByObjectType을 썻다면 발생하지 않았을 수 있음
- 범용성을 생각해 Single이 아닌 Multi를 사용하는 과정에서 동일한 액터에 대한 중복 처리를 생각하지 않은 것이 원인
- 경우에 따라서는 한 액터에 여러번의 Damage 처리를 해야하는 경우도 있을 수 있으므로 함수에서 중복 처리를 해주지 않는 것은 당연한 것으로 보임
ex) 몬스터헌터 시리즈의 공격의 경우 위 상황에 해당하는 것으로 보임
(무기가 몬스터를 지나가는 동안 여러번의 데미지 처리) - 이후 다른 콜리전 관련 함수들을 사용하는 과정에서도 중복 처리에 관해 유의할 것
참고자료
https://www.youtube.com/watch?v=htFAeywLuNQ&list=PLNwKK6OwH7eW1n49TW6-FmiZhqRn97cRy&index=7
'언리얼엔진(UE)' 카테고리의 다른 글
[UE] 몬스터 AI 구현 #9 (피격 반응 애니메이션 및 상태 로직) (0) | 2025.03.06 |
---|---|
[UE] 몬스터 AI 구현 #8 (원거리 공격 투사체 발사 및 공격 이펙트) (0) | 2025.03.05 |
[UE] 몬스터 AI 구현 #6 (Perception에 따른 상태 변화) (0) | 2025.02.28 |
[UE] 몬스터 AI 구현 #5 (AI Percetion) (0) | 2025.02.27 |
[UE] 몬스터 AI 구현 #4 (몬스터 기본 클래스 구성) (0) | 2025.02.26 |