보스 레벨에서 플레이어들이 사용하는 다리 Mesh가 있는데, 이것을 파괴하는 시스템을 개발하게 되었다.
1. 고민
처음에는 해당 기획을 듣고 여러 방식을 떠올리게 되었다.
- Chaos
- Physics Constraint
- etc...
개발하기 전 가장 중요하게 생각했던 부분은 "부서진 이후의 결과물"에 대한 고민이었다.
- 파괴된 이후의 Mesh가 사라지도록 하여 게임에 영향을 미치지 않도록 할 것인가
- 파괴된 이후의 Mesh가 이후 게임에 영향을 미치도록 할 것인가
이 부분은 게임 플레이에 영향을 미칠 수 있는 부분이라고 생각하여 최대한 게임 플레이에 있어서 지장을 주지 않는 방향으로 생각하고 개발을 하였다.
다음으로는 고민한 내용은 얼마나 예쁘게 부술 수 있는가에 대한 내용이었다.
- 현재 팀에는 배경 모델링을 담당해주시는 분이 있었기 때문에 카오스로 부수기보다는 부서져 있는 에셋 자체를 쓰는 것이 더 좋다는 생각이 들었다.
- 다리 메시가 뼈모양의 다리였고, 이 경우 뼈 마디마디가 부서지는 것이 자연스럽다는 생각이 들었기 때문이다.
- 돌이나 기둥 같은 에셋이었다면, 매번 다르게 부서지는 것이 더 그럴듯 해 보이겠지만...
결론적으로 아래와 같이 각각의 조각으로 구성된 메시 에셋을 받아서 작업을 시작하게 되었다.


2. 여러 시도들
다리 메시 에셋을 받기 전, 카오스 시스템에 대한 테스트를 해보았다.
- 가장 큰 문제점은 부서진 이후의 위치가 동기화 되지 않는다는 문제가 있었다.
- 이걸 해결하는 방법을 찾아보긴 했지만, Chaos 시스템에 익숙하지 않은 상태에서 너무 오랜 시간을 쏟아버리는 것은 좋지 않다고 판단했다.
- 특히 위치 동기화가 제대로 되지 않은 경우, 부서진 이후 게임 내에서 사라지지 않도록 구현하게 된다면 문제가 되기 때문에 다른 방법을 선택하게 되었다.

새롭게 시도한 방식이 Physics Constraint를 사용한 내용이다.
- 언리얼이 가지고 있는 기능으로 로프 시스템이나 흔들다리와 같은 시스템을 개발할 때 주로 사용하는 컴포넌트이다
- 아래 사진처럼 각 mesh를 연결하여 두고, 특정 힘을 가했을 때 해당 연결을 끊어주는 식으로 구현하려고 했다.
- 그러나 모든 제한조건을 잠궈두어도, 약간 용수철처럼 통통거리는 느낌이 나서 뼈 모양의 다리와는 어울리지 않는다고 느껴졌고,
- 이 방식 역시 새롭게 사용하는 방식이다 보니 시간내에 원하는 퀄리티의 결과물을 내기 어려울 것으로 판단되었다.


마지막으로 가장 단순한 방식으로 개발을 하게 되었다.
- 다리를 구성하는 메시 한 조각을 각각 액터로 만든 후, 조각 액터를 모두 가지는 부모 액터로 다리를 구성하는 방식이다.
- 처음에는 조각 액터들을 simulate physics를 false로 만들어두고, 이후에 true로 변경하여 부서지도록 구현을 하게 되었다.
- 이 방식으로 구현하면, Actor의 리플리케이션을 통해 모든 클라이언트들에게 같은 모습으로 보여줄 수 있기 때문에, 처음한 고민에 있어서도 문제가 없기에 이 방식을 택하게 되었다.
3. 개발
먼저 각 조각 단위의 액터를 만들었다.
- 이때 주의할 점은 각 메시가 너무 가볍게 날라가는 느낌이 아니라 묵직하게 무너져 내리는 느낌을 내고 싶었다.
- 이 느낌을 내기 위해서 메시의 무게를 무겁게 만드는 방법을 사용하였다.
- 또한 데미지를 가한 방향을 무시하여, 무조건 아래쪽으로만 내려가도록 구현하였다.
AGS_BridgePiece::AGS_BridgePiece()
{
...
MeshComponent->SetSimulatePhysics(false);
MeshComponent->SetMassOverrideInKg(NAME_None, 100000.0f, true);
}
//데미지 입히기 및 파괴되면 시뮬레이션 시작하는 함수
void AGS_BridgePiece::BrokeBridge(float InDamage)
{
if (HasAuthority() && !bIsDestroyed)
{
CurrentHealth -= InDamage;
if (CurrentHealth <= KINDA_SMALL_NUMBER)
{
MeshComponent->SetSimulatePhysics(true);
MeshComponent->bApplyImpulseOnDamage = false;
MeshComponent->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);
...
}
}
}
//처음에 StaticMesh를 세팅하는 함수
void AGS_BridgePiece::SetBridgeMesh(UStaticMesh* InMesh, float InValue)
{
if (MeshComponent && InMesh)
{
MeshComponent->SetStaticMesh(InMesh);
MaxHealth = InValue;
}
}
이후로 Piece를 포함하는 Bridge 액터를 만들었다.
- 또한 각 조각마다 체력을 가지고 있어, 일정 데미지 이상 맞은 경우 파괴되는 시스템으로 구현하고 싶었다.
- 이때 가운데 부분에 있어서는 체력을 낮게 설정하여, 쉽게 부서지고 가장자리로 갈수록 체력이 높아서 잘 부서지지 않도록 구현하였다.
- 공중에 가운데 쪽 메시만 떠잇는 모습은 플레이어들에게 이상하게 느껴질 수 있기 때문
- Bridge 액터에서는 StaticMesh의 갯수만큼 BridgePiece를 만들어서 ChildActor로 붙여주는 식으로 구성하였다.
- 추가적으로 양 끝에 존재하는 다리 조각은 부서지지 않고 남아있도록 구현하였다.
- 다리가 다 부서져서 사라지는것 보다 "원래 이곳이 다리가 있었던 자리다" 라는 것을 알려주기 위해 부서지지 않게 하였다.
//GS_Bridge.h
//부서지지 않는 액터
UPROPERTY(EditAnywhere)
TObjectPtr<UStaticMeshComponent> NotBrokenPieces1;
...
//GS_Bridge.cpp
void AGS_Bridge::SetUpBridge()
{
if (HasAuthority())
{
HalfBridgeMeshSize = BridgeMeshAssets.Num() / 2;
for (int32 i = 0; i < BridgeMeshAssets.Num(); ++i)
{
UStaticMesh* CurrentMesh = BridgeMeshAssets[i];
if (!CurrentMesh)
{
continue;
}
UChildActorComponent* NewPieceComponent = NewObject<UChildActorComponent>(this);
if (NewPieceComponent)
{
NewPieceComponent->SetChildActorClass(AGS_BridgePiece::StaticClass());
NewPieceComponent->AttachToComponent(RootComponent,FAttachmentTransformRules::KeepRelativeTransform);
NewPieceComponent->RegisterComponent();
AGS_BridgePiece* PieceActor = Cast<AGS_BridgePiece>(NewPieceComponent->GetChildActor());
if (PieceActor)
{
//가운데 인덱스의 가중치를 크게 만들기
float weight = 1.f - (abs(i - HalfBridgeMeshSize) / (float)HalfBridgeMeshSize);
FTransform Transform = FTransform::Identity;
PieceActor->SetActorRelativeTransform(Transform);
PieceActor->SetBridgeMesh(CurrentMesh, weight * 100.f + 100.f);
}
}
}
}
}
4. 트러블 슈팅
리플리케이션 관련 문제가 있었다.
- GS_BridgePiece의 생성자에서 replicated 설정만 해주었더니 아래와 같이 메시가 보이지 않는 문제가 발생했다.

원인을 찾아보니 액터 자체는 리플리케이트 되지만, 메시 컴포넌트의 변경사항은 클라이언트에게 전송하지 않기 때문이었다.
- 언리얼 엔진은 성능상의 이슈로 모든 컴포넌트를 복제하지 않기 때문에 필요한 부분에 있어서는 아래와 같이 직접 리플리케이트 세팅을 해주어야 한다.
MeshComponent->SetIsReplicated(true);
- 참고) GS_Bridge는 리플리케이트 하지 않음
< 패키징 시 오류 발생 >
- 에디터로 테스트할 때에는 문제가 없었지만, 패키징 시 아래와 같은 에러와 함께 패키징에 실패하였다.
- 해당 원인 분석 결과 "생성자에서 물리 관련된 속성을 바꾸려고 할 때 생기는 문제" 였다.
- CDO라는 것은 에디터에서 속성 패널등을 표시하기 위한 용도로 사용되는 것인데, CDO가 생성될 때는 게임 월드나 GEngine이 아직 로드되지 않은 상황이기 때문에 컴포넌트 자체가 시스템보다 먼저 초기화되면서 생기는 문제였다.
LogPhysics: Error: FBodyInstance::GetSimplePhysicalMaterial : GEngine not initialized! Cannot call this during native CDO construction, wrap with if(!HasAnyFlags(RF_ClassDefaultObject)) or move out of constructor, material parameters will not be correct.
해결 방안으로 생성자에서 진행한 물리세팅 로직을 BeginPlay로 옮겨서 해결하였다.
void AGS_BridgePiece::BeginPlay()
{
Super::BeginPlay();
...
MeshComponent->SetSimulatePhysics(false);
MeshComponent->SetMassOverrideInKg(NAME_None, 100000.0f, true);
}
< 텍스쳐 적용 오류 >
- 텍스쳐를 적용하기 위해 아래와 같이 SetBridgeMesh 함수에 매개변수를 추가하여 해결하려고하였다.
void AGS_BridgePiece::SetBridgeMesh(UStaticMesh* InMesh, UMaterialInterface* InMaterial, float InValue)
{
if (MeshComponent && InMesh)
{
MeshComponent->SetStaticMesh(InMesh);
//텍스쳐 세팅
MeshComponent->SetMaterial(0, BridgeMaterial);
MaxHealth = InValue;
}
}
- 그러나 이 경우 아래와 같이 텍스쳐 적용이 되지 않는 문제가 있었다.

- 이유는 SetMaterial 함수가 리플리케이티드 되지 않기 때문이었다.
- 이것을 해결하기 위해서 Material 자체를 OnRep함수와 연동하여 클라이언트 쪽에서 SetMaterail 함수를 호출하도록 구현하였다.
//헤더
UFUNCTION()
void OnRep_BridgeMaterial();
UPROPERTY(ReplicatedUsing=OnRep_BridgeMaterial)
UMaterialInterface* BridgeMaterial;
//cpp
void AGS_BridgePiece::SetBridgeMesh(UStaticMesh* InMesh, UMaterialInterface* InMaterial, float InValue)
{
if (MeshComponent && InMesh)
{
MeshComponent->SetStaticMesh(InMesh);
if (InMaterial)
{
BridgeMaterial = InMaterial;
}
MaxHealth = InValue;
}
}
void AGS_BridgePiece::OnRep_BridgeMaterial()
{
if (MeshComponent && BridgeMaterial)
{
MeshComponent->SetMaterial(0, BridgeMaterial);
}
}
- 해결된 모습

5. 결과 및 추후 개발할 것
- 현재까지 결정된 내용으로는 일정 시간 시뮬레이션 후, 사라지는 것으로 기획이 결정되었다.
- 사라질때 디졸브 효과로 사라지게 하는 것을 추가할 예정이며,
- 사라지는 것으로 기획이 픽스된다면, 카오스를 통해 갈비뼈와 같이 약한 부분을 부수는 시스템을 추가하여 좀 더 실감나는 파괴 시뮬레이션을 구현해 볼 예정이다.
'Unreal Engine' 카테고리의 다른 글
| UnrealEngine - HitStop (0) | 2025.06.17 |
|---|---|
| UE5 Issues - 서버 & 클라이언트 로직 (0) | 2025.06.15 |
| UE5 Issues : 데디케이트 서버 UI에 관해 (0) | 2025.06.12 |
| Unreal Engine - 데디케이티드 서버 11 (콤보 공격 최적화) (0) | 2025.06.02 |
| Unreal Engine - 데디케이티드 서버 10 (콤보 공격) (0) | 2025.05.30 |