언리얼 오브젝트 관리 Ⅱ - 패키지
언리얼 오브젝트 패키지
- UObject를 파일로 저장하면 UAsset, UAsset들을 하나로 모으면 UPackage
패키지의 중의적 개념
- 언리얼 엔진은 다양한 곳에서 패키지란 단어를 사용하고 있음
- 언리얼 오브젝트를 감싼 포장 오브젝트
- 개발된 최종 콘텐츠를 정리해 프로그램으로 만드는 작업(게임 패키징)
- DLC같은 확장 콘텐츠에 사용되는 데이터 묶음
패키지와 애셋
- 기본적으로 언리얼 오브젝트는 반드시 패키지에 소속돼있음
- 특정 패키지를 선택하지 않으면 임시 패키지에 소속됨(Transient Package)
- 구조상 패키지는 다수의 언리얼 오브젝트를 소유할 수 있으나 일반적으로는 하나의 애셋만 가짐
NewObject의 비밀
T* NewObject(UObject* Outer = (UObject*)GetTransientPackage())- 기본값으로 TransientPackage로 끼워줌
- TransientPackage는 타고 들어가보면 UObject임
패키지 저장 방식:
void UMyGameInstance::SaveStudentPackage() const{ UPackage* StudentPackage = CreatePackage(*PackageName); // 패키지에 사용할 플래그. Public | Standalone이 가장 일반적 조합 EObjectFlags ObjectFlag = RF_Public | RF_Standalone; UStudent* Student = NewObject<UStudent>( StudentPackage, // UObject 생성 시에 Package와 연결함 UStudent::StaticClass(), *AssetName, ObjectFlag ); Student->SetName(TEXT("곽민규")); Student->SetOrder(19); FString PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension()); // FString PackageFileName2 = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Content"), FString::Printf(TEXT("%s%s"), *PackageName, *FPackageName::GetAssetPackageExtension())); FPaths::MakeStandardFilename(PackageFileName); FSavePackageArgs SaveArgs; SaveArgs.TopLevelFlags = ObjectFlag; if (UPackage::SavePackage( StudentPackage, nullptr, // asset은 이미 package에 연결돼있으므로 지정 필요없음 *PackageFileName, SaveArgs )) { UE_LOG(LogTemp, Log, TEXT("패키지가 성공적으로 저장되었습니다.")); }}UPROPERTY() 옵션
- ~DefaultsOnly: 애셋을 생성하면 메모리에 올라가기 전 파일 상태의 기본값을 설정할 수 있음
- ~InstanceOnly: 실제 객체에 포함돼 메모리에 올라갔을 때를 가리킴
패키지 로드 방식:
void UMyGameInstance::LoadStudentPackage() const{ UPackage* StudentPackage = LoadPackage(nullptr, *PackageName, LOAD_None); if (!StudentPackage) { UE_LOG(LogTemp, Warning, TEXT("패키지를 찾을 수 없습니다.")); return; } // 패키지 규모가 큰 경우 완전히 로드 보장 StudentPackage->FullyLoad(); UStudent* Student = FindObject<UStudent>(StudentPackage, *AssetName); if (Student) { PrintStudentInfo(TEXT("FindObject Result"), Student); }}서브 오브젝트 추가
// 서브 오브젝트 추가const int32 SubObjectCount = 10;for (int32 i = 1; i <= SubObjectCount; ++i){ FString SubObjectName = FString::Printf(TEXT("Student%d"), i); UStudent* SubStudent = NewObject<UStudent> ( Student, UStudent::StaticClass(), *SubObjectName, ObjectFlag ); SubStudent->SetName(FString::Printf(TEXT("학생%d"), i)); SubStudent->SetOrder(i);}- 구조: 패키지 → 애셋 → 서브오브젝트
저장하고 uasset 파일을 열어보면 서브오브젝트가 저장돼있음

애셋 정보 저장과 로딩 전략
- 게임 제작 단계에서 애셋 간 연결 작업을 위해 직접 패키지를 불러 할당하는 작업은 부하가 큼
- 애셋 로딩 대신 오브젝트 경로(
FName, 해시테이블)로만 에디터에서 사용 - 오브젝트 경로는 유일함(같은 경로 내 같은 파일명 불가능하므로)
로딩 전략
- 에디터에서 애셋을 건드릴 때마다 로드할 필요는 없음
- 프로젝트에서 처음부터 애셋이 반드시 필요한 경우: 생성자 코드에서 미리 로딩(강참조)
- 런타임에서 필요한 때 바로 로딩하는 경우: 런타임 로직에서 정적 로딩
- 런타임에서 비동기적으로 로딩하는 경우: 런타임 로직에서 관리자를 사용해 비동기 로딩
강참조
UPROPERTY()TObjectPtr<UTexture2D> HardRefTexture; // 또는 UTexture2D*UPROPERTY()TSubclassOf<AActor> HardRefClass;특징:
- 즉시 로드: 해당 객체를 참조하는 순간 자동으로 메모리에 로드됩니다
- 패키지 의존성: 컴파일 타임에 의존성이 생성되어, 참조하는 애셋이 항상 함께 로드됩니다
- 메모리 상주: 참조가 끊기지 않는 한 메모리에 계속 남아있습니다
- 빠른 접근: 이미 메모리에 있으므로 즉시 사용 가능합니다 문제점:
- 불필요한 애셋까지 로드되어 메모리 낭비 발생
- 로딩 시간이 길어질 수 있음
- 대용량 게임에서는 성능 저하 원인
약참조
UPROPERTY()TSoftObjectPtr<UTexture2D> SoftRefTexture;UPROPERTY()TSoftClassPtr<AActor> SoftRefClass;특징:
- 지연 로드: 참조만 가지고 있고, 실제 필요할 때만 로드합니다
- 경로만 저장: FSoftObjectPath로 애셋 경로만 저장 (약 수십 바이트)
- 명시적 로드 필요: LoadSynchronous() 또는 비동기 로드 필요
- 메모리 효율적: 필요한 것만 로드하여 메모리 절약
|상황|사용할 참조| |항상 필요한 핵심 애셋 (플레이어 캐릭터 등)|강참조| |가끔 사용되는 애셋 (특정 무기, 스킬 이펙트)|약참조| |대용량 애셋 (시네마틱, 보스 몬스터)|약참조| |레벨별로 다른 애셋|약참조| |UI에서 선택 가능한 수백 개의 아이템|약참조|
애셋 스트리밍 관리자(Streamable Manager)
- 애셋 비동기 로딩 지원하는 관리자 객체
- 콘텐츠 제작과 무관한 싱글톤 클래스에 FStreamableManager 선언하기(GameInstance도 좋은 선택)
- FStreamableManager로 애셋의 동기/비동기 로딩 관리.
- 다수의 오브젝트 경로를 입력해 한꺼번에 로딩도 가능
언리얼 빌드 시스템
에디터 구성
- 게임 제작을 위해 에픽 게임즈가 제공하는 저작도구
- 에디터: 게임제작을 위해 제공되는 응용 프로그램
- 게임빌드: EXE 파일과 리소스로 이루어진 독립적으로 동작하는 게임 클라이언트
- 에디터에서 기획과 개발을 완료한 후, 게임 빌드를 통해 최종 게임 결과물을 패키징함
에디터의 동작
.uproject확장자 실행하면 에디터 트리거.uproject확장자는 윈도우 레지스트리에 등록돼있음(등록 안돼있으면 런처 실행해 등록)- UnrealVersionSelector 프로그램으로 프로젝트 정보가 넘겨짐
- UnrealVersionSelect는 런처가 저장해놓은 에디터 정보에 적합한 에디터 실행
에디터 버전 정보
{프로젝트}.uproject텍스트 파일에 기록돼있음.uproject확장자는 에디터를 띄우기 위한 명세서 역할- JSON 형식임
ProgramData/Epic/UnrealLauncher폴더에 에픽 런처가 지정한 정보가 있음(JSON)

언리얼 C++ 모듈
- 언리얼 엔진의 소스코드는 모두 모듈(Module) 단위로 구성돼있음
- 모듈을 컴파일함으로서 에디터 및 게임에 우리가 제작한 로직을 공급함
- C++ 소스코드 컴파일하면
- 에디터용 DLL(동적 라이브러리, 장점: 이식성, 단점: 메모리 공간 이동으로 불안정)
- 게임용 정적 라이브러리(최종 빌드에 포함)
- 에디터용 모듈은 언제나
UnrealEditor-{모듈이름}.dll규칙을 준수함

직접 모듈 추가
- 기본 언리얼 모듈에 자체제작 C++ 모듈을 추가하려면 DLL을 빌드 폴더에 넣어줘야 함
모듈 C++ 코드 관리
- 언리얼 소스코드는 특정 플랫폼에 종속적이지 않음
- UnrealBuildTool C#프로그램에 의해 플랫폼에 맞게 컴파일됨

Source 폴더 구조
/Source- 타겟 설정 파일- {프로젝트이름} - 모듈 설정 파일 - 소스 코드 파일타겟 설정 파일: 전체 솔루션이 다룰 빌드 대상 지정
{프로젝트이름}.Target.cs: 게임 빌드 설정{프로젝트이름}Editor.Target.cs: 에디터 빌드 설정
모듈 설정 파일: 모듈 빌드용 C++ 프로젝트 설정 정보
{모듈이름}.Build.cs: 모듈 빌드 환경 설정
C# 특성상(JIT 컴파일) 런타임에 cs 파일을 읽어 빌드 환경 구축하고 컴파일함
- Windows, Android만 JIT됨. MacOS, iOS는 AOT만 됨(유니티 고통)
모듈 구성요소
- 소스코드(
.h,.cpp) - 모듈의 뼈대 - 매크로
IMPLEMENT_MODULE: 일반 모듈(= 플러그인)IMPLEMENT_GAME_MODULE: 게임모듈IMPLEMENT_PRIMARY_GAME_MODULE: 주 게임모듈(게임에 반드시 하나는 있어야 함)
모듈간 종속 관계
- 모든 모듈들이 GameModule에 포함되고, GameModule을 언리얼이 가져가서 exe로 만듦

모듈의 공개와 참조
- 필요한 만큼만 공개해야 의존성도 줄어들고 컴파일 시간도 최소화됨
- 예전 언리얼 엔진은
/Classes폴더가 있어 Public 폴더 역할을 하면서 언리얼 오브젝트를 관리했음. - 지금은 공개 파일은 모두
/Public로 - 숨길 파일은 모두
/Private로 - 외부로 공개할 클래스 선언엔
{모듈이름}_API매크로 붙이기(__declspec(dllexport)) - 각 모듈에서
.Build.cs설정으로 참조할 모듈 설정할 수 있음
플러그인 시스템
- 모듈을 분리시키는 방법 중 하나
- 플러그인은 결국 상호 독립적인 모듈임
- 에디터 설정 > 플러그인 창에서 빌트인이나 자체제작 플러그인 유연하게 추가삭제 가능
구조
.uplugin:.uproject와 동일한 개념/Resources: 아이콘 등 리소스/Source/{플러그인이름}/: 모듈 소스코드(Public/Private), 의존성 설정(.Build.cs)
게임 빌드
- 게임 타겟 설정(
{프로젝트이름}.Target.cs)을 추가하면 게임 빌드 옵션(Shipping)이 추가됨 - 게임 타겟으로 빌드된 모듈은 정적 라이브러리로 exe에 포함됨
- 게임이 실행되기 위해선 실행 파일 + 콘텐츠 애셋이 필요함.
- 빌드: 실행 파일을 생성하기 위한 컴파일
- 쿠킹: 지정한 플랫폼에 맞는 콘텐츠 애셋 변환 작업
- 패키징: 실행 파일과 콘텐츠 소스를 모아 하나의 프로그램으로 만드는 작업
쿠킹 단계가 빠지면 아래와 같이 exe 실행이 안됨

캡쳐와 같이 Shipping 옵션으로 Package Project하면 지정된 경로에 실행파일이 빌드됨
- 쿠킹 단계만 미리 해놓으면 콘텐츠 소스 변경이 없다는 가정 하에 패키징 시간이 줄어듦
