언리얼엔진(UE)

[UE] 몬스터 AI 구현 #7 (Damage 중복 적용 오류 해결)

2h1824 2025. 3. 4. 22:36

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