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
모의 면접 복기
오늘 모의 면접을 봤는데 개인적으로 많이 아쉬웠습니다 그래서 복기합니다 질문 1. 포인터와 레퍼런스의 차이점을 설명해주세요.내 대답:포인터와 레퍼런스의 차이는 주로 메모리 접근 방식과 유연성에 있습니다.포인터는 어떤 객체의 메모리 주소를 저장해서, 나중에 해당 주소를 통해 값을 바꾸거나 확인할 수 있습니다. nullptr이 될 수도 있어서 객체 존재 여부를 체크해야 할 때 유용합니다.레퍼런스는 어떤 객체의 ‘별명’처럼 동작합니다. 한 번 연결되면 다른 객체로 바꿀 수 없고, 항상 유효해야 하므로 존재가 확실한 경우에 사용합니다. 꼬리 질문 1. 레퍼런스를 함수 인자로 사용할 경우, 값을 복사하지 않고 직접 참조만 하기 때문에 복사 비용이 절감됩니다. 이때 발생할 수 있는 문제와 그 해결 방법은 무엇인가요..
2025.05.01

스팀 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 외 변경 사항도 검토 필수

 

모의 면접 복기

언리얼 거토
|2025. 5. 1. 17:43

오늘 모의 면접을 봤는데 개인적으로 많이 아쉬웠습니다 그래서 복기합니다

 

질문 1. 포인터와 레퍼런스의 차이점을 설명해주세요.

내 대답:

포인터와 레퍼런스의 차이는 주로 메모리 접근 방식과 유연성에 있습니다.

  • 포인터는 어떤 객체의 메모리 주소를 저장해서, 나중에 해당 주소를 통해 값을 바꾸거나 확인할 수 있습니다. nullptr이 될 수도 있어서 객체 존재 여부를 체크해야 할 때 유용합니다.
  • 레퍼런스는 어떤 객체의 ‘별명’처럼 동작합니다. 한 번 연결되면 다른 객체로 바꿀 수 없고, 항상 유효해야 하므로 존재가 확실한 경우에 사용합니다.

 

꼬리 질문 1.

레퍼런스를 함수 인자로 사용할 경우, 값을 복사하지 않고 직접 참조만 하기 때문에 복사 비용이 절감됩니다. 이때 발생할 수 있는 문제와 그 해결 방법은 무엇인가요?

 

❌ 내 대답: 

한 객체가 다른 객체의 멤버를 참조하면 객체의 압축성(? → 일관성 or 캡슐화)을 해칠 수 있어서 문제가 됩니다.

 

✅ 기대 대답:

레퍼런스를 함수 인자로 사용할 경우 원본 데이터를 직접 수정할 수 있기 때문에, 의도치 않은 수정이 발생할 수 있습니다.
이를 방지하려면 const 키워드를 사용해서 읽기 전용 참조로 만들면 됩니다.
예: void Func(const MyStruct& data);


 

질문 2. Stack과 Heap의 메모리 차이가 무엇인가요?

내 대답:

Stack

  • 컴파일 타임에 크기와 생명 주기가 정해진 데이터 저장 영역
  • 지역 변수일시적인 계산용 변수 저장 (예: float DeltaTime)
  • 메모리 자동 할당/해제, 접근 속도 빠름, 오버헤드 적음
  • 공간 제한, 동적 할당에는 부적합
  • Unreal에서는 Tick 함수 내부의 임시 변수 등에 사용됨

Heap

  • 런타임에 크기나 생명 주기가 유동적인 객체 저장
  • new, delete 등을 통해 수동으로 메모리 관리
  • 유연하지만, 메모리 누수나 해제 타이밍 주의 필요
  • Unreal에서는 대부분의 게임 오브젝트(예: AActor, UObject)가 Heap에 생성됨
  • SpawnActor, NewObject 등을 사용하며, GC(가비지 컬렉션)으로 관리

 

꼬리 질문 1:

float DeltaTime의 경우 호출될때 어디에 저장되고 언제 해제 되는지?

 

⚠️ 내 대답: Stack에 저장되고 함수가 종료되면 해제됩니다.

 

✅ 기대 대답: 

 

float DeltaTime은 Tick(float DeltaTime) 함수의 매개변수로 전달되는 값입니다.
이 값은 함수 호출 시 생성되는 Stack 프레임 내에 저장됩니다.

Stack 프레임이란?
함수가 호출될 때 생성되는 임시 메모리 블록으로,
매개변수, 지역 변수, 리턴 주소 등의 정보를 저장합니다.
함수가 종료되면 해당 Stack 프레임은 사라지면서,
그 안의 변수들도 함께 자동으로 해제됩니다

 

따라서 DeltaTime은 매 프레임마다 Tick() 함수가 호출될 때
Stack 프레임에 저장되고, 함수가 끝나면 프레임과 함께 해제됩니다.

 

꼬리 질문 2:

delete는 누가해주나요?

 

내 대답: ... (머뭇)... 제가.. 해줍니다?

반응: 네ㅋㅋㅋ 개발자가 해줍니다.

 

꼬리 질문 3:

Heap과 관련하여 메모리 관리를 해주는 번거로움이 있다고 하셨는데, 스마트 포인터가 있습니다.

그 종류와 차이점에 대해 말씀해주세요.

 

⚠️ 내 대답:

스마트 포인터의 종류는 unique_ptr, shared_ptr, weak_ptr이 있다.
unique_ptr은 하나만 참조 가능, shared_ptr은 여러 참조 가능.
weak_ptr은 참조 카운트를 증가시키지 않아 순환 참조를 피할 수 있다.

 

➡️ 핵심 개념은 잘 언급했으나, 각 포인터의 사용 시점, 해제 타이밍, 구체적 차이에 대한 설명이 부족했음.

 

✅ 기대 대답:

종류 소유권 참조 카운트 해제 시점 용도
unique_ptr 단독 소유 X 포인터가 범위를 벗어나면 즉시 해제 리소스를 하나만 소유할 때
shared_ptr 공동 소유 O 참조 카운트가 0이 되면 해제 여러 개가 소유할 수 있을 때
weak_ptr 소유 X O (shared_ptr의 참조를 약하게 유지) 없음 (shared_ptr이 해제되면 무효화됨) 순환 참조 방지,
shared_ptr 감시 용도

 

꼬리 질문 4:

스마트 포인터를 이용하는 이유는 무엇인가요?

 

⚠️ 내 대답: 안전 코딩? 메모리 누수 방지?

반응: 하나만 말해주세요

✅ 내 대답: 메무리 누수 방지요..

반응: 네 맞습니다^^


질문 3. 해시 테이블이란 무엇이며, 충돌은 어떻게 해결할 수 있나요?

내 대답:

해시 테이블: 데이터를 "키(Key)와 값(Value)" 쌍으로 저장하는 자료구조

  • 빠르게 데이터를 저장하고 검색할 수 있다는 장점. 
  • Collision이 없다는 가정하, 배열과 다르게 선형 검색 X 시간복잡도: O(1)
  • 해시 테이블의 작동 원리:
    • 구조: 데이터 저장 자체는 배열에
    • Hash Function:특정 키를 받아 배열의 인덱스(해시값)를 반환
    • Key 입력 -> Hash Function -> 인덱스 반환 -> 배열 접근

Collision: 해시 Function은 여러 개의 키를 하나의 인덱스로 보낼 수 있기 때문에, 두 개 이상의 키가 같은 인덱스를 가지는 현상

해결 방법:

1. 체이닝(Chaining): 충돌이 발생한 인덱스에 리스트를 두고 값을 계속 추가하는 방식

2. 오픈 어드레싱(Open Addressing): 충돌이 발생하면 빈 다른 인덱스를 찾아서 저장

 

반응: "인덱스"보다 "해시값"이라는 표현을 쓰는 것이 더 적절함

 

꼬리 질문 1:

체이닝(Chaning) 방법을 사용하는 이점이 무엇인가요?

 

❌ 내 대답:

  • 충돌이 발생한 인덱스에 계속해서 값을 추가할 수 있다?

기대 대답:

  • 체이닝은 각 해시값에 연결 리스트(혹은 다른 구조, 예: 트리)를 두어 충돌된 데이터를 저장함
  • 장점: 해시 테이블의 크기보다 더 많은 데이터를 저장할 수 있으며, 삽입이 간단함
  • 연결 리스트의 이점과 연관: 연결 리스트 구조를 쓰면 동적으로 메모리를 사용할 수 있어 충돌이 여러 번 발생해도 유연하게 처리 가능함
  • 리사이징 필요성이 덜함

 

꼬리 질문 2:

좋은 해시 함수란 무엇을 의미할까요?

 

⚠️  내 대답:

  • Collision이 덜 일어나는 Function?

기대 대답:

  • 좋은 해시 함수란 해시값이 균등하게 분포되도록 만드는 함수
  • 충돌을 최소화
  • 계산이 너무 느리지 않도록 효율적이어야 함
  • 예시로는 DJB2, MurmurHash 등이 있음