icon안동민 개발노트

레이캐스트와 스윕 테스트


 레이캐스트와 스윕 테스트는 언리얼 엔진에서 충돌 감지와 공간 쿼리를 수행하는 강력한 도구입니다.

 이 절에서는 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);
}

성능 최적화 전략

  1. 적절한 충돌 채널 사용
  2. 불필요한 복잡한 충돌 검사 피하기
  3. 공간 분할 구조 활용 (예 : 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. 목적에 맞는 쿼리 유형 선택
  • 단순 충돌 체크에는 레이캐스트, 복잡한 형상 검사에는 스윕 테스트 사용
  1. 충돌 채널 최적화
  • 필요한 객체 유형만 검사하도록 충돌 채널 설정
  1. 비동기 트레이스 활용
  • 성능 중요 상황에서 비동기 트레이스 사용
  1. 결과 캐싱
  • 자주 사용되는 쿼리 결과를 캐싱하여 반복 계산 방지
  1. LOD(Level of Detail) 적용
  • 거리에 따라 충돌 검사의 정밀도 조절

 레이캐스트와 스윕 테스트는 언리얼 엔진에서 다양한 게임플레이 기능을 구현하는 데 필수적인 도구입니다. 이들을 효과적으로 활용하면 시야 체크, 탄도 계산, 장애물 감지 등 다양한 기능을 구현할 수 있습니다.

 성능 최적화를 위해서는 적절한 충돌 채널 사용, 불필요한 복잡한 검사 회피, 공간 분할 구조 활용 등의 전략을 고려해야 합니다. 또한, 디버그 드로잉을 통해 레이캐스트와 스윕 테스트의 결과를 시각화하면 개발 과정에서 문제를 쉽게 식별하고 해결할 수 있습니다.

 복잡한 형상에 대한 정확한 충돌 검사가 필요한 경우, 복합 충돌 형상을 사용하거나 메시의 정확한 지오메트리를 활용할 수 있습니다. 그러나 이는 성능 비용이 높으므로 꼭 필요한 경우에만 사용해야 합니다.

 마지막으로, 레이캐스트와 스윕 테스트를 사용할 때는 항상 성능과 정확성 사이의 균형을 고려해야 합니다. 게임의 요구사항과 목표 플랫폼의 성능을 고려하여 적절한 접근 방식을 선택하는 것이 중요합니다.