물리 시뮬레이션과 Force 적용
Chaos 물리 시뮬레이션을 활성화하고 Force·Impulse·물리 머티리얼로 물체의 운동과 반응을 제어합니다.
트리거 박스와 오버랩 이벤트에서는 물체를 움직이지 않고 겹침만 감지했습니다. 하지만 폭발에 날아가는 상자, 계단을 굴러떨어지는 공, 총에 맞아 흔들리는 표적처럼 물체가 스스로 떨어지고 부딪히고 튕겨야 하는 장면도 있습니다. 이런 움직임을 위치 값을 직접 바꿔 가며 흉내 내려면 중력, 질량, 마찰을 모두 손으로 계산해야 합니다.
언리얼 엔진은 이 계산을 물리 시뮬레이션(physics simulation)에 맡기게 해 줍니다. 이 글에서는 컴포넌트의 물리 시뮬레이션을 켜는 조건을 살펴보고, 힘(force)과 충격량(impulse)을 주는 함수가 시간과 질량을 어떻게 다르게 다루는지 비교한 뒤, 표면의 마찰과 반발을 정하는 물리 재질(Physical Material)을 다룹니다.
물리 시뮬레이션과 그 조건
물리 시뮬레이션은 게임 엔진이 중력, 질량, 마찰, 반발 같은 현실의 물리 법칙을 흉내 내 3D 물체의 움직임을 계산하는 과정입니다. 언리얼 엔진 5의 카오스(Chaos) 물리 엔진은 액터에 붙은 UPrimitiveComponent, 예를 들어 UStaticMeshComponent, USkeletalMeshComponent, UBoxComponent, UCapsuleComponent의 충돌 형상과 물리 속성을 바탕으로 동작합니다.
어떤 컴포넌트가 물리 시뮬레이션의 영향을 받으려면 몇 가지가 갖춰져야 합니다. 먼저 시뮬레이션 대상이 될 UPrimitiveComponent가 있어야 하는데, 가장 흔한 것은 UStaticMeshComponent나 USkeletalMeshComponent입니다. 이 컴포넌트에는 유효한 물리 바디가 있어야 하므로, 스태틱 메시라면 충돌 형상이, 스켈레탈 메시라면 피직스 에셋(Physics Asset)이 필요합니다. 그리고 SetSimulatePhysics(true)로 시뮬레이션을 켜고, 충돌 활성화 방식은 QueryAndPhysics나 PhysicsOnly처럼 물리에 참여하는 값으로 둡니다. 모빌리티가 Movable인지도 확인합니다.
다음은 물리 시뮬레이션되는 스태틱 메시 하나를 가진 액터입니다.
#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; // 물리 시뮬레이션될 메시 컴포넌트
};#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();
// 물리 시뮬레이션은 자동으로 시작됩니다.
}생성자에서는 MyMesh를 루트로 만들고, SetSimulatePhysics(true)로 시뮬레이션을 켠 뒤 PhysicsActor 프로파일을 적용합니다. 이 프로파일은 물리 바디 채널로 쿼리와 물리에 모두 참여하도록 정의되어 있습니다. 주석으로 남긴 ConstructorHelpers::FObjectFinder는 생성자에서 메시 에셋을 코드로 찾아 지정하는 방법이지만, 에셋 경로가 코드에 고정되므로 블루프린트에서 지정하는 편을 권장합니다.
이 클래스를 부모로 블루프린트 BP_MyPhysicsActor를 만들어 레벨에 배치하고, MyMesh 컴포넌트를 선택해 디테일 패널의 Physics 항목에서 Simulate Physics가 체크되어 있는지 확인합니다. 그리고 MyMesh에 유효한 충돌 형상을 가진 메시 에셋을 지정합니다. 중력, 모빌리티, 시뮬레이션, 상대 물체와의 충돌 응답이 모두 맞아야 플레이 중에 떨어지고 부딪히는 반응을 볼 수 있습니다. 코드만으로 에셋이 지정되거나 움직임이 확인되는 것은 아닙니다.
힘과 충격량을 주는 함수
물리 시뮬레이션이 켜진 물체에는 바깥에서 힘을 주어 움직임을 제어할 수 있습니다. 이런 함수들은 주로 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가 있습니다. 이름과 달리 월드의 다른 컴포넌트를 스스로 찾지는 않으므로, 여러 물체를 밀려면 대상을 따로 찾아 각각에 적용해야 합니다.
지속 힘·단발 충격량·적용 대상을 구분합니다
세 예시 프레임에서 AddForce 반복과 AddImpulse 한 번이라는 독립 입력 방식의 메시지/경과 상태를 비교한다. Chaos 내부 서브스텝·측정 변위로 확대하지 않는다.
UNREAL C++ · CH5-3
지속 힘·단발 충격량·적용 대상을 구분합니다
본문 API 소개와 실제 함수 호출을 비교합니다. 같은 벡터 크기라도 Force와 Impulse의 시간 의미·질량 처리·대상 범위가 다릅니다.
두 비교는 독립된 입력 방식입니다. 메시지 개수만으로 같은 변위를 보장하지 않으며, 원 코드의 ApplyExplosionForce는 MyMesh 수신 대상에 한 번 힘을 보내는 호출입니다.
| 호출 | 시간·질량 해석 | 이 예제에서 읽을 범위 |
|---|---|---|
| AddForce | 유지할 동안 반복 호출. 기본값은 질량의 영향 받음 | API 소개이며 아래 코드에는 실제 AddForce 호출이 없음 |
| ApplyUpwardImpulse | 한 번의 충격량. 코드의 bVelChange는 false | MyMesh 한 바디에 적용하며 속도 변화는 질량에 따라 달라짐 |
| ApplyExplosionForce | 호출할 때 방사형 힘 적용 | MyMesh의 반경 내 바디만 대상. 한 번 호출했다고 지속 힘이나 월드 전체 폭발이 되지 않음 |
- AddForce
- 시간·질량 해석: 유지할 동안 반복 호출. 기본값은 질량의 영향 받음이 예제에서 읽을 범위: API 소개이며 아래 코드에는 실제 AddForce 호출이 없음
- ApplyUpwardImpulse
- 시간·질량 해석: 한 번의 충격량. 코드의 bVelChange는 false이 예제에서 읽을 범위: MyMesh 한 바디에 적용하며 속도 변화는 질량에 따라 달라짐
- ApplyExplosionForce
- 시간·질량 해석: 호출할 때 방사형 힘 적용이 예제에서 읽을 범위: MyMesh의 반경 내 바디만 대상. 한 번 호출했다고 지속 힘이나 월드 전체 폭발이 되지 않음
ApplyUpwardImpulse의 로그는 호출을 알립니다. 실제 변위·충돌을 측정한 결과가 아닙니다. 순간 방사형 충격에는 AddRadialImpulse를 구분하며, 여러 물체에 적용할 대상 검색도 별도로 설계합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 왼쪽 또는 위쪽은 같은 힘을 유지할 기간에 AddForce를 반복해서 같은 대상 바디에 보내는 예이고, 오른쪽 또는 아래쪽은 AddImpulse를 한 번 보내는 별도 비교다. 선은 호출 메시지이며 시간은 아래로 진행한다. 세 호출은 예시 게임 프레임이지 Chaos 내부 서브스텝이나 실측 변위가 아니다. |
| 원 표의 셀 | 유지할 동안 반복 호출. 기본값에서는 질량의 영향을 받음 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 첫 프레임 두 독립 방식에서 같은 대상을 비교한다. |
|
|
| 뒤 프레임들 설명용 세 프레임의 메시지를 비교한다. |
|
|
| 해석 범위 호출 지속과 대상 범위를 읽는다. |
|
|
지속 힘·단발 충격량·적용 대상을 구분합니다
세 예시 프레임에서 AddForce 반복과 AddImpulse 한 번이라는 독립 입력 방식의 메시지/경과 상태를 비교한다. Chaos 내부 서브스텝·측정 변위로 확대하지 않는다.
UNREAL C++ · CH5-3
지속 힘·단발 충격량·적용 대상을 구분합니다
본문 API 소개와 실제 함수 호출을 비교합니다. 같은 벡터 크기라도 Force와 Impulse의 시간 의미·질량 처리·대상 범위가 다릅니다.
두 비교는 독립된 입력 방식입니다. 메시지 개수만으로 같은 변위를 보장하지 않으며, 원 코드의 ApplyExplosionForce는 MyMesh 수신 대상에 한 번 힘을 보내는 호출입니다.
| 호출 | 시간·질량 해석 | 이 예제에서 읽을 범위 |
|---|---|---|
| AddForce | 유지할 동안 반복 호출. 기본값은 질량의 영향 받음 | API 소개이며 아래 코드에는 실제 AddForce 호출이 없음 |
| ApplyUpwardImpulse | 한 번의 충격량. 코드의 bVelChange는 false | MyMesh 한 바디에 적용하며 속도 변화는 질량에 따라 달라짐 |
| ApplyExplosionForce | 호출할 때 방사형 힘 적용 | MyMesh의 반경 내 바디만 대상. 한 번 호출했다고 지속 힘이나 월드 전체 폭발이 되지 않음 |
- AddForce
- 시간·질량 해석: 유지할 동안 반복 호출. 기본값은 질량의 영향 받음이 예제에서 읽을 범위: API 소개이며 아래 코드에는 실제 AddForce 호출이 없음
- ApplyUpwardImpulse
- 시간·질량 해석: 한 번의 충격량. 코드의 bVelChange는 false이 예제에서 읽을 범위: MyMesh 한 바디에 적용하며 속도 변화는 질량에 따라 달라짐
- ApplyExplosionForce
- 시간·질량 해석: 호출할 때 방사형 힘 적용이 예제에서 읽을 범위: MyMesh의 반경 내 바디만 대상. 한 번 호출했다고 지속 힘이나 월드 전체 폭발이 되지 않음
ApplyUpwardImpulse의 로그는 호출을 알립니다. 실제 변위·충돌을 측정한 결과가 아닙니다. 순간 방사형 충격에는 AddRadialImpulse를 구분하며, 여러 물체에 적용할 대상 검색도 별도로 설계합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 왼쪽 또는 위쪽은 같은 힘을 유지할 기간에 AddForce를 반복해서 같은 대상 바디에 보내는 예이고, 오른쪽 또는 아래쪽은 AddImpulse를 한 번 보내는 별도 비교다. 선은 호출 메시지이며 시간은 아래로 진행한다. 세 호출은 예시 게임 프레임이지 Chaos 내부 서브스텝이나 실측 변위가 아니다. |
| 원 표의 셀 | 유지할 동안 반복 호출. 기본값에서는 질량의 영향을 받음 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 첫 프레임 두 독립 방식에서 같은 대상을 비교한다. |
|
|
| 뒤 프레임들 설명용 세 프레임의 메시지를 비교한다. |
|
|
| 해석 범위 호출 지속과 대상 범위를 읽는다. |
|
|
위 그림의 위쪽은 지속 힘과 단발 충격량을 시간 축에서 나란히 비교합니다. 왼쪽은 같은 힘을 유지할 기간 동안 호출자가 대상 바디에 AddForce를 거듭 보내는 경우이고, 오른쪽은 AddImpulse를 한 번 보낸 뒤 다음 프레임부터는 아무 입력도 보내지 않는 경우입니다. 그림의 선은 호출 메시지이고 시간은 아래로 흐르며, 세 번의 호출은 설명을 위한 게임 프레임일 뿐 카오스 내부의 서브스텝(substep) 횟수나 실제로 측정한 변위가 아닙니다. 그래서 메시지 개수가 같다고 같은 변위가 나온다고 볼 수 없습니다.
그림의 표는 호출마다 시간과 질량의 해석, 그리고 이 예제에서 읽을 범위를 정리합니다. AddForce는 유지할 동안 반복 호출하고 기본값에서는 질량의 영향을 받지만, 이 글의 아래 코드에는 실제 AddForce 호출이 없고 API 소개로만 나옵니다. 뒤에 나올 ApplyUpwardImpulse는 bVelChange를 false로 둔 한 번의 충격량으로, MyMesh 한 바디에 적용되며 속도 변화는 질량에 따라 달라집니다. ApplyExplosionForce는 호출할 때 방사형 힘을 주지만 MyMesh의 반경 안 바디만 대상이므로, 한 번 불렀다고 지속 힘이나 월드 전체의 폭발이 되지는 않습니다. 그림 아래의 설명처럼 순간적인 방사형 충격이 필요하다면 AddRadialImpulse를 따로 쓰고, 여러 물체에 적용할 대상 검색도 따로 설계합니다.
C++에서 충격량 주기
이제 AMyPhysicsActor에 위쪽으로 충격량을 주는 함수와 방사형 힘을 주는 함수를 더해 봅니다. 헤더에서는 두 함수를 public에 두고 BlueprintCallable을 붙여, 뒤에서 블루프린트의 클릭 이벤트가 부를 수 있게 합니다. cpp의 두 함수는 모두 먼저 IsSimulatingPhysics()로 시뮬레이션 중인지 확인합니다. 시뮬레이션이 꺼진 바디는 물리 엔진이 움직이지 않으므로, 힘이나 충격량을 주어도 반응이 없기 때문입니다.
#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);
};#include "MyPhysicsActor.h"
#include "Components/StaticMeshComponent.h"
// AddRadialForce는 위 StaticMeshComponent가 상속받은 함수입니다.
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());
}
}이 헤더와 .cpp는 앞의 MyPhysicsActor에 함수를 더하는 발췌입니다. 같은 클래스의 생성자와 BeginPlay를 두 번 정의하지 말고, 기존 정의에 새 함수만 더합니다. 특히 ... 기존 생성자 코드 ...를 빈 생성자로 바꾸면 앞에서 만든 메시 생성, 루트 설정, 충돌 설정이 사라지므로 주의합니다.
ApplyUpwardImpulse는 메시가 시뮬레이션 중일 때만 Z축 방향으로 50000의 충격량을 주고, 마지막 인자 false로 질량의 영향을 받는 충격량을 씁니다. ApplyExplosionForce는 받은 폭발 지점, 반경, 세기로 MyMesh에 방사형 힘을 주며, RIF_Linear이므로 폭발 지점에서 멀수록 힘이 줄어듭니다. 두 함수 모두 로그를 남기지만, 그 로그는 호출되었다는 사실을 알려 줄 뿐 실제로 얼마나 움직였는지 측정한 결과가 아닙니다.
질량이 결과에 어떻게 영향을 주는지 계산해 봅시다. 다른 힘과 접촉을 빼고 이상적인 조건에서, 같은 충격량 J를 질량 m과 2m인 바디에 한 번씩 주면 속도 변화는 각각 J/m과 J/(2m)입니다. 질량이 두 배인 바디는 속도가 절반만 바뀝니다.
반면 AddForce는 힘을 유지하려는 기간 동안 반복해서 부르는 방식입니다. 같은 힘 F를 다른 힘 없이 같은 질량 m에 시간 t 동안 유지하면 속도 변화는 F×t/m으로 계산할 수 있습니다. 이 이상적인 식에서는 시간을 두 배로 늘리면 속도 변화도 두 배가 되지만, 충돌, 중력, 저항이 있는 실제 장면 전체의 결과까지 두 배라고 단정할 수는 없습니다. 또 엔진이 받는 AddForce의 입력은 힘 자체이므로, 결과를 예상하려고 곱한 시간을 입력 값에 한 번 더 곱해 넘기면 안 됩니다.
블루프린트에서 시험하기
BP_MyPhysicsActor를 레벨에 여러 개 배치하고, 각 인스턴스에 메시 에셋과 유효한 물리 바디가 있는지, 시뮬레이션이 켜져 있는지 확인합니다. 그다음 입력이 연결된 로컬 플레이어 컨트롤러나 플레이어 캐릭터의 이벤트 그래프를 엽니다. 별도의 BP_PhysicsLauncher 액터를 쓴다면 그 액터가 입력을 받도록 먼저 설정합니다.
마우스 왼쪽 클릭 이벤트를 받아, 마우스의 화면 좌표를 월드 위치와 방향으로 변환한 뒤 시작점과 끝점을 만들고 Line Trace By Channel이나 Sphere Trace By Channel로 대상을 찾습니다. 트레이스가 찾은 Hit Actor를 Cast To MyPhysicsActor로 확인하고, 캐스팅에 성공하면 그 인스턴스에서 Apply Upward Impulse나 Apply Explosion Force를 부릅니다. Apply Explosion Force의 폭발 지점에는 트레이스의 Hit Location을 넣고, 반경과 세기는 시험할 값으로 정합니다. 트레이스 자체는 라인 트레이스 (Trace) 시스템에서 자세히 다룹니다.
더 깊이: 한 프레임에 물리를 여러 번 계산하는 서브스테핑
언리얼은 프레임마다 길이가 달라지는 가변 프레임으로 동작하지만, 물리 엔진은 작고 일정한 시간 간격으로 계산할 때 가장 안정적입니다. 프로젝트 설정의 Physics 항목에서 서브스테핑(Substepping)을 켜면, 엔진은 한 프레임의 시간을 여러 개의 짧은 물리 스텝으로 나눠 계산합니다. Max Substep Delta Time은 서브스텝 하나의 최대 길이로, 이 값이 0.025초이면 0.05초짜리 스텝은 서브스텝 두 개로 나뉩니다. Max Substeps는 한 스텝을 나눌 수 있는 최대 횟수라서, 프레임이 아주 길어져도 서브스텝 수가 끝없이 늘어나지 않습니다. 서브스텝을 짧게 잡을수록 시뮬레이션은 안정되지만 CPU 비용이 커지며, 공식 문서는 래그돌의 떨림처럼 복잡한 피직스 에셋에서 효과가 가장 크게 드러난다고 설명합니다.
서브스테핑을 켰다고 AddForce를 서브스텝마다 다시 부를 필요는 없습니다. 같은 가속을 내려면 그 힘을 연속한 서브스텝 모두에 적용해야 하는데, 엔진이 이 일을 내부에서 처리합니다. 대신 충돌 콜백은 성능을 위해 마지막 서브스텝이 끝날 때까지 미뤄지므로, 같은 충돌에 대해 콜백을 여러 번 받을 수 있습니다. 히트 이벤트에서 피해를 주는 코드라면 한 프레임 안에서 같은 상대가 여러 번 들어오는 경우를 고려해야 합니다. 또 문서는 서브스테핑이 현재 모바일 기기에서 지원되지 않는다고 적고 있습니다.
물리 재질로 표면 특성 정하기
물리 재질(Physical Material)은 물체 표면의 마찰과 반발 같은 물리적 특성을 정하는 에셋입니다. 메시에 적용하면 그 물체가 다른 물리 물체와 만날 때 표면에 맞는 반응을 얻을 수 있습니다. 얼음 바닥은 마찰을 줄여 더 미끄러지게 하고, 고무공은 반발을 키워 더 높이 튀게 하는 식입니다.
콘텐츠 브라우저에서 마우스 오른쪽 버튼을 눌러 Physics 메뉴의 Physical Material을 고르면 새 물리 재질 에셋이 만들어집니다(예: PM_Rubber, PM_Ice). 에셋을 열어 Friction(마찰), Restitution(반발, 탄성), Density(밀도) 같은 값을 조절합니다.
만든 물리 재질은 두 곳에 지정할 수 있습니다. 메시 컴포넌트에 직접 지정하려면 C++에서 MyMesh->SetPhysMaterialOverride(PhysMat)처럼 부르거나, 디테일 패널의 Physics 항목에서 Phys Material Override에 넣습니다. 머티리얼에 지정하려면 메시에 쓰인 머티리얼 에셋을 열어 Phys Material 속성에 넣습니다. 다만 실제로 쓰이는 물리 재질은 단순 충돌과 복잡 충돌 가운데 무엇을 쓰는지, 그리고 컴포넌트의 오버라이드가 있는지에 따라 달라집니다.
물리 재질은 마찰과 반발 같은 접촉의 특성을 정하고, 앞의 힘 함수들은 운동에 입력을 줍니다. 두 가지는 서로 다른 일을 하므로, 물체가 예상만큼 미끄러지지 않는다면 힘을 키우기 전에 표면의 물리 재질부터 확인합니다.
정리
- 물리 시뮬레이션에는 유효한 물리 바디를 가진
UPrimitiveComponent,SetSimulatePhysics(true), 물리에 참여하는 충돌 설정,Movable모빌리티가 필요합니다. AddForce는 유지할 기간 동안 반복해서 부르는 힘이고,AddImpulse는 한 번에 속도를 바꾸는 충격량입니다. 기본 모드에서는 둘 다 질량의 영향을 받습니다.- 이상적인 조건에서 충격량 J의 속도 변화는 J/m, 시간 t 동안 유지한 힘 F의 속도 변화는 F×t/m입니다.
bAccelChange와bVelChange를true로 두면 질량과 상관없는 변화로 해석합니다.AddRadialForce는 호출한 컴포넌트의 범위 안 바디에만 적용되며, 여러 물체를 밀려면 대상을 따로 찾아야 합니다.- 물리 재질은 마찰, 반발, 밀도 같은 표면 특성을 정하며, 컴포넌트의 오버라이드나 머티리얼에 지정합니다.
연습 문제
1. 질량이 세 배라면
같은 충격량 J를 받은 두 바디의 질량이 m과 3m입니다. 질량의 영향을 받는 기본 모드에서 두 속도 변화의 비는 얼마일까요?
해설 보기
속도 변화는 각각 J/m과 J/(3m)이므로 3:1입니다. 질량이 세 배인 바디는 속도가 3분의 1만 바뀝니다.
질량과 상관없이 같은 속도 변화를 원한다면 bVelChange를 true로 두는 방식을 검토할 수 있습니다. 하지만 이 글의 코드는 false로 썼으므로, 코드를 읽을 때 그 동작으로 바꿔 해석하면 안 됩니다.
2. 한 번의 폭발 호출
첫 번째 BP_MyPhysicsActor에서 ApplyExplosionForce를 한 번 불렀습니다. 반경 안에 있는 두 번째 BP_MyPhysicsActor도 함께 밀려날까요? 첫 번째 액터는 다음 프레임에도 계속 밀릴까요?
해설 보기
둘 다 아닙니다. 이 함수의 AddRadialForce는 호출한 MyMesh의 반경 안 바디만 대상으로 하므로, 다른 액터를 찾아 함께 밀지 않습니다. 또 힘은 호출한 그 순간에만 들어가며, 코드에 반복 호출하는 로직이 없으므로 다음 프레임에도 계속 밀리지는 않습니다.
여러 물체에 한 번의 폭발 충격을 주고 싶다면, 반경 안의 대상을 따로 찾고 각 대상에 AddRadialImpulse 같은 순간 충격을 적용하는 경로를 설계합니다.
3. 미끄러지지 않는 얼음
얼음 바닥 위의 상자가 예상만큼 미끄러지지 않아 AddForce의 세기를 두 배로 늘렸습니다. 먼저 확인할 것은 무엇일까요?
해설 보기
힘을 키우기 전에 바닥과 상자에 어떤 물리 재질이 실제로 쓰이고 있는지 확인합니다. 바닥 메시에 PM_Ice를 지정했더라도 컴포넌트의 Phys Material Override나 충돌 방식에 따라 다른 물리 재질이 쓰일 수 있고, 상자 쪽 표면의 마찰도 함께 영향을 줍니다.
물리 재질은 접촉의 특성을, 힘은 운동의 입력을 정합니다. 마찰이 원인이라면 힘을 키우는 것은 원인을 고치지 않고 결과만 덮는 일입니다.
힘과 충격량 함수의 매개변수는 Epic의 UPrimitiveComponent API 문서에서, 물리 재질의 속성은 Physical Materials 문서에서 확인할 수 있습니다.
다음 글 라인 트레이스 (Trace) 시스템에서는 보이지 않는 선이나 도형을 쏘아 맞은 대상을 찾는 트레이스를 C++로 구현합니다.