icon안동민 개발노트

충돌 감지 및 응답 시스템


 언리얼 엔진의 충돌 시스템은 게임 내 객체 간의 상호작용을 관리하는 핵심 요소입니다.

 이 절에서는 C++에서 충돌 시스템을 구현하고 활용하는 방법을 살펴보겠습니다.

충돌 채널 설정

 충돌 채널은 객체 간의 충돌 여부를 결정합니다.

// Config/DefaultEngine.ini에 충돌 채널 추가
[/Script/Engine.CollisionProfile]
+DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,DefaultResponse=ECR_Block,bTraceType=False,bStaticObject=False,Name="Player")
+DefaultChannelResponses=(Channel=ECC_GameTraceChannel2,DefaultResponse=ECR_Overlap,bTraceType=False,bStaticObject=False,Name="Trigger")
 
// C++에서 충돌 채널 사용
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* MeshComponent;
 
AMyActor::AMyActor()
{
    MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
    MeshComponent->SetCollisionObjectType(ECC_GameTraceChannel1); // "Player" 채널로 설정
}

충돌 응답 설정

 충돌 응답은 객체가 다른 객체와 충돌했을 때의 행동을 정의합니다.

void AMyActor::SetupCollisionResponses()
{
    if (MeshComponent)
    {
        MeshComponent->SetCollisionResponseToChannel(ECC_GameTraceChannel2, ECR_Overlap); // "Trigger" 채널과 오버랩
        MeshComponent->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block); // Pawn과 블록
    }
}

커스텀 충돌 응답 구현

 특정 상황에 대한 커스텀 충돌 응답을 구현할 수 있습니다.

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
 
public:
    UFUNCTION()
    void OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
 
    UFUNCTION()
    void OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
};
 
void AMyActor::BeginPlay()
{
    Super::BeginPlay();
 
    MeshComponent->OnComponentBeginOverlap.AddDynamic(this, &AMyActor::OnComponentBeginOverlap);
    MeshComponent->OnComponentHit.AddDynamic(this, &AMyActor::OnComponentHit);
}
 
void AMyActor::OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    UE_LOG(LogTemp, Warning, TEXT("Overlap Begin with %s"), *OtherActor->GetName());
    // 오버랩 시 로직 구현
}
 
void AMyActor::OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
    UE_LOG(LogTemp, Warning, TEXT("Hit by %s"), *OtherActor->GetName());
    // 충돌 시 로직 구현
}

오버랩 이벤트 처리

 오버랩 이벤트는 물리적 충돌 없이 객체 간의 겹침을 감지합니다.

void AMyTrigger::BeginPlay()
{
    Super::BeginPlay();
 
    TriggerBox->OnComponentBeginOverlap.AddDynamic(this, &AMyTrigger::OnOverlapBegin);
    TriggerBox->OnComponentEndOverlap.AddDynamic(this, &AMyTrigger::OnOverlapEnd);
}
 
void AMyTrigger::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    if (Cast<APlayerCharacter>(OtherActor))
    {
        // 플레이어가 트리거에 진입했을 때의 로직
    }
}
 
void AMyTrigger::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    if (Cast<APlayerCharacter>(OtherActor))
    {
        // 플레이어가 트리거에서 나갔을 때의 로직
    }
}

레이캐스트 및 스윕 테스트

 레이캐스트와 스윕 테스트는 충돌 감지를 위한 강력한 도구입니다.

bool AMyActor::PerformRaycast()
{
    FHitResult HitResult;
    FVector Start = GetActorLocation();
    FVector End = Start + GetActorForwardVector() * 1000.0f;
 
    FCollisionQueryParams QueryParams;
    QueryParams.AddIgnoredActor(this);
 
    return GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Visibility, QueryParams);
}
 
bool AMyActor::PerformSweepTest()
{
    FHitResult HitResult;
    FVector Start = GetActorLocation();
    FVector End = Start + GetActorForwardVector() * 1000.0f;
 
    FCollisionShape CollisionShape;
    CollisionShape.SetSphere(50.0f);
 
    return GetWorld()->SweepSingleByChannel(HitResult, Start, End, FQuat::Identity, ECC_Visibility, CollisionShape);
}

복잡한 충돌 시나리오 처리

 여러 객체 간의 복잡한 충돌을 처리하기 위해 충돌 그룹과 프로파일을 활용할 수 있습니다.

void AMyActor::SetupComplexCollision()
{
    MeshComponent->SetCollisionProfileName(TEXT("CustomProfile"));
    MeshComponent->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Ignore);
    MeshComponent->SetCollisionResponseToChannel(ECC_GameTraceChannel2, ECR_Overlap);
    MeshComponent->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block);
}

충돌 이벤트의 성능 최적화

 충돌 이벤트는 성능에 큰 영향을 미칠 수 있으므로 최적화가 중요합니다.

void AMyActor::OptimizeCollisionEvents()
{
    // 필요한 경우에만 충돌 이벤트 활성화
    MeshComponent->SetGenerateOverlapEvents(false);
    MeshComponent->SetNotifyRigidBodyCollision(false);
 
    // 특정 조건에서만 충돌 이벤트 처리
    if (bShouldCheckCollisions)
    {
        MeshComponent->SetGenerateOverlapEvents(true);
    }
}

네트워크 게임에서의 충돌 처리

 네트워크 게임에서는 서버와 클라이언트 간의 충돌 동기화가 중요합니다.

void AMyNetworkedActor::HandleCollision(AActor* OtherActor)
{
    if (HasAuthority()) // 서버에서만 처리
    {
        // 충돌 로직 처리
        MulticastCollisionEffect(OtherActor);
    }
}
 
UFUNCTION(NetMulticast, Reliable)
void AMyNetworkedActor::MulticastCollisionEffect(AActor* OtherActor)
{
    // 모든 클라이언트에서 실행될 시각적/청각적 효과
}

게임플레이 메커니즘 구현 예시

 충돌 시스템을 활용한 간단한 파워업 메커니즘 구현:

UCLASS()
class APowerUp : public AActor
{
    GENERATED_BODY()
 
public:
    APowerUp();
 
    UFUNCTION()
    void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
 
    UPROPERTY(EditAnywhere, Category = "PowerUp")
    float PowerUpStrength;
 
private:
    UPROPERTY(VisibleAnywhere, Category = "Components")
    USphereComponent* CollisionSphere;
 
    UPROPERTY(VisibleAnywhere, Category = "Components")
    UStaticMeshComponent* MeshComponent;
};
 
APowerUp::APowerUp()
{
    CollisionSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionSphere"));
    RootComponent = CollisionSphere;
    CollisionSphere->SetCollisionProfileName("OverlapAllDynamic");
 
    MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
    MeshComponent->SetupAttachment(RootComponent);
    MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
 
    CollisionSphere->OnComponentBeginOverlap.AddDynamic(this, &APowerUp::OnOverlapBegin);
}
 
void APowerUp::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    APlayerCharacter* PlayerCharacter = Cast<APlayerCharacter>(OtherActor);
    if (PlayerCharacter)
    {
        PlayerCharacter->ApplyPowerUp(PowerUpStrength);
        Destroy(); // 파워업 사용 후 제거
    }
}

Best Practices

  1. 충돌 채널 효율적 사용
  • 필요한 만큼만 충돌 채널을 생성하고, 적절히 그룹화하여 사용합니다.
  1. 충돌 응답 최적화
  • 불필요한 충돌 검사를 피하고, 필요한 경우에만 상세한 충돌 응답을 구현합니다.
  1. 레이어 기반 충돌
  • 오브젝트 타입별로 레이어를 구분하여 충돌 처리의 효율성을 높입니다.
  1. 비동기 물리 활용
  • 복잡한 물리 시뮬레이션의 경우, 비동기 물리를 사용하여 성능을 향상시킵니다.
  1. 충돌 프로파일 활용
  • 자주 사용되는 충돌 설정을 프로파일로 만들어 재사용성을 높입니다.

 언리얼 엔진의 충돌 시스템은 매우 강력하고 유연하지만, 효과적으로 활용하기 위해서는 세심한 설정과 최적화가 필요합니다. 충돌 채널과 응답을 적절히 설정하고, 오버랩 이벤트와 레이캐스트를 효과적으로 활용함으로써 다양한 게임플레이 메커니즘을 구현할 수 있습니다.

 특히 복잡한 충돌 시나리오나 네트워크 게임에서의 충돌 처리는 추가적인 주의가 필요합니다. 서버와 클라이언트 간의 일관성을 유지하면서도 효율적인 충돌 처리를 구현해야 합니다.

 성능 최적화 측면에서는 불필요한 충돌 검사를 최소화하고, 필요한 경우에만 상세한 충돌 응답을 구현하는 것이 중요합니다. 또한, 프로파일링 도구를 활용하여 충돌 처리로 인한 성능 병목을 식별하고 개선해 나가야 합니다.

 마지막으로, 충돌 시스템은 게임플레이 메커니즘 구현의 핵심 요소입니다. 파워업, 트리거 영역, 투사체 충돌 등 다양한 게임플레이 요소를 충돌 시스템을 통해 효과적으로 구현할 수 있습니다. 이를 통해 플레이어에게 직관적이고 반응성 높은 게임 경험을 제공할 수 있습