icon안동민 개발노트

물리 엔진 개요와 설정


 언리얼 엔진의 물리 시스템은 NVIDIA의 PhysX 엔진을 기반으로 하며, 게임 내 객체들의 현실적인 움직임과 상호작용을 가능하게 합니다.

 이 절에서는 물리 시스템의 기본 개념과 C++에서의 설정 및 제어 방법을 살펴보겠습니다.

물리 시뮬레이션 기본 개념

  1. 강체(Rigid Body) : 변형되지 않는 고정된 형태의 물체
  2. 충돌(Collision) : 물체 간의 접촉과 반응
  3. 중력(Gravity) : 물체에 작용하는 아래 방향의 힘
  4. 마찰(Friction) : 물체 간 접촉 시 발생하는 저항력

PhysX 엔진과의 통합

 언리얼 엔진은 PhysX를 내부적으로 사용하며 개발자가 직접 PhysX API를 호출할 필요 없이 언리얼의 인터페이스를 통해 물리 기능을 사용할 수 있습니다.

주요 물리 설정 조정

 중력 설정

UWorld* World = GetWorld();
if (World)
{
    FVector CustomGravity(0, 0, -980); // cm/s^2
    World->GetWorldSettings()->SetGlobalGravityZ(CustomGravity.Z);
}

 시뮬레이션 빈도 설정

UWorld* World = GetWorld();
if (World)
{
    World->GetWorldSettings()->SetFixedFrameRate(60.0f); // 60 FPS
}

물리 객체 생성

 정적 메시

 움직이지 않지만 다른 물체와 충돌하는 객체입니다.

UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* StaticMeshComponent;
 
AMyActor::AMyActor()
{
    StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComponent"));
    RootComponent = StaticMeshComponent;
    StaticMeshComponent->SetSimulatePhysics(false);
    StaticMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}

 동적 메시

 물리 시뮬레이션의 영향을 받아 움직이는 객체입니다.

UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* DynamicMeshComponent;
 
AMyDynamicActor::AMyDynamicActor()
{
    DynamicMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DynamicMeshComponent"));
    RootComponent = DynamicMeshComponent;
    DynamicMeshComponent->SetSimulatePhysics(true);
    DynamicMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}

 키네마틱 객체

 물리 시뮬레이션에 영향을 주지만 받지는 않는 객체입니다.

UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* KinematicMeshComponent;
 
AMyKinematicActor::AMyKinematicActor()
{
    KinematicMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("KinematicMeshComponent"));
    RootComponent = KinematicMeshComponent;
    KinematicMeshComponent->SetSimulatePhysics(true);
    KinematicMeshComponent->SetMobility(EComponentMobility::Movable);
    KinematicMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}
 
void AMyKinematicActor::MoveKinematicActor()
{
    FVector NewLocation = GetActorLocation() + FVector(100, 0, 0);
    KinematicMeshComponent->SetWorldLocation(NewLocation, true); // 'true'는 물리적인 이동을 의미
}

물리 재질 설정

 물리 재질은 객체의 마찰, 반발력 등의 속성을 정의합니다.

UPROPERTY(EditAnywhere, Category = "Physics")
UPhysicalMaterial* CustomPhysicalMaterial;
 
void AMyActor::SetupPhysicalMaterial()
{
    if (CustomPhysicalMaterial && StaticMeshComponent)
    {
        StaticMeshComponent->SetPhysMaterialOverride(CustomPhysicalMaterial);
    }
}

물리 제약 조건(Constraints) 구현

 물리 제약 조건은 두 객체 간의 물리적 연결을 정의합니다.

UPROPERTY(VisibleAnywhere)
UPhysicsConstraintComponent* PhysicsConstraintComponent;
 
AMyConstrainedActor::AMyConstrainedActor()
{
    PhysicsConstraintComponent = CreateDefaultSubobject<UPhysicsConstraintComponent>(TEXT("PhysicsConstraintComponent"));
    RootComponent = PhysicsConstraintComponent;
 
    // 제약 조건 설정
    PhysicsConstraintComponent->SetLinearXLimit(ELinearConstraintMotion::LCM_Limited, 100.0f);
    PhysicsConstraintComponent->SetLinearYLimit(ELinearConstraintMotion::LCM_Locked, 0.0f);
    PhysicsConstraintComponent->SetLinearZLimit(ELinearConstraintMotion::LCM_Free, 0.0f);
}

물리 시뮬레이션이 성능에 미치는 영향

  1. 시뮬레이션 객체 수 제한 : 필요한 객체만 물리 시뮬레이션 적용
  2. LOD(Level of Detail) 시스템 활용 : 거리에 따라 물리 복잡도 조절
  3. 물리 서브스테핑 사용 : 높은 프레임률에서도 안정적인 시뮬레이션 제공
UWorld* World = GetWorld();
if (World)
{
    World->GetWorldSettings()->bSubstepping = true;
    World->GetWorldSettings()->MaxSubstepDeltaTime = 0.016667f; // 60 FPS
    World->GetWorldSettings()->MaxSubsteps = 6;
}

물리 디버깅 도구 활용

 언리얼 엔진은 물리 시뮬레이션을 시각화하고 디버그하기 위한 도구를 제공합니다.

// 물리 디버깅 활성화
void AMyGameMode::EnablePhysicsDebugger()
{
    if (UWorld* World = GetWorld())
    {
        if (APlayerController* PC = World->GetFirstPlayerController())
        {
            PC->ConsoleCommand(TEXT("p.VisualizePhysicsCompartments 1"));
            PC->ConsoleCommand(TEXT("p.VisualizePhysicsDepth 1"));
        }
    }
}

Best Practices

 1. 물리 설정 최적화

  • 게임의 요구사항에 맞게 물리 설정을 조정하여 성능과 현실성의 균형을 맞춥니다.
void AMyGameMode::OptimizePhysicsSettings()
{
    UWorld* World = GetWorld();
    if (World)
    {
        World->GetWorldSettings()->bEnableWorldBoundsChecks = false; // 대규모 월드에서 성능 향상
        World->GetWorldSettings()->bEnableAdaptiveForce = true; // 안정성 향상
    }
}

 2. 물리 프로파일링

  • 주기적으로 물리 성능을 프로파일링하여 병목 지점을 식별하고 최적화합니다.
void AMyGameMode::StartPhysicsProfiler()
{
    FEngineShowFlags::EnableShowFlag(TEXT("PhysicalMaterials"), true);
    FEngineShowFlags::EnableShowFlag(TEXT("PhysicsGrid"), true);
}

 3. 비동기 물리 사용

  • 복잡한 시뮬레이션의 경우 비동기 물리를 활용하여 성능을 향상시킵니다.
void AMyGameMode::EnableAsyncPhysics()
{
    UWorld* World = GetWorld();
    if (World)
    {
        World->GetWorldSettings()->bAsyncPhysicsTickEnabled = true;
    }
}

 4. 물리 객체 풀링

  • 자주 생성되고 제거되는 물리 객체의 경우 객체 풀링을 사용하여 성능을 개선합니다.
class FPhysicsObjectPool
{
public:
    APhysicsActor* GetPhysicsActor()
    {
        if (Pool.Num() > 0)
        {
            return Pool.Pop();
        }
        return GetWorld()->SpawnActor<APhysicsActor>();
    }
 
    void ReturnPhysicsActor(APhysicsActor* Actor)
    {
        Actor->SetActorHiddenInGame(true);
        Actor->SetActorEnableCollision(false);
        Pool.Push(Actor);
    }
 
private:
    TArray<APhysicsActor*> Pool;
};

 5. 복잡한 물리 연산 최적화

  • 복잡한 물리 연산의 경우, 계산을 단순화하거나 근사치를 사용하여 성능을 개선합니다.
float AMyActor::CalculateApproximatePhysicsForce(const FVector& Velocity, float Mass)
{
    // 복잡한 물리 공식 대신 근사치 계산
    return Velocity.Size() * Mass * 0.5f;
}

 언리얼 엔진의 물리 시스템은 강력하고 유연하지만, 효과적으로 활용하기 위해서는 주의 깊은 설정과 최적화가 필요합니다.

 물리 객체의 생성과 설정, 제약 조건의 적용, 그리고 전반적인 물리 환경의 조정을 통해 현실적이고 성능이 좋은 게임 환경을 구축할 수 있습니다.