no image
언리얼 엔진 퍼포먼스 최적화의 첫걸음
최적화란?퍼포먼스 최적화는 일회성 작업이 아니라 반복적인 과정이다.가장 느린 병목 지점을 해결하면, 그 다음으로 느린 부분이 병목이 되고, 이를 다시 해결하는 과정을 반복하게 된다.즉, 끝이 없는 두더지 잡기와 같다. 최적화 전 준비: 테스트 환경 정리최적화를 시작하기 전에 테스트 환경을 정리하는 것이 매우 중요하다.불필요한 요소들이 성능 측정에 영향을 줄 수 있기 때문이다.예를 들어, 언리얼 엔진 에디터의 UI 자체가 성능에 영향을 줄 수 있다.따라서 최적화 테스트는 다음과 같은 환경에서 진행하는 것이 좋다:패키지된 빌드를 사용하거나최소한 Standalone Mode에서 테스트 1프레임의 기준 시간일반적인 게임은 60fps를 목표로 한다.이는 1초(1000ms)를 60프레임으로 나눈 값인 약 16.66..
2025.07.15
no image
스팀 OSS 연동
https://partner.steamgames.com/ public class Steamworks : ModuleRules{ public Steamworks(ReadOnlyTargetRules Target) : base(Target) { // The current SDK version number. double SteamVersionNumber = 1.62; // Mark the current version of the Steam SDK string SteamVersion = String.Format(CultureInfo.InvariantCulture, "v{0}", SteamVersionNumber).Replace(".", ""); Type = M..
2025.06.24
언리얼 사운드 시스템 기초
1. 개요프로젝트에서 게임 설정 관련된 작업을 시작하게 되었습니다. 가장 먼저 구현하고 싶었던 기능은 사운드 조절이었습니다. 이를 위해 언리얼 엔진의 사운드 시스템을 간단히 조사해보았고, 특히 Sound Class, Sound Mix, 그리고 이를 제어할 수 있는 C++ 함수들에 대해 처음으로 다뤄보게 되었습니다. 2. Sound ClassSound Class는 사운드를 논리적인 그룹으로 나누어 제어할 수 있게 해주는 시스템입니다. 예를 들어, 배경음(BGM), 효과음(SFX), 음성(Voice)을 각각의 사운드 클래스에 할당해두면, 이들 그룹의 볼륨을 개별적으로 제어할 수 있습니다. SC_BGM → 배경음용 Sound ClassSC_SFX → 효과음용 Sound ClassSC_UI → UI 효과음 전용..
2025.06.16
no image
온도 시스템
2025.06.04
언리얼 엔진의 입력 모드(FInputMode)
1. 개요언리얼 엔진에서는 플레이어가 키보드/마우스를 이용해 게임과 UI 중 어디에 입력을 보낼지 제어하기 위해 FInputMode 구조체를 사용합니다. UI 위젯을 띄웠을 때 플레이어가 게임 조작도 가능해야 하는지, 아니면 UI에만 집중해야 하는지를 정하는 설정입니다. 2. 주요 구조체 종류구조체설명FInputModeGameOnly게임 조작만 가능. UI 반응 XFInputModeUIOnlyUI에만 입력. 게임 입력은 무시FInputModeGameAndUI게임 조작 + UI 조작 둘 다 가능 3. 개념 설명3-1. 포커싱(Focus)UI 위젯 중 어떤 UI가 키보드 입력을 받을지 정하는 것.예를 들어 텍스트 입력창에 커서가 깜빡이는 상태가 “포커싱된” 상태.SetWidgetToFocus() 함수로 지정..
2025.05.21
no image
Instanced Static Mesh (ISM)
1. 개요오픈 월드 생존 게임에서 상호작용이 가능한 나무나 돌을 배치해야되는데 Foliage를 활용할 것 같아 찾아보던 중, Foliage로 배치된 스태틱 매쉬는 Instanced Static Mesh의 형태로 월드에 그룹화 되어 배치된다는 사실을 알게되었습니다. 그러나 Instanced 된 Static Mesh Component는 상호작용을 구현하기 어렵다고하여 좀 더 정리해 보았습니다.2. Instanced Static Mesh (ISM)동일한 스태틱 매시를 여러 번 배치할 때 퍼포먼스를 향상시키기 위한 시스템일반적인 Static Mesh Actor는 각각 별도의 액터/컴포넌트로 동작해서 많은 Draw Call과 메모리를 차지하게 되는데, ISM은 하나의 컴포넌트에서 여러 인스턴스를 관리하여 Draw..
2025.05.15
no image
언리얼 엔진 레벨 전환 시 로딩 방식
1. 개요오픈 월드 생존 게임을 개발 중이며, 메인 메뉴에서 인게임으로 넘어갈 때 자연스럽고 끊김 없는 레벨 전환을 구현하고 싶어서 레벨 로딩 방식에 대해 정리하게 되었습니다. 이를 위해 언리얼 엔진에서 제공하는 다양한 레벨 로딩 방식을 조사하였습니다.2. 언리얼 엔진의 레벨 전환 방식들OpenLevel(): 전통적인 동기적 방식, 전체 레벨 교체Level Streaming: 비동기 방식, 필요한 레벨을 불러오거나 제거Seamless Travel:서버와 클라이언트의 상태를 유지한 채 전환하는 방식3. OpenLevel - 동기적 레벨 전환Level을 완전히 교체하는 방식호출 시 현재 레벨 언로드 후 새로운 레벨 로드새로운 레벨 로딩 완료까지 화면 렌더링 멈춤장점단점구현이 가장 간단함클린 상태에서 로딩되므..
2025.05.14
no image
언리얼 엔진의 맵 로딩
오늘은 언리얼 엔진에서 대형 맵(오픈 월드 환경)을 효율적으로 로딩하는 방법에 대해 정리하겠습니다. 특히 World Partition과 Level Streaming의 차이, 그리고 Git LFS와의 연계를 중심으로 고민했습니다. 1. 언리얼 엔진의 주요 맵 로딩 방식Level Streaming (기존 방식)Persistent Level과 여러 서브 레벨을 나눠 구성필요할 때만 특정 레벨을 로딩하거나 언로드Streaming Volume, Blueprint, 또는 C++ 코드로 로딩 제어World Partition (UE5 이상)맵 전체를 자동으로 그리드 단위로 분할하고, 스트리밍을 자동화수동으로 서브 레벨을 나눌 필요 없음HLOD, Data Layer, Runtime Grid 등을 활용한 고급 스트리밍 가..
2025.05.08

최적화란?

퍼포먼스 최적화는 일회성 작업이 아니라 반복적인 과정이다.
가장 느린 병목 지점을 해결하면, 그 다음으로 느린 부분이 병목이 되고, 이를 다시 해결하는 과정을 반복하게 된다.
즉, 끝이 없는 두더지 잡기와 같다.

 

최적화 전 준비: 테스트 환경 정리

최적화를 시작하기 전에 테스트 환경을 정리하는 것이 매우 중요하다.
불필요한 요소들이 성능 측정에 영향을 줄 수 있기 때문이다.

예를 들어, 언리얼 엔진 에디터의 UI 자체가 성능에 영향을 줄 수 있다.
따라서 최적화 테스트는 다음과 같은 환경에서 진행하는 것이 좋다:

  • 패키지된 빌드를 사용하거나
  • 최소한 Standalone Mode에서 테스트

 

1프레임의 기준 시간

일반적인 게임은 60fps를 목표로 한다.
이는 1초(1000ms)를 60프레임으로 나눈 값인 약 16.66ms 안에 게임의 모든 연산이 끝나야 한다는 뜻이다.

  • 만약 특정 프레임이 10ms에 끝났다면?
    → 아직 6.66ms의 여유가 있으므로, 추가적인 로직이나 연출이 들어가도 성능상 문제가 없다.

이 기준 시간(16.66ms)은 퍼포먼스 분석의 핵심 기준점이 된다.


Stat Tools 3종 세트

1. Stat Unit

  • 병목 지점이 CPU Bound인지 GPU Bound인지 쉽게 확인 가능.
  • 명령어: ~ → stat unit

 

2. Stat FPS

  • 프레임 속도를 숫자로 확인 가능.
  • 명령어: ~ → stat fps

 

3. Stat Game

  • Game thread, Draw thread 등 상세한 게임 로직 실행 시간 확인 가능.
  • 명령어: ~ → stat game

 


Unreal Insights

  • 고급 프로파일링 툴
  • 퍼포먼스를 시스템적으로 분석하고 싶을 때 사용

 

Unreal Insights는 UE가 설치된 경로의 Engine > Binaries > Win64에서 UnrealInsights.exe를 눌러 실행할 수 있다

 

에디터를 실행 중이라면 위 그림과 같은 방법으로 쉽게 실행할 수 있다

 

두 방법으로 Unreal Insights를 실행시키면 위와 같은 창이 뜨게 된다

 

Unreal Insights 창을 띄운 채로 PIE(Play In Editor)를 실행하면, 게임의 실행 과정을 실시간으로 추적하고 세부적인 퍼포먼스 데이터를 수집하는 분석이 시작된다.

 

다음 글에서는 현재 진행 중인 프로젝트를 활용해 퍼포먼스를 개선해 나가는 과정을 공유드리겠습니다:)

스팀 OSS 연동

언리얼 거토
|2025. 6. 24. 17:50

https://partner.steamgames.com/

 

 

public class Steamworks : ModuleRules
{
	public Steamworks(ReadOnlyTargetRules Target) : base(Target)
	{
        // The current SDK version number.
        double SteamVersionNumber = 1.62;

        // Mark the current version of the Steam SDK
        string SteamVersion = String.Format(CultureInfo.InvariantCulture, "v{0}", SteamVersionNumber).Replace(".", "");

		Type = ModuleType.External;

		PublicDefinitions.Add(String.Format(CultureInfo.InvariantCulture, "STEAM_SDK_VER=TEXT(\"{0}\")", SteamVersionNumber));
		PublicDefinitions.Add("STEAM_SDK_VER_PATH=TEXT(\"Steam" + SteamVersion + "\")");

        string SdkBase = Target.UEThirdPartySourceDirectory + "Steamworks/Steam" + SteamVersion + "/sdk";
		if (!Directory.Exists(SdkBase))
		{
			string Err = string.Format("steamworks SDK not found in {0}", SdkBase);
			System.Console.WriteLine(Err);
			throw new BuildException(Err);
		}

 

DefaultEngine.ini

[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")

[OnlineSubsystem]
DefaultPlatformService=Steam

[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480

; If using Sessions
; bInitServerOnClient=true

[/Script/OnlineSubsystemSteam.SteamNetDriver]
NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"

1. 개요

프로젝트에서 게임 설정 관련된 작업을 시작하게 되었습니다. 가장 먼저 구현하고 싶었던 기능은 사운드 조절이었습니다. 이를 위해 언리얼 엔진의 사운드 시스템을 간단히 조사해보았고, 특히 Sound Class, Sound Mix, 그리고 이를 제어할 수 있는 C++ 함수들에 대해 처음으로 다뤄보게 되었습니다.

 

2. Sound Class

Sound Class는 사운드를 논리적인 그룹으로 나누어 제어할 수 있게 해주는 시스템입니다. 예를 들어, 배경음(BGM), 효과음(SFX), 음성(Voice)을 각각의 사운드 클래스에 할당해두면, 이들 그룹의 볼륨을 개별적으로 제어할 수 있습니다.

 

  • SC_BGM → 배경음용 Sound Class
  • SC_SFX → 효과음용 Sound Class
  • SC_UI → UI 효과음 전용 Sound Class
  • SC_Master → 위 Sound Class를 Child Class로 둔 전체 마스터 볼륨 설정용 클래스

🔧 에디터에서의 설정 방법:

  1. Content Browser에서 우클릭 → Sounds → Sound Class 생성
  2. 생성한 Sound Class의 속성에서 Parent Class를 설정하거나 계층 구조를 구성 가능
  3. 사운드 에셋(Sound Cue 등)에서 해당 Sound Class를 지정

이렇게 분리해두면 C++이나 Blueprint에서 각 그룹의 볼륨을 따로 조절할 수 있습니다.

 

 

3. Sound Mix

Sound Mix는 Sound Class에 정의된 속성(예: 볼륨, 피치 등)을 일시적으로 덮어쓰기 할 수 있도록 도와주는 시스템입니다. 예를 들어 UI를 열었을 때 배경음을 살짝 줄이거나, 특정 이벤트 발생 시 효과음을 강조하는 식의 연출이 가능합니다.

🔸 주로 사용되는 예시:

  • UI 열릴 때 BGM은 30%로 줄이고, 효과음은 그대로 유지
  • 일시정지 시 모든 사운드 볼륨 감소
  • 전투 진입 시 효과음 강조

사운드 믹스는 사운드 클래스의 볼륨을 즉시 바꾸는 것이 아니라, 오버라이드 형태로 부드럽게 변화시킬 수 있어 매우 유용합니다.

4. C++로 SoundMix 제어하기

이번에 새롭게 알게 된 부분은 사운드 믹스를 코드로도 동적으로 제어할 수 있다는 점이었습니다. UGameplayStatics에서 제공하는 몇 가지 함수를 통해 이를 처리할 수 있습니다.

🔹 1) UGameplayStatics::PushSoundMixModifier(UObject* WorldContextObject, USoundMix* InSoundMix)

  • 기능: SoundMix를 활성화합니다.
  • 효과: 해당 믹스에 정의된 Sound Class의 오버라이드가 적용됨
  • 예시:
  • cpp
     
UGameplayStatics::PushSoundMixModifier(GetWorld(), MySoundMix);

🔹 2) UGameplayStatics::PopSoundMixModifier(UObject* WorldContextObject, USoundMix* InSoundMix)

  • 기능: 적용 중인 SoundMix를 해제합니다.
  • 효과: 해당 믹스에서 설정한 오버라이드가 사라지고 원래 설정으로 복귀
  • 예시:
  • cpp
     
UGameplayStatics::PopSoundMixModifier(GetWorld(), MySoundMix);

🔹 3) UGameplayStatics::SetSoundMixClassOverride(...)

UGameplayStatics::SetSoundMixClassOverride(
    GetWorld(),              // 월드 컨텍스트
    MySoundMix,              // 적용할 SoundMix
    MySoundClass,            // 대상 SoundClass
    0.2f,                    // 볼륨 (0.0 ~ 1.0)
    1.0f,                    // 피치 (기본값 1.0)
    0.5f                     // 페이드 시간 (초 단위)
);
 
  • 기능: 특정 SoundMix 내에서 특정 SoundClass의 속성을 오버라이드
  • 장점: 에디터에서 설정한 SoundMix를 수정하지 않고도 런타임에 유연한 조절 가능
  • 사용 예시: UI가 열릴 때 BGM SoundClass만 볼륨을 0.2로 변경

5. 마무리

 

  • 처음 써보니 사운드 시스템이 꽤 강력하고 유연하다는 점
  • 다음엔 SoundConcurrency나 Audio Volume도 함께 공부하고 싶다는 마무리

 

온도 시스템

언리얼 거토
|2025. 6. 4. 21:00

'TIL > C++와 UE' 카테고리의 다른 글

언리얼엔진 멀티플레이에서의 PlayerState  (0) 2025.04.08
GameInstanceSubsystem  (0) 2025.04.02
Object Pooling  (0) 2025.03.28
언리얼 엔진 레플리케이션(Replication)  (0) 2025.03.11
네트워킹 및 멀티 플레이어 개요  (0) 2025.03.10

1. 개요

언리얼 엔진에서는 플레이어가 키보드/마우스를 이용해 게임과 UI 중 어디에 입력을 보낼지 제어하기 위해 FInputMode 구조체를 사용합니다. UI 위젯을 띄웠을 때 플레이어가 게임 조작도 가능해야 하는지, 아니면 UI에만 집중해야 하는지를 정하는 설정입니다.

 

2. 주요 구조체 종류

구조체 설명
FInputModeGameOnly 게임 조작만 가능. UI 반응 X
FInputModeUIOnly UI에만 입력. 게임 입력은 무시
FInputModeGameAndUI 게임 조작 + UI 조작 둘 다 가능

 

3. 개념 설명

3-1. 포커싱(Focus)

  • UI 위젯 중 어떤 UI가 키보드 입력을 받을지 정하는 것.
  • 예를 들어 텍스트 입력창에 커서가 깜빡이는 상태가 “포커싱된” 상태.
  • SetWidgetToFocus() 함수로 지정.
FInputModeUIOnly InputMode;
InputMode.SetWidgetToFocus(PauseWidget->TakeWidget())

PC->SetInputMode(InputMode);
PC->bShowMouseCursor = true;

 

TakeWidget()은 UUserWidget 내부의 Slate 루트 위젯을 받아옴( UUserWidget → SWidget 변환)


Slate 위젯
- UI를 구성하기 위한 저수준 UI 프레임워크
- 실질적으로 화면에 표시되는 UI의 렌더링과 입력 처리를 담당 -> 입력 포커싱도 그 일부
- 포커싱은 Slate 시스템 레벨에서 처리되는 기능

 

3-2. 마우스 락(Mouse Lock)

  • 마우스 커서를 화면 안에 가두는 것.
  • 예: FPS 게임에서 마우스가 화면 밖으로 나가는 걸 방지.
설정값 설명
DoNotLock 마우스가 화면 밖으로 자유롭게 나감
LockAlways 마우스를 항상 화면 안에 가둠
LockOnCapture 마우스를 클릭했을 때만 화면 안에 가둠
InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::LockAlways);
 
 

3-3. 마우스 캡쳐(Mouse Capture)

  • 마우스 움직임이 엔진에 의해 직접 추적되고 처리되는 상태.
  • FPS처럼 커서는 안 보이지만 마우스 움직임으로 카메라가 도는 게임이 대표적.
  • 캡처된 마우스는 커서가 보이지 않고, 마우스 좌표도 고정된 상태로 처리됨.

📌 FInputModeGameAndUI에서는 아래 함수로 마우스 캡처 중 커서를 숨길지 설정 가능:

FInputModeGameAndUI InputMode;
InputMode.SetHideCursorDuringCapture(false); // 캡쳐 중 커서 숨김

 

 

Instanced Static Mesh (ISM)

언리얼 거토
|2025. 5. 15. 18:41

1. 개요

오픈 월드 생존 게임에서 상호작용이 가능한 나무나 돌을 배치해야되는데 Foliage를 활용할 것 같아 찾아보던 중, Foliage로 배치된 스태틱 매쉬는 Instanced Static Mesh의 형태로 월드에 그룹화 되어 배치된다는 사실을 알게되었습니다. 그러나 Instanced 된 Static Mesh Component는 상호작용을 구현하기 어렵다고하여 좀 더 정리해 보았습니다.

2. Instanced Static Mesh (ISM)

  • 동일한 스태틱 매시를 여러 번 배치할 때 퍼포먼스를 향상시키기 위한 시스템
  • 일반적인 Static Mesh Actor는 각각 별도의 액터/컴포넌트로 동작해서 많은 Draw Call과 메모리를 차지하게 되는데, ISM은 하나의 컴포넌트에서 여러 인스턴스를 관리하여 Draw Call을 줄이고 렌더링 효율을 높임. → 압도적인 퍼포먼스
  • 대표적인 예: 숲에 수백, 수천 그루의 나무를 배치할 때

3. ISM과 Static Mesh Component의 차이

스태틱 메시 컴포넌트
인스턴스드 스태틱 메시 컴포넌트

항목 Static Mesh Component  Instanced Static Mesh Component
트랜스폼 개별 설정 가능 인스턴스별 위치/회전/스케일만 가능
머티리얼 개별 적용 가능 전체 인스턴스가 동일 머티리얼 공유
콜리전 개별 설정 가능 콜리전 설정은 전체 공유
액터화 각각 액터로 존재 하나의 컴포넌트에 여러 인스턴스 포함

4. ISM과 상호작용의 문제점

 

  • 각 인스턴스는 독립된 액터가 아니기 때문에 상호작용 처리가 어렵다.
    • 예: 플레이어가 특정 나무만 "베는" 행동을 하려면 그 인스턴스 하나만 제거하거나 상태를 바꿔야 함.
  • 인스턴스별 상태를 추적하거나 커스텀 이벤트 처리 불가
    • 예: “이 나무는 체력이 0이 되면 쓰러진다” 같은 논리 구현이 복잡함.
  • 트리거, 인터랙션, 개별 이벤트 처리가 필요한 경우에는 적합하지 않음

5. 그럼 오똑행?

방법 1: 초기엔 ISM으로 전체 배치 + 상호작용 필요 시 개별 액터로 교체 또는 거리 기반 액터로 교체

  • 예:
    1. 숲 전체는 ISM으로 구성
    2. 플레이어 근처 일정 거리 안에 있는 나무만 추적
    3. 상호작용 요청 시, 해당 인스턴스를 제거하고, 같은 위치에 동일한 Static Mesh 액터를 생성해서 교체
    4. 이후 해당 액터를 대상으로 상호작용 처리 (체력, 애니메이션, 사운드 등)

방법 2: ISM 인스턴스 ID 기반으로 최소한의 상호작용 구현

  • GetInstanceTransform, RemoveInstance, UpdateInstanceTransform 같은 함수로 제한적인 조작 가능
  • 예를 들어 나무를 "한 번에 쓰러뜨리는" 단순한 시스템이라면, RemoveInstance() 만으로도 구현 가능

방법 3: 모든 나무를 액터로 배치

  • 상호작용이 많은 게임이더라도, 수천 개 액터는 퍼포먼스 문제
  • LOD 거리 안의 나무만 액터화하는 동적 스폰 시스템 필요

6. 참고 함수들

 

  • AddInstance(): 새로운 인스턴스를 추가
  • RemoveInstance(int32 InstanceIndex): 특정 인스턴스를 제거
  • GetInstanceTransform(int32 InstanceIndex): 해당 인스턴스의 위치/회전/스케일 정보 가져오기
  • UpdateInstanceTransform(...): 인스턴스 위치 갱신
  • GetInstancesOverlappingBox(...): 특정 범위 내 인스턴스 찾기

 

언리얼 엔진 공식 문서: Instanced Static Mesh Component

 

1. 개요

오픈 월드 생존 게임을 개발 중이며, 메인 메뉴에서 인게임으로 넘어갈 때 자연스럽고 끊김 없는 레벨 전환을 구현하고 싶어서 레벨 로딩 방식에 대해 정리하게 되었습니다. 이를 위해 언리얼 엔진에서 제공하는 다양한 레벨 로딩 방식을 조사하였습니다.


2. 언리얼 엔진의 레벨 전환 방식들

  • OpenLevel(): 전통적인 동기적 방식, 전체 레벨 교체
  • Level Streaming: 비동기 방식, 필요한 레벨을 불러오거나 제거
  • Seamless Travel:서버와 클라이언트의 상태를 유지한 채 전환하는 방식

3. OpenLevel - 동기적 레벨 전환

  • Level을 완전히 교체하는 방식
  • 호출 시 현재 레벨 언로드 후 새로운 레벨 로드
  • 새로운 레벨 로딩 완료까지 화면 렌더링 멈춤
장점 단점
구현이 가장 간단함
클린 상태에서 로딩되므로 디버깅 용이
로딩중 화면 멈춤(로딩 화면 구현 필요)
메모리 재로딩 비용 큼
네트워크 게임에서 클라이언트 재연결 필요

4. Level Streaming - 비동기적 레벨 전환

  • 기존 레벨을 유지하고 백그라운드에서 다른 레벨 불러오기 가능
    • LoadStreamLevel() / UnloadStreamLevel() 함수
  • 원하는 시점에 해당 레벨을 보이게 만들거나 숨기기 가능
    • LoadStreamLevel() / LevelStreamingDynamic의 파라미터 조절
    • SetShouldBeVisible() / SetShouldBeLoaded() 함수로 처리
// 파라미터 조절 방식
LoadStreamLevel(LevelName = "Cave_Level",  
                MakeVisibleAfterLoad = false,  // ← 핵심!
                ShouldBlockOnLoad = false)

// 함수 사용 방식
if (StreamingLevel)
{
    StreamingLevel->SetShouldBeVisible(false);  // 일단 숨긴 상태로 로딩
    StreamingLevel->SetShouldBeLoaded(true);    // 로딩만 함

    // 나중에 이 타이밍에서 보이게 만들기
    StreamingLevel->SetShouldBeVisible(true);
}
장점 단점
비동기적 방식으로 자연스러운 로딩 가능
메모리 관리 효율적
레벨 간 의존성 주의 필요
면밀한 월드 구조 설계 필요

5. Seamless Travel - 네트워크 상태 유지 전환 방식

  • 서버가 맵 이동 시 클라이언트와의 연결 유지해야 할 때 사용
    • 매커니즘: Actor Preservation ("보존 가능한 액터" 목록을 만들고, 이 액터들을 파괴하지 않고 유지)
    • APlayerController를 유지한 채로 레벨 전환.
    • ServerTravel() 또는 ClientTravel()에서 ?Seamless 옵션 사용
  • Transition Map: 전환 중 임시로 로딩되는 맵. Seamless Travel 중 발생하는 공백을 보완
    • 목적: 원활한 레벨전환, 플레이어 상태 유지(캐릭터, 인벤토리 등), 경량화된 임시 맵
장점 단점
멀티플레이에서 상태 유지 가능
클라이언트 재접속 없이 전환 가능
여러 리소스를 병렬로 로딩 가능
Transition Map 구성 필요
디버깅 난이도 어려움

 

언리얼 엔진 공식 문서: Travellling in Multiplayer


6. BONUS: 더 부드러운 레벨 전환 방법

방법  설명
Preloading Assets 게임 시작 시 미리 로딩할 리소스를 AssetManager로 관리
LevelStreamingDynamic "동적"으로 원하는 시점에 로딩 → 전환
Camera Fade 연출 PlayerController->ClientStartCameraFade() 사용해서 전환 부드럽게 보이기

언리얼 엔진의 맵 로딩

언리얼 거토
|2025. 5. 8. 12:58

오늘은 언리얼 엔진에서 대형 맵(오픈 월드 환경)을 효율적으로 로딩하는 방법에 대해 정리하겠습니다. 특히 World PartitionLevel Streaming의 차이, 그리고 Git LFS와의 연계를 중심으로 고민했습니다.

 

1. 언리얼 엔진의 주요 맵 로딩 방식

Level Streaming (기존 방식)

  • Persistent Level과 여러 서브 레벨을 나눠 구성
  • 필요할 때만 특정 레벨을 로딩하거나 언로드
  • Streaming Volume, Blueprint, 또는 C++ 코드로 로딩 제어

World Partition (UE5 이상)

  • 맵 전체를 자동으로 그리드 단위로 분할하고, 스트리밍을 자동화
  • 수동으로 서브 레벨을 나눌 필요 없음
  • HLOD, Data Layer, Runtime Grid 등을 활용한 고급 스트리밍 가능
  • 협업 시 섹션 단위 커밋 가능 → Git 충돌 최소화

2. Level Streaming

Level Streaming 언리얼 공식 문서

2-1. 핵심 개념 : Persistent Level과 Sub Level

  • Persistent Level: 게임의 기본 월드. 가장 처음 로딩되는 베이스 레벨이며, 항상 메모리에 존재함.
    • 예: Directional Light(태양), Sky Atmosphere(하늘 표현), Fog(안개), Sky Light(글로벌 조명), NavMesh GameMode/State 등 전역 씬 요소
  • Sub Level: Persistent Level에 속해 있는 개별 레벨들. 필요할 때만 로딩하거나 언로드됨.
    • 예: 지역별 실제 지형, 건물, NPC 등
MyWorld (Persistent Level)
├── Town_Sub (Sub Level)
├── Forest_Sub (Sub Level)
├── Dungeon_Sub (Sub Level)
  • Streaming: 필요한 데이터만 그때그때 불러오고, 필요 없으면 메모리에서 제거하는 방식

2-2. Level Streaming의 로딩 방식

자동 로딩

  • Trigger Box 형태의 Streaming Volumes를 맵에 배치
  • 플레이어가 볼륨 안에 들어오면 해당 Sub Level을 자동 로딩
  • 나가면 자동 언로드 가능

수동 로딩

  • Blueprint에서 Load Stream Level, Unload Stream Level
  • C++ 코드 제어
ULevelStreaming* StreamLevel = UGameplayStatics::GetStreamingLevel(World, "Dungeon_Sub");
if (StreamLevel)
{
    StreamLevel->SetShouldBeLoaded(true);
    StreamLevel->SetShouldBeVisible(true);
}

 

2-3. Level Streaming의 장/단점

구분 장점 단점
메모리 효율 필요한 레벨만 로딩하여 메모리 절약 전체 로딩 순서와 조건을 수동 관리해야 함
유연한 로딩 제어 Blueprint, C++ 등으로 세밀한 제어 가능 복잡한 로딩 로직은 유지보수가 어려움
작업 분담 서브 레벨 단위로 협업 가능 동시에 같은 레벨을 수정하면 충돌 발생
구현 난이도 World Partition보다 간단하고 직관적 오픈 월드 전체 자동 로딩에는 부적합
호환성 UE4 기반 프로젝트와 잘 맞음 UE5에서 새 기능(World Partition) 대비 기능 부족

3. World Partition

World Patrition 언리얼 공식 문서

3-1. 핵심 기능 및 개념

(1). 그리드 기반 스트리밍

  • 월드를 자동으로 일정한 그리드 셀 단위로 분할
  • Runtime Grid: "어떻게 셀을 나눌지" 정의하는 설정 시스템
  • 플레이어 위치에 따라 필요한 셀만 로드 / 언로드

(2). Editor에서의 시각적 스트리밍 관리

  • 에디터에서 World Partition 창을 통해 어떤 셀(섹터)이 활성화되는지 시각적으로 확인 가능
  • 개발 중에도 일부 영역만 로딩해서 작업 가능 → 메모리 부담↓, 로딩 속도↑

(3). Data Layers 지원

  • Data Layers: 월드 안의 오브젝트들을 논리적으로 묶는 레이어 시스템
  • 월드 파티션의 "자동 셀 스트리밍"과 별도로, 게임 상황에 따라 특정 그룹을 통째로 로딩하거나 언로드
  • 같은 위치라도 레이어에 따라 다르게 표현 가능 → 환경에 따라 다른 월드 구성 가능 (예: 낮/밤, 계절)
  • Data Layers 언리얼 공식 문서

(4). HLOD(Hierarchcal Level of Detail) 지원

  • HLOD: 멀리 있는 오브젝트들을 간단한 형태(저해상도)로 자동으로 묶고 표시하는 시스템
  • 퍼포먼스 최적화에 매우 유용
  • HLOD 언리얼 공식 문서

(5). Streaming Source

  • 플레이어의 위치뿐만 아니라, 카메라 또는 특정 객체를 기준으로 월드 스트리밍을 제어
  • 기본적으로는 플레이어의 위치를 기준, but 추가하고 싶다면 UWorldPartitionSubsystem 에 등록

3-2.World Partition의 장/단점

구분 장점 단점
레벨 구조 하나의 .umap으로 월드 전체 구성 가능
→ Sub Level 분할 없이 관리
내부적으로 수천 개의 셀 .uasset 생성됨
→ 구조 단순해 보이지만 파일은 훨씬 많아짐
로딩 효율 플레이어 위치 기반 자동 로딩/언로딩으로
거대한 오픈 월드 구현에 최적
셀 단위 자동 로딩은 특정 상황(이벤트, 퍼즐)에서 세밀한 제어가 어려움
협업 효율 셀 단위로 동시에 여러 작업자가 병렬 작업 가능 → 작업 영역 분담 쉬움 같은 셀을 수정하면 충돌 발생, 병합 불가능 /
셀이 자동으로 나뉘기 때문에 명확한 소유권이 불분명
Git LFS와 충돌 시 치명적
성능 최적화 HLOD, Data Layer, Runtime Grid 등
최신 최적화 기능 내장
기능이 많고 설정이 복잡해 숙련되지 않으면 디버깅과 튜닝 어려움
작업 단순화 Sub Level을 수동으로 나누고
스트리밍 설정하는 작업이 필요 없음
자동화된 셀 구조는 개발자가 직접 컨트롤하기 어려운 영역도 있음
버전 관리 (Git) Sub Level 방식보다 .umap 충돌 가능성은 낮음 (파일 하나니까) Git LFS에서 수천 개의 셀 .uasset 추적
→ LFS 용량 폭발, 속도 저하, 충돌 빈번

3-2-1. 참고: World Partition에서의 파일 구조

항목 내용
.umap 파일 월드의 "메인 레벨" 메타 정보. 단 하나 존재. 기존의 Persistent + Sub Level 구조 대신 하나로 통합.
셀 .uasset 파일 World Partition이 맵을 자동으로 그리드 셀로 쪼개어 각각의 셀을 .uasset 파일로 저장. 수백~수천 개 생성.
HLOD .uasset 파일 자동 생성된 계층적 LOD 정보. 별도의 디렉터리에 다수의 .uasset 파일로 저장됨.
Data Layer 파일 선택적으로 사용하는 경우, 레이어별 콘텐츠 구성을 위한 추가 .uasset 파일 생성 가능.

3-3. Git 문제 대처 방법 및 협업 전략

(1). .gitattributes 설정으로 셀 제외

# 기본적으로 모든 바이너리 자산은 LFS로 추적
*.uasset filter=lfs diff=lfs merge=lfs -text
*.umap filter=lfs diff=lfs merge=lfs -text

# ⛔ World Partition 셀은 일반 Git으로 제외
Content/Maps/YourMapName/_WPGrid/*.uasset !filter !diff !merge text

# (선택) HLOD 관련 파일도 제외
Content/Maps/YourMapName/HLOD/*.uasset !filter !diff !merge text

 

(2). 협업 워크 플로우

전략 설명
작업 셀 분담 World Partition 창에서 각자 영역 미리 Lock(에디터 기능)
셀 자동 커밋 방지 수정 안 한 셀은 커밋 제외 또는 무시 설정
대형 자산 분리 저장 Git 외부 저장소(S3, GDrive 등)와 스크립트 연동
자동 배포/백업 스크립트 구성 Rsync, Python, Bash 등으로 데이터 통제
수정 범위 사전 공유 Discord/Slack 등으로 오늘 작업 셀 공유
변경 사항 리뷰 병합 전 .umap 외 변경 사항도 검토 필수