더보기
기존에 캐릭터에게 AI가 공격했을 때 데미지를 주는 방식을 UWorld::LineTraceSingleByChannel을 사용해서 테스트하며 행동트리를 구현했었는데, 실질적으로 정확하게 공격 판정 로직을 구현하기 위해 UWorld::SweepSingleByChannel로 변경하였다.
✅ 근접 공격 로직
void ANormalMeleeEnemy::PerformMeleeAttack()
{
if (!GetMesh()) return;
if (!GetMesh() || !bIsAttacking) return;
FVector Start = GetMesh()->GetSocketLocation("FX_Trail_02_R");
FVector End = GetMesh()->GetSocketLocation("FX_Trail_01_R");
FVector MidPoint = (Start + End) * 0.5f;
FVector SwordDirection = (End - Start).GetSafeNormal();
FQuat SwordRotation = FQuat::FindBetweenVectors(FVector::UpVector, SwordDirection);
float CapsuleRadius = 15.0f;
float CapsuleHalfHeight = (Start - End).Size() * 0.6f;
FHitResult HitResult;
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
bool bHit = GetWorld()->SweepSingleByChannel
(
HitResult,
MidPoint,
MidPoint,
SwordRotation,
ECC_Pawn,
FCollisionShape::MakeCapsule(CapsuleRadius, CapsuleHalfHeight),
QueryParams
);
if (bHit)
{
AActor* HitActor = HitResult.GetActor();
if (HitActor && HitActor->ActorHasTag("Player"))
{
if (!AlreadyHitActors.Contains(HitActor))
{
AlreadyHitActors.Add(HitActor);
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, FString::Printf(TEXT("Melee Hit: %s"), *HitActor->GetName()));
UGameplayStatics::ApplyDamage(HitActor, Damage, GetController(), this, UDamageType::StaticClass());
}
}
}
DrawDebugCapsule(GetWorld(), MidPoint, CapsuleHalfHeight, CapsuleRadius, SwordRotation, FColor::Red, false, 0.5f, 0, 2.0f);
}
✅ 원거리(레이저 총) 공격 로직
void ANormalRangeEnemy::PerformRangeAttack()
{
FVector Start = GetMesh()->GetSocketLocation("Muzzle_Front");
FVector ForwardVector = GetActorForwardVector();
FVector End = Start + (ForwardVector * AttackRange);
float CapsuleRadius = 5.0f;
float CapsuleHalfHeight = AttackRange * 0.5f;
FVector CapsuleCenter = Start + (ForwardVector * CapsuleHalfHeight);
FQuat CapsuleRotation = FQuat::FindBetweenVectors(FVector::UpVector, ForwardVector);
FHitResult HitResult;
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
bool bHit = GetWorld()->SweepSingleByChannel
(
HitResult,
CapsuleCenter,
CapsuleCenter + (ForwardVector * AttackRange),
CapsuleRotation,
ECC_Pawn,
FCollisionShape::MakeCapsule(CapsuleRadius, CapsuleHalfHeight),
QueryParams
);
if (bHit)
{
AActor* HitActor = HitResult.GetActor();
if (HitActor && HitActor->ActorHasTag("Player"))
{
GEngine->AddOnScreenDebugMessage(2, 2.0f, FColor::Blue, FString::Printf(TEXT("Range Monster Attack Hit Damage %f"), Damage));
UGameplayStatics::ApplyDamage(HitActor, Damage, GetController(), this, UDamageType::StaticClass());
}
}
DrawDebugCapsule(GetWorld(), CapsuleCenter, CapsuleHalfHeight, CapsuleRadius, CapsuleRotation, FColor::Blue, false, 1.0f, 0, 2.0f);
}
🔎함수 원형
bool UWorld::SweepSingleByChannel(
struct FHitResult& OutHit,
const FVector& Start,
const FVector& End,
const FQuat& Rot,
ECollisionChannel TraceChannel,
const FCollisionShape& CollisionShape,
const FCollisionQueryParams& Params
) const
static FQuat FindBetweenVectors(const FVector& A, const FVector& B);
🚫 오류
처음에 당연히 Start에 무기의 손잡이 부분, End에 무기의 끝 부분을 넣으면 된다고 생각했는데,
아래 첫번째와 두번째 사진을 보면 결과적으로 무기의 시작 부분이 캡슐의 가운데에 와있는 것을 볼 수 있다.
SweepSingleByChannel에서 캡슐이 움직이는 방식은 "Start에서 End까지" 캡슐이 이동하며 충돌을 감지한다.
Start는 캡슐의 "중심"이 처음 위치하는 좌표를 의미하고, End는 캡슐의 "중심"이 마지막에 위치하는 좌표를 의미한다.
즉, Start와 End는 캡슐의 양쪽 끝이 아니라 중심이어야한다.
마지막 3번째 사진을 보면 캡슐이 수직으로 되어있는 것을 볼 수있다.
네번째 인자인 FQuat& Rot에 FQuat::Identity,(회전 없음)을 사용해서 월드 Z축을 기준으로 설정되었기 때문이다.
FQuatBetweenVectors 함수는 특정 방향을 기준으로 다른 방향으로 회전할 때 사용한다.
A가 현재 벡터를 의미하고 B가 목표 벡터를 의미하고 반환 값은 A를 B 방향으로 회전하는 FQuat 값을 반환한다.
// 근접 공격
FVector Start = GetMesh()->GetSocketLocation("FX_Trail_02_R");
FVector End = GetMesh()->GetSocketLocation("FX_Trail_01_R");
FVector MidPoint = (Start + End) * 0.5f;
FVector SwordDirection = (End - Start).GetSafeNormal();
FQuat SwordRotation = FQuat::FindBetweenVectors(FVector::UpVector, SwordDirection);
bool bHit = GetWorld()->SweepSingleByChannel
(
HitResult,
MidPoint,
MidPoint,
SwordRotation,
ECC_Pawn,
FCollisionShape::MakeCapsule(CapsuleRadius, CapsuleHalfHeight),
QueryParams
);
// 원거리 공격
FVector Start = GetMesh()->GetSocketLocation("Muzzle_Front");
FVector ForwardVector = GetActorForwardVector();
FVector End = Start + (ForwardVector * AttackRange);
FVector CapsuleCenter = Start + (ForwardVector * CapsuleHalfHeight);
FQuat CapsuleRotation = FQuat::FindBetweenVectors(FVector::UpVector, ForwardVector);
bool bHit = GetWorld()->SweepSingleByChannel
(
HitResult,
CapsuleCenter,
CapsuleCenter + (ForwardVector * AttackRange),
CapsuleRotation,
ECC_Pawn,
FCollisionShape::MakeCapsule(CapsuleRadius, CapsuleHalfHeight),
QueryParams
);
근접 공격의 경우 Start와 End 모두 MidPoint 변수를 전달했는데, 근접공격의 경우 칼의 가운데에 캡슐의 중심점이 계속 붙어있어야 하기 때문이다. 캡슐이 칼을 감싸고 움직인다고 생각하면 편하다.
반면 원거리 공격의 경우 캡슐이 한 지점에 고정되어서 머무르는 것이 아니라, 발사체처럼 이동하면서 충돌을 감지해야한다. 그래서 캡슐의 중심점이 총구에서 시작되고, 사격 범위만큼 전방백터 방향으로 이동시킨 곳을 End지점으로 설정한 것이다.
🔔 결과
'UE_FPS 슈터 게임 팀프로젝트 > 일반 AI' 카테고리의 다른 글
AIController와 BT 설계 (0) | 2025.03.06 |
---|---|
일반 AI_공격 구현 (0) | 2025.03.04 |
AIController, Behavior Tree 초기 Test (0) | 2025.02.19 |