레이캐스트와 스윕 테스트
레이캐스트와 스윕 테스트는 언리얼 엔진에서 충돌 감지와 공간 쿼리를 수행하는 강력한 도구입니다.
이 절에서는 C++ 관점에서 이들의 구현 방법과 활용 사례를 살펴보겠습니다.
레이캐스트 구현
단일 레이캐스트
단일 레이캐스트는 한 지점에서 다른 지점까지의 직선 경로를 따라 충돌을 검사합니다.
bool AMyActor::PerformRaycast(FVector Start, FVector End, FHitResult& HitResult)
{
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
return GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Visibility, QueryParams);
}
void AMyActor::UseRaycast()
{
FVector Start = GetActorLocation();
FVector End = Start + GetActorForwardVector() * 1000.0f;
FHitResult HitResult;
if (PerformRaycast(Start, End, HitResult))
{
UE_LOG(LogTemp, Warning, TEXT("Hit Actor: %s"), *HitResult.GetActor()->GetName());
}
}
멀티 레이캐스트
멀티 레이캐스트는 경로상의 모든 충돌을 감지합니다.
TArray<FHitResult> AMyActor::PerformMultiRaycast(FVector Start, FVector End)
{
TArray<FHitResult> HitResults;
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
GetWorld()->LineTraceMultiByChannel(HitResults, Start, End, ECC_Visibility, QueryParams);
return HitResults;
}
void AMyActor::UseMultiRaycast()
{
FVector Start = GetActorLocation();
FVector End = Start + GetActorForwardVector() * 1000.0f;
TArray<FHitResult> HitResults = PerformMultiRaycast(Start, End);
for (const FHitResult& Hit : HitResults)
{
UE_LOG(LogTemp, Warning, TEXT("Hit Actor: %s"), *Hit.GetActor()->GetName());
}
}
스윕 테스트 구현
스윕 테스트는 특정 형상을 이동시키면서 충돌을 검사합니다.
구 형태의 스윕 테스트
bool AMyActor::PerformSphereSweep(FVector Start, FVector End, float Radius, FHitResult& HitResult)
{
FCollisionShape SphereShape = FCollisionShape::MakeSphere(Radius);
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
return GetWorld()->SweepSingleByChannel(HitResult, Start, End, FQuat::Identity, ECC_Visibility, SphereShape, QueryParams);
}
void AMyActor::UseSphereSweep()
{
FVector Start = GetActorLocation();
FVector End = Start + GetActorForwardVector() * 1000.0f;
FHitResult HitResult;
if (PerformSphereSweep(Start, End, 50.0f, HitResult))
{
UE_LOG(LogTemp, Warning, TEXT("Sphere Sweep Hit Actor: %s"), *HitResult.GetActor()->GetName());
}
}
박스 형태의 스윕 테스트
bool AMyActor::PerformBoxSweep(FVector Start, FVector End, FVector HalfSize, FHitResult& HitResult)
{
FCollisionShape BoxShape = FCollisionShape::MakeBox(HalfSize);
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
return GetWorld()->SweepSingleByChannel(HitResult, Start, End, FQuat::Identity, ECC_Visibility, BoxShape, QueryParams);
}
void AMyActor::UseBoxSweep()
{
FVector Start = GetActorLocation();
FVector End = Start + GetActorForwardVector() * 1000.0f;
FVector HalfSize(50.0f, 50.0f, 50.0f);
FHitResult HitResult;
if (PerformBoxSweep(Start, End, HalfSize, HitResult))
{
UE_LOG(LogTemp, Warning, TEXT("Box Sweep Hit Actor: %s"), *HitResult.GetActor()->GetName());
}
}
게임플레이 기능 구현
시야 체크
bool AMyAIController::HasLineOfSightTo(AActor* TargetActor)
{
if (!TargetActor)
return false;
FVector EyeLocation;
FRotator EyeRotation;
GetActorEyesViewPoint(EyeLocation, EyeRotation);
FVector TargetLocation = TargetActor->GetActorLocation();
FHitResult HitResult;
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(GetPawn());
bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, EyeLocation, TargetLocation, ECC_Visibility, QueryParams);
return !bHit || HitResult.GetActor() == TargetActor;
}
탄도 계산
FVector AMyWeapon::CalculateProjectileTrajectory(FVector StartLocation, FVector TargetLocation, float ProjectileSpeed)
{
FVector ToTarget = TargetLocation - StartLocation;
float DistanceToTarget = ToTarget.Size();
FVector Direction = ToTarget.GetSafeNormal();
// 간단한 탄도 계산 (중력 무시)
FVector Velocity = Direction * ProjectileSpeed;
return Velocity;
}
void AMyWeapon::FireProjectile()
{
FVector StartLocation = GetMuzzleLocation();
FVector TargetLocation = GetTargetLocation();
float ProjectileSpeed = 2000.0f;
FVector ProjectileVelocity = CalculateProjectileTrajectory(StartLocation, TargetLocation, ProjectileSpeed);
// 발사 로직...
}
장애물 감지
TArray<AActor*> AMyCharacter::DetectObstacles(float DetectionRadius)
{
TArray<AActor*> DetectedObstacles;
TArray<FHitResult> HitResults;
FVector Start = GetActorLocation();
FVector End = Start; // 같은 위치로 설정하여 전방향 검사
FCollisionShape SphereShape = FCollisionShape::MakeSphere(DetectionRadius);
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
GetWorld()->SweepMultiByChannel(HitResults, Start, End, FQuat::Identity, ECC_Visibility, SphereShape, QueryParams);
for (const FHitResult& Hit : HitResults)
{
if (Hit.GetActor())
{
DetectedObstacles.AddUnique(Hit.GetActor());
}
}
return DetectedObstacles;
}
복잡한 형상에 대한 정확한 충돌 검사
복잡한 형상에 대해 더 정확한 충돌 검사를 수행하려면 복합 충돌 형상을 사용하거나 메시의 정확한 지오메트리를 사용할 수 있습니다.
bool AMyActor::PerformComplexCollisionTest(UPrimitiveComponent* Component, FVector TestLocation)
{
if (!Component)
return false;
FCollisionQueryParams QueryParams;
QueryParams.bTraceComplex = true; // 복잡한 충돌 사용
QueryParams.AddIgnoredActor(this);
return Component->IsOverlappingLocation(TestLocation, QueryParams);
}
성능 최적화 전략
- 적절한 충돌 채널 사용
- 불필요한 복잡한 충돌 검사 피하기
- 공간 분할 구조 활용 (예 : Octree)
void AMyActor::OptimizeCollisionQueries()
{
// 충돌 채널 최적화
FCollisionObjectQueryParams ObjectQueryParams;
ObjectQueryParams.AddObjectTypesToQuery(ECC_WorldStatic);
ObjectQueryParams.AddObjectTypesToQuery(ECC_WorldDynamic);
// 간단한 충돌 검사 사용
FCollisionQueryParams QueryParams;
QueryParams.bTraceComplex = false;
// 공간 분할 구조 활용 (예시)
if (UWorld* World = GetWorld())
{
if (ISpatialPartitionInterface* SpatialPartition = World->GetSpatialPartitionInterface())
{
// 공간 분할 구조를 사용한 쿼리
}
}
}
디버그 드로잉을 통한 시각화
void AMyActor::VisualizeRaycast(FVector Start, FVector End, bool bHit, const FHitResult& HitResult)
{
FColor LineColor = bHit ? FColor::Red : FColor::Green;
float Duration = 1.0f;
DrawDebugLine(GetWorld(), Start, bHit ? HitResult.ImpactPoint : End, LineColor, false, Duration);
if (bHit)
{
DrawDebugPoint(GetWorld(), HitResult.ImpactPoint, 10.0f, FColor::Red, false, Duration);
}
}
void AMyActor::PerformAndVisualizeRaycast()
{
FVector Start = GetActorLocation();
FVector End = Start + GetActorForwardVector() * 1000.0f;
FHitResult HitResult;
bool bHit = PerformRaycast(Start, End, HitResult);
VisualizeRaycast(Start, End, bHit, HitResult);
}
Best Practices
1. 목적에 맞는 쿼리 유형 선택
- 단순 충돌 체크에는 레이캐스트, 복잡한 형상 검사에는 스윕 테스트 사용
2. 충돌 채널 최적화
- 필요한 객체 유형만 검사하도록 충돌 채널 설정
3. 비동기 트레이스 활용
- 성능 중요 상황에서 비동기 트레이스 사용
4. 결과 캐싱
- 자주 사용되는 쿼리 결과를 캐싱하여 반복 계산 방지
5. LOD(Level of Detail) 적용
- 거리에 따라 충돌 검사의 정밀도 조절
레이캐스트와 스윕 테스트는 언리얼 엔진에서 다양한 게임플레이 기능을 구현하는 데 필수적인 도구입니다.
이들을 효과적으로 활용하면 시야 체크, 탄도 계산, 장애물 감지 등 다양한 기능을 구현할 수 있습니다.
복잡한 형상에 대한 정확한 충돌 검사가 필요한 경우, 복합 충돌 형상을 사용하거나 메시의 정확한 지오메트리를 활용할 수 있습니다.
그러나 이는 성능 비용이 높으므로 꼭 필요한 경우에만 사용해야 합니다.