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. 목적에 맞는 쿼리 유형 선택

  • 단순 충돌 체크에는 레이캐스트, 복잡한 형상 검사에는 스윕 테스트 사용

 2. 충돌 채널 최적화

  • 필요한 객체 유형만 검사하도록 충돌 채널 설정

 3. 비동기 트레이스 활용

  • 성능 중요 상황에서 비동기 트레이스 사용

 4. 결과 캐싱

  • 자주 사용되는 쿼리 결과를 캐싱하여 반복 계산 방지

 5. LOD(Level of Detail) 적용

  • 거리에 따라 충돌 검사의 정밀도 조절

 레이캐스트와 스윕 테스트는 언리얼 엔진에서 다양한 게임플레이 기능을 구현하는 데 필수적인 도구입니다.

 이들을 효과적으로 활용하면 시야 체크, 탄도 계산, 장애물 감지 등 다양한 기능을 구현할 수 있습니다.

 복잡한 형상에 대한 정확한 충돌 검사가 필요한 경우, 복합 충돌 형상을 사용하거나 메시의 정확한 지오메트리를 활용할 수 있습니다.

 그러나 이는 성능 비용이 높으므로 꼭 필요한 경우에만 사용해야 합니다.