icon
5장 : 충돌, 물리, 인터랙션

물리 시뮬레이션과 Force 적용


이전 절에서 우리는 트리거 박스와 오버랩 이벤트를 통해 비물리적인 상호작용을 감지하는 방법을 배웠습니다. 이제 게임 월드 내에서 오브젝트들이 중력의 영향을 받거나, 충돌 시 튕겨 나가고, 외부 힘에 의해 움직이는 등 현실적인 물리적 상호작용을 구현하는 방법을 다룰 시간입니다. 언리얼 엔진은 강력한 물리 시뮬레이션(Physics Simulation) 시스템을 제공하며, 이를 통해 오브젝트에 힘(Force) 을 적용하여 동적인 게임플레이를 만들 수 있습니다.


물리 시뮬레이션이란?

물리 시뮬레이션은 게임 엔진이 현실 세계의 물리 법칙(중력, 질량, 마찰, 반발력 등)을 모방하여 3D 오브젝트의 움직임을 계산하는 과정입니다. 언리얼 엔진의 물리 엔진(Nvidia PhysX 또는 Chaos)은 액터에 부착된 UPrimitiveComponent (예: UStaticMeshComponent, USkeletalMeshComponent, UBoxComponent, UCapsuleComponent 등)의 충돌 메시(Collision Mesh)물리 속성을 기반으로 작동합니다.

물리 시뮬레이션 활성화의 전제 조건

어떤 액터나 컴포넌트가 물리 시뮬레이션의 영향을 받으려면 다음 두 가지 조건이 충족되어야 합니다.

충돌 컴포넌트를 가질 것: 액터에 UPrimitiveComponent (가장 흔하게는 UStaticMeshComponentUSkeletalMeshComponent)가 부착되어 있어야 합니다. 이 컴포넌트가 물리 시뮬레이션의 대상이 됩니다.

SetSimulatePhysics(true) 호출: 해당 컴포넌트의 물리 시뮬레이션을 활성화해야 합니다. 또한 해당 컴포넌트의 Collision EnabledQueryAndPhysics 또는 PhysicsOnly로 설정되어 있어야 합니다.

AMyPhysicsActor.h (물리 시뮬레이션될 액터 예시)
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyPhysicsActor.generated.h"

class UStaticMeshComponent; // 스태틱 메시 컴포넌트 선언

UCLASS()
class MYPROJECT_API AMyPhysicsActor : public AActor
{
    GENERATED_BODY()
    
public:    
    AMyPhysicsActor();

protected:
    virtual void BeginPlay() override;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
    UStaticMeshComponent* MyMesh; // 물리 시뮬레이션될 메시 컴포넌트
};
AMyPhysicsActor.cpp (생성자)
#include "MyPhysicsActor.h"
#include "Components/StaticMeshComponent.h" // UStaticMeshComponent 헤더 포함

AMyPhysicsActor::AMyPhysicsActor()
{
    PrimaryActorTick.bCanEverTick = true; // 틱이 필요할 경우 활성화

    // 1. 스태틱 메시 컴포넌트 생성
    MyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyMesh"));
    RootComponent = MyMesh; // 루트 컴포넌트로 설정

    // (선택 사항) 스태틱 메시 에셋 할당 (블루프린트에서 할당하는 것을 권장)
    // static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeMeshAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
    // if (CubeMeshAsset.Succeeded())
    // {
    //     MyMesh->SetStaticMesh(CubeMeshAsset.Object);
    // }

    // 2. 물리 시뮬레이션 활성화
    MyMesh->SetSimulatePhysics(true); 

    // 3. 충돌 설정: 물리 시뮬레이션과 쿼리 모두 가능하도록 설정
    // Project Settings에서 적절한 Collision Preset을 사용하는 것을 권장합니다.
    MyMesh->SetCollisionProfileName(TEXT("PhysicsActor")); // 또는 "BlockAllDynamic"
    // 또는 수동 설정:
    // MyMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); 
    // MyMesh->SetCollisionObjectType(ECollisionChannel::ECC_PhysicsBody); // 물리 오브젝트 채널
}

void AMyPhysicsActor::BeginPlay()
{
    Super::BeginPlay();
    // 물리 시뮬레이션은 자동으로 시작됩니다.
}

에디터에서 확인

이 C++ 클래스를 기반으로 블루프린트(BP_MyPhysicsActor)를 만든 후 레벨에 배치합니다. BP_MyPhysicsActorMyMesh 컴포넌트를 선택하고, 디테일 패널의 Physics 섹션에서 Simulate Physics가 체크되어 있는지 확인하세요. 플레이를 시작하면 이 액터는 중력에 의해 떨어지거나 다른 물리 오브젝트와 충돌할 것입니다.


Force (힘) 적용하기

물리 시뮬레이션이 활성화된 오브젝트에 외부에서 힘을 가하여 움직임을 제어할 수 있습니다. 언리얼 엔진은 다양한 종류의 힘을 적용하는 함수를 제공합니다. 이 함수들은 주로 UPrimitiveComponent에 정의되어 있습니다.

주요 힘 적용 함수

  • AddForce(FVector Force, FName BoneName = NAME_None, bool bAccelChange = false)

  • 오브젝트의 질량에 비례하여 지속적으로 가해지는 힘입니다. 마치 로켓 엔진이 지속적으로 추진력을 가하는 것과 유사합니다.

  • bAccelChange = true로 설정하면 질량과 상관없이 즉각적인 가속도 변화를 줍니다.

  • BoneName을 지정하면 스켈레탈 메시의 특정 본에만 힘을 가할 수 있습니다.

  • AddImpulse(FVector Impulse, FName BoneName = NAME_None, bool bVelChange = false)

  • 오브젝트에 순간적으로 가해지는 강한 힘입니다. 폭발이나 총알에 맞는 것처럼 한 번의 타격으로 속도를 변화시킬 때 사용합니다.

  • bVelChange = true로 설정하면 질량과 상관없이 즉각적인 속도 변화를 줍니다.

  • AddTorqueInRadians(FVector Torque, FName BoneName = NAME_None, bool bAccelChange = false)

  • 오브젝트에 회전력(Torque) 을 가합니다. 오브젝트를 회전시키거나 굴릴 때 사용합니다.

  • AddRadialForce(FVector Origin, float Radius, float Strength, ERadialImpulseFalloff Falloff, bool bAccelChange = false)

  • 특정 지점(Origin)에서 시작하여 Radius 범위 내의 모든 물리 오브젝트에 방사형 힘을 가합니다. 폭발 효과를 구현할 때 매우 유용합니다.

  • Falloff: 힘의 감소 방식(Constant 또는 Linear).

  • 이 함수는 월드의 모든 물리 오브젝트를 검사하므로 주의해서 사용해야 합니다.

C++에서 Force 적용 예시

이제 AMyPhysicsActor에 마우스 클릭 시 상방향으로 임펄스를 가하는 기능을 추가해 봅시다.

AMyPhysicsActor.h (AddForce/Impulse 테스트를 위한 함수 추가)
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyPhysicsActor.generated.h"

class UStaticMeshComponent; 

UCLASS()
class MYPROJECT_API AMyPhysicsActor : public AActor
{
    GENERATED_BODY()
    
public:    
    AMyPhysicsActor();

protected:
    virtual void BeginPlay() override;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
    UStaticMeshComponent* MyMesh; 

public:
    // 블루프린트에서도 호출할 수 있도록 BlueprintCallable 추가
    UFUNCTION(BlueprintCallable, Category = "Physics")
    void ApplyUpwardImpulse();

    UFUNCTION(BlueprintCallable, Category = "Physics")
    void ApplyExplosionForce(FVector ExplosionOrigin, float ExplosionRadius, float ExplosionStrength);
};
AMyPhysicsActor.cpp
#include "MyPhysicsActor.h"
#include "Components/StaticMeshComponent.h" 
#include "Kismet/GameplayStatics.h" // ApplyRadialDamage 또는 AddRadialForce 등을 위해

AMyPhysicsActor::AMyPhysicsActor()
{
    // ... 기존 생성자 코드 ...
}

void AMyPhysicsActor::BeginPlay()
{
    Super::BeginPlay();
}

void AMyPhysicsActor::ApplyUpwardImpulse()
{
    if (MyMesh && MyMesh->IsSimulatingPhysics())
    {
        // Z축 양의 방향으로 50000 단위의 임펄스 적용
        // bVelChange = true;로 설정하면 질량과 관계없이 일정한 속도 변화를 줍니다.
        MyMesh->AddImpulse(FVector(0.0f, 0.0f, 50000.0f), NAME_None, false); 
        UE_LOG(LogTemp, Warning, TEXT("Applied upward impulse to %s"), *GetName());
    }
}

void AMyPhysicsActor::ApplyExplosionForce(FVector ExplosionOrigin, float ExplosionRadius, float ExplosionStrength)
{
    if (MyMesh && MyMesh->IsSimulatingPhysics())
    {
        // MyMesh에 방사형 힘 적용
        // AddRadialForce는 UPrimitiveComponent의 함수입니다.
        // 폭발 지점에서 MyMesh까지의 거리에 따라 힘이 약해집니다 (Linear Falloff).
        MyMesh->AddRadialForce(ExplosionOrigin, ExplosionRadius, ExplosionStrength, ERadialImpulseFalloff::RIF_Linear);
        UE_LOG(LogTemp, Warning, TEXT("Applied radial force to %s from %s"), *GetName(), *ExplosionOrigin.ToString());
    }
}

테스트 방법 (블루프린트 연동)

BP_MyPhysicsActor 블루프린트를 레벨에 여러 개 배치합니다.

새로운 액터 블루프린트(예: BP_PhysicsLauncher)를 만들거나, AMyCharacter 블루프린트의 이벤트 그래프를 엽니다.

마우스 왼쪽 클릭 이벤트를 받습니다.

Line Trace By Channel 또는 Sphere Trace By Channel 노드를 사용하여 마우스 위치에서 월드로 트레이스를 쏴서 충돌한 액터를 감지합니다.

트레이스 결과로 감지된 Hit ActorBP_MyPhysicsActor 타입인지 Cast To MyPhysicsActor 노드를 사용하여 확인합니다.

성공적으로 캐스팅되면 해당 BP_MyPhysicsActor 레퍼런스에서 Apply Upward Impulse 또는 Apply Explosion Force 함수를 호출합니다. Apply Explosion Force의 경우, 폭발 지점은 트레이스의 Hit Location으로, 반경과 힘은 임의의 값으로 설정할 수 있습니다.


물리 재질 (Physical Material)

물리 재질(Physical Material) 은 오브젝트의 표면이 가진 물리적 특성(마찰, 반발력 등)을 정의하는 에셋입니다. 이를 메시 컴포넌트에 적용하면 해당 오브젝트가 다른 물리 오브젝트와 상호작용할 때 더욱 사실적인 물리 반응을 얻을 수 있습니다.

물리 재질 설정

콘텐츠 브라우저에서 마우스 오른쪽 버튼 클릭 > Physics > Physical Material을 선택하여 새 물리 재질 에셋을 생성합니다. (예: PM_Rubber, PM_Ice)

물리 재질 에셋을 열고 Friction (마찰), Restitution (반발력, 탄성), Density (밀도) 등을 조절합니다.

이 물리 재질을 UStaticMeshComponent 또는 UMaterialInterface (메시에 적용된 재질)의 속성으로 할당합니다.

  • 메시 컴포넌트에 직접 할당: UStaticMeshComponent->SetPhysMaterialOverride(UMyPhysicalMaterial); 또는 에디터 디테일 패널에서 Physics > Phys Material Override에 할당합니다.
  • 재질에 할당: UMaterialInterface 에셋을 열고 Physical Material 속성에 할당합니다. (이 방법이 더 일반적입니다)

물리 재질을 통해 오브젝트의 표면 특성을 섬세하게 조절하여 게임 월드의 물리적 상호작용에 깊이를 더할 수 있습니다. 예를 들어, 얼음 바닥 위에서는 마찰이 적어 더 미끄러지게 하고, 고무공은 더 높이 튀어 오르게 할 수 있습니다.


마치며

물리 시뮬레이션과 힘의 적용은 게임 월드에 생동감과 반응성을 부여하는 핵심 요소입니다. 액터에 물리 시뮬레이션을 활성화하고, AddForce, AddImpulse, AddRadialForce와 같은 함수들을 사용하여 원하는 물리적 상호작용을 구현할 수 있습니다. 또한 물리 재질을 통해 오브젝트의 표면 특성을 정의하여 더욱 사실적인 물리적 행동을 만들 수 있습니다.