대규모 프로젝트에서는 자산의 규모가 매우 방대하고, 제작 팀도 매우 광범위하게 관여합니다. 씬, 캐릭터, UI, 애니메이션, 특수 효과, 블루프린트, 데이터 테이블 등 다양한 요소가 있으며, 이로 인해 자산의 양과 자산 표준 관리가 어려워집니다.
자산 규격을 정해도 아티스트가 100% 모든 상황을 커버하기 어렵고, 실수로 놓치는 경우가 있습니다. 대부분의 경우 패키지 내에서 문제를 발견한 후에야 처리하게 되며, 기존 자산의 경우 검토와 수정에 많은 인력이 소요됩니다.
이러한 문제점을 바탕으로, 저는 이전에 자산 스캔 도구를 개발했습니다. 이 도구는 프로젝트 내 자산을 스캔하기 위한 규칙을 쉽게 편집할 수 있습니다.
- UE에서 리플렉션을 활용한 자산 속성 캐시 구축
- UE에서 자산 자동 수정 설계 및 구현 방안
- UE에서 다단계 자동화 자산 검사 솔루션
- ResScannerUE 기반 자산 검사 자동화 실천
- UE 자산 규정 준수 검사 도구 ResScannerUE
최근에 플러그인을 전면적으로 업그레이드하여 편집 시 및 자동화 검사 기능을 강화했습니다. 이 글에서는 ResScannerUE를 활용하여 편집 시, 커밋 시, CI 정기 또는 Hook 작업, Cook 시 등 다양한 단계에서 자산 스캔을 구현하는 방법을 소개합니다. 이를 통해 잘못된 자산을 가능한 한 제작 단계에서 발견하고 해결하여 패키지 내 자산 이상을 방지할 수 있습니다.
플러그인 구현에 있어서도 스캔 속도를 위한 많은 최적화를 진행했으며, 검사가 가능한 한 느껴지지 않는 행동이 되도록 했습니다. 이에 대해 글에서 자세히 설명하겠습니다.
ResScanner 소개
이전에 이미 두 개의 글에서 기능을 소개했으므로, 여기서는 자세한 설명을 반복하지 않고 간단히 기능을 소개하겠습니다.
ResScannerUE는 UE의 자산 스캔 도구로, 규칙을 매우 쉽게 편집할 수 있으며, 순수 구성 및 블루프린트 스크립트 규칙을 지원합니다. 99%의 경우 C++ 코드를 작성할 필요가 없습니다. 경로 및 이름 지정 검사의 경우 실제로 자산을 로드할 필요가 없습니다.
경로 규칙, 이름 지정 규칙, 속성 규칙, 사용자 정의 규칙 네 가지 유형을 지원하며, 블루프린트나 C++를 사용하여 규칙을 확장할 수 있습니다. 또한 단일 규칙이 여러 조합 조건을 지원하고, 논리 연산(AND/OR)을 지원하여 복잡한 규칙에 매우 적합합니다.
속성 검사 규칙의 경우, UE의 리플렉션 및 Detail Customization 메커니즘을 기반으로 선택한 자산 유형의 모든 속성을 나열하고, 선택한 속성에 따라 해당 속성 유형의 위젯을 구성할 수 있습니다. 속성 검사는 마우스로 클릭하여 완료할 수 있습니다:
Git과 깊이 통합되어 Git 버전 관리 기반 스캔이 가능하며, 커밋 대기 중인 파일 및 커밋 기록 간의 차이점 스캔을 지원합니다.
완벽한 Commandlet 지원을 갖추고 있어 구성 및 명령줄 매개변수 형태로 CI/CD 시스템에 통합할 수 있습니다.
편집 시 규칙 스캔을 지원하여 오류를 초기 단계에서 방지합니다.
편집 시
대부분의 자산 문제는 일반적으로 다음과 같은 상황에서 발생합니다:
- 공식 자산 디렉토리에 임시 자산 생성
- 옵션 체크 또는 설정 누락
- 자산 규격 불일치
- 수정하면 안 되는 자산 커밋
이러한 상황은 팀 규모가 커질수록 거의 피할 수 없는 일입니다. 특히 5번의 경우, 자산은 바이너리 형식이라 diff를 할 수 없어 덮어쓰기 상황이 발생합니다. 문제가 발생하면 덮어쓴 자산을 롤백하거나 다시 제작해야 하므로 인력 낭비가 심합니다.
이러한 문제점을 바탕으로, ResScannerUE에 편집 시 실시간 알림 메커니즘을 추가했습니다. 자산을 저장하는 동시에 스캔을 실행하여 규정을 준수하지 않는 부분이 있으면 즉시 알려줍니다:
또한 현재 편집 중인 사람이 자산에 대한 수정 권한이 있는지 확인할 수 있습니다:
플러그인은 저장을 막지 않고 수정을 위한 안내만 제공합니다.
구현 측면에서는 UPackage::PackageSavedEvent를 모니터링하여 자산이 저장될 때 이 Delegate가 실행되어 어떤 자산이 수정되었는지 파악합니다:
UPackage::PackageSavedEvent.AddRaw(this,&FResScannerEditorModule::PackageSaved);
콜백에서 검사를 수행합니다:
voidFResScannerEditorModule::PackageSaved(const FString& PacStr,UObject* PackageSaved)
{
bool bEnable =GetDefault<UResScannerEditorSettings>()->bEnableEditorCheck;
if(bEnable)
{
static UResScannerEditorSettings* ResScannerEditorSettings =GetMutableDefault<UResScannerEditorSettings>();
ScannerProxy->SetScannerConfig(ResScannerEditorSettings->EditorScannerConfig);
FString PackageName =LongPackageNameToPackagePath(FPackageName::FilenameToLongPackageName(PacStr));
FSoftObjectPathObjectPath(PackageName);
FAssetData AssetData;
if(UAssetManager::Get().GetAssetDataForPath(ObjectPath,AssetData))
{
constauto& ScanResult = ScannerProxy->ScanAssets(TArray<FAssetData>{AssetData});
if(ScanResult.HasValidResult())
{
FString ScanResultStr = ScanResult.SerializeResult(true);
UE_LOG(LogResScannerEditor,Warning,TEXT("\\n%s"),*ScanResultStr);
FText DialogText = UKismetTextLibrary::Conv_StringToText(ScanResultStr);
FText DialogTitle = UKismetTextLibrary::Conv_StringToText(TEXT("ResScanner Message"));
FMessageDialog::Open(EAppMsgType::Ok, DialogText,&DialogTitle);
}
}
}
}
Expand
매번 하나의 자산만 검사하고, 저장된 자산은 이미 엔진에 로드되어 있으므로 스캔 속도가 매우 빠르며 거의 느껴지지 않습니다.
활성화 방법
Project Settings-Game-Res Scanner Settings 패널을 열고, bEnableEditorCheck를 활성화한 다음, FScannerMatchRule 유형의 DataTable을 새로 만들고 규칙 데이터 테이블에서 지정합니다.
이 DataTable의 각 항목은 개별 규칙입니다.
프로젝트 상황에 맞게 스캔 규칙을 편집할 수 있으며, 예를 들어 IMPORTANT 유형의 규칙만 스캔하거나, 블랙리스트 및 화이트리스트 방식으로 규칙을 지정할 수 있습니다.
커밋 시
자산 커밋 단계에서의 검사는 주로 버전 관리 소프트웨어에서 제공하는 HOOK 메커니즘을 활용합니다. Git의 Pre-Commit hook, Ugit의 훅 등이 있습니다. 기본적으로 커밋 시 스크립트를 실행하여 자산 스캔 기능을 시작하고, 규칙이 트리거되면 커밋을 금지합니다.
이 부분의 구현에 대해서는 ResScannerUE 기반 자산 검사 자동화 실천 글에서 자세히 설명했습니다.
CI/CD
자원 스캔을 CI/CD 시스템에 통합하여 정기적으로 또는 Hook이 트리거될 때 스캔을 실행할 수 있습니다:
플러그인의 Commandlet 메커니즘을 활용하여 내보낸 구성 파일, 명령줄 매개변수 대체 등의 방법으로 구현할 수 있습니다.
UE4Editor.exe Project.uproject -run="ResScanner" -config="Full_Scan_Config.json" -gitChecker.bGitCheck=true -gitchecker.beginCommitHash=HEAD~ -gitchecker.endCommitHash=HEAD
스캔이 완료된 후, 기업용 WeChat 봇 등을 통해 그룹 알림을 보내고 관련 커밋자에게 처리를 요청할 수 있습니다.
완전한 구현 스크립트를 제공했습니다. 자세한 내용은 다음을 참조하세요: 기업용 WeChat 알림
패키징 시
엔진의 모든 자원이 패키지에 완전히 포함되는 것은 아닙니다. 현재 패키지에 포함된 자원에 대해서만 규칙 검사를 분석하려면 플러그인에서도 지원을 제공합니다.
활성화하면 Cook 단계에서 자동으로 실행되며, Cook이 완료된 후 검사 보고서를 생성합니다. 기본적으로 Saved/ResScanner/Cooking.txt에 저장됩니다.
전체 구현은 비침습적이므로 플러그인을 통합하기만 하면 되며 엔진을 변경할 필요가 없습니다.
활성화 방법
Project Settings-Game-ResScanner Settings에서 bEnableCookingCheck를 활성화하고 Cook 시 스캔 규칙을 구성하기만 하면 됩니다:
스캔 최적화
자원 스캔은 기본적으로 UE의 UE4Editor-cmd 프로세스를 실행하여 Commandlet을 실행하는 것입니다. 엔진을 완전히 시작해야 하기 때문에 시간이 다소 민감합니다.
따라서 Commandlet 시작을 최적화하여 엔진 시작이 너무 오래 걸려 프로세스가 지연되는 것을 방지해야 합니다.
먼저 엔진 시작 시 각 모듈의 시작 시간, 즉 각 모듈의 StartupModule 시간을 분석해야 합니다. 엔진에는 기본적으로 모든 모듈을 분석할 수 있는 Profiling 태그가 추가되어 있지 않습니다. FModuleDescriptor::LoadModulesForPhase 및 FModuleManager::LoadModule에 Profiling 태그를 추가하여 확인할 수 있습니다:
Runtime\Projects\Private\ModuleDescriptor.cpp
voidFModuleDescriptor::LoadModulesForPhase(ELoadingPhase::Type LoadingPhase,const TArray<FModuleDescriptor>& Modules, TMap<FName, EModuleLoadResult>& ModuleLoadErrors)
{
FScopedSlowTaskSlowTask(Modules.Num());
for (int Idx =0; Idx < Modules.Num(); Idx++)
{
SlowTask.EnterProgressFrame(1);
const FModuleDescriptor& Descriptor = Modules[Idx];
// Don't need to do anything if this module is already loaded
if (!FModuleManager::Get().IsModuleLoaded(Descriptor.Name))
{
if (LoadingPhase == Descriptor.LoadingPhase && Descriptor.IsLoadedInCurrentConfiguration())
{
// @todo plugin: DLL search problems. Plugins that statically depend on other modules within this plugin may not be found? Need to test this.
//NOTE: Loading this module may cause other modules to become loaded, both in the engine or game, or other modules
// that are part of this project or plugin. That's totally fine.
FScopedNamedEventStaticLoadModuleEvent(FColor::Red,*Descriptor.Name.ToString());
EModuleLoadResult FailureReason;
IModuleInterface* ModuleInterface = FModuleManager::Get().LoadModuleWithFailureReason(Descriptor.Name, FailureReason);
if (ModuleInterface ==nullptr)
{
// The module failed to load. Note this in the ModuleLoadErrors list.
ModuleLoadErrors.Add(Descriptor.Name, FailureReason);
}
}
}
}
}
Expand
FModuleManager::LoadModule:
Runtime\Core\Private\Modules\ModuleManager.cpp
IModuleInterface*FModuleManager::LoadModule(const FName InModuleName )
{
FScopedNamedEventStaticLoadModuleEvent(FColor::Red,*InModuleName.ToString());
// We allow an already loaded module to be returned in other threads to simplify
// parallel processing scenarios but they must have been loaded from the main thread beforehand.
if(!IsInGameThread())
{
returnGetModule(InModuleName);
}
EModuleLoadResult FailureReason;
IModuleInterface* Result =LoadModuleWithFailureReason(InModuleName, FailureReason );
// This should return a valid pointer only if and only if the module is loaded
checkSlow((Result !=nullptr) ==IsModuleLoaded(InModuleName));
return Result;
}
AkAudio
프로젝트에 WWise 오디오 기능이 포함된 경우, AkAudio 모듈 시작은 매우 시간이 많이 소요됩니다(프로젝트의 자원 양에 따라 다름). 분석 결과, StartupModule에서 전체 프로젝트 Content 디렉토리의 파일을 스캔하고 AssetRegistry 데이터를 스캔하는 것으로 나타났습니다. 이 부분은 매우 시간이 많이 소요되며, 파일 스캔만으로도 수십 초가 걸립니다:
AkAudioModule.cpp
TArray<FString> paths;
IFileManager::Get().FindFilesRecursive(paths, *FPaths::ProjectContentDir(),TEXT("InitBank.uasset"),true,false);
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
AssetRegistryModule.Get().ScanFilesSynchronous(paths,false);
이 두 작업은 매우 시간이 많이 소요되지만 Commandlet에는 이 두 단계가 필요하지 않으므로 ::IsRunningCommandlet을 확인하여 비활성화할 수 있습니다.
주의: EBP 모드의 Wwise에서는 InitBank가 AssetRegistry에 존재하는지 확인해야 합니다. 그렇지 않으면 새로 하나 생성되어 팝업 메시지가 표시됩니다.이 문제를 해결하는 방법은 전체 스캔을 무시하면서 InitBank 자원에 대한 Registry 데이터만 생성하는 것입니다.
FString PackageFilename;
if(FPackageName::FindPackageFileWithoutExtension(FPackageName::LongPackageNameToFilename(TEXT("/Game/WwiseAudio/InitBank")), PackageFilename))
{
AssetRegistryModule.Get().ScanModifiedAssetFiles(TArray<FString>{PackageFilename});
}
LiveCoding
프로젝트에서 LiveCoding을 활성화한 경우 시작 시간이 상당히 깁니다:
그러나 Commandlet은 LiveCoding 관련 지원이 전혀 필요하지 않습니다. 직접 끄면 일상적인 개발에 영향을 줄 수 있습니다.
따라서 Commandlet 실행 시 LiveCoding을 동적으로 비활성화하는 방법을 구현했습니다. 이렇게 하면 EditorSetting에서 LiveCoding이 항상 활성화되어 있더라도 Commandlet 실행 시 비활성화할 수 있습니다.
방법은 우선순위가 높은 모듈(Default)의 StartupModule에서 GEditorPreProjectIni의 구성을 변경하는 것입니다:
if(IsRunningCommandlet())
{
GConfig->SetBool(TEXT("/Script/LiveCoding.LiveCodingSettings"),TEXT("bEnabled"),false,GEditorPerProjectIni);
}
이렇게 하면 LiveCoding 모듈이 시작될 때 비활성화된 것으로 인식하여 시간이 많이 소요되는 작업을 실행하지 않습니다.
Asset Registry
엔진 시작 시 AssetManager를 통해 PrimaryAsset을 스캔합니다. 캐시가 없는 경우 자원에서 AssetRegistry 데이터를 재구축하는 과정도 매우 시간이 많이 소요됩니다.
자원 스캔의 경우, 대부분의 상황(특히 로컬 실행 시)에서 완전한 AssetRegistry 데이터가 필요하지 않습니다. SearchAllAssets 및 ScanPrimaryAssetTypesFromConfig 스캔을 비활성화하고 자원 검사 시 필요에 따라 해당 자원의 AssetRegistry 데이터를 생성할 수 있습니다.
엔진을 수정할 필요 없이 UAssetManager 클래스를 상속받아 ScanPrimaryAssetTypesFromConfig 인터페이스를 재정의하기만 하면 됩니다:
// .h
UCLASS()
classHOTPATCHERRUNTIME_API UHotPatcherAssetManager :public UAssetManager
{
GENERATED_BODY()
public:
virtualvoidScanPrimaryAssetTypesFromConfig()override;
};
// .cpp
voidUHotPatcherAssetManager::ScanPrimaryAssetTypesFromConfig()
{
SCOPED_NAMED_EVENT_TEXT("ScanPrimaryAssetTypesFromConfig",FColor::Red);
staticconstbool bNoScanPrimaryAsset = FParse::Param(FCommandLine::Get(),TEXT("NoScanPrimaryAsset"));
if(bNoScanPrimaryAsset)
{
UE_LOG(LogHotPatcher,Display,TEXT("Skip ScanPrimaryAssetTypesFromConfig"));
return;
}
Super::ScanPrimaryAssetTypesFromConfig();
}
Commandlet 실행 시 -NoScanPrimaryAsset 매개변수를 통해 AssetRegistry 스캔을 비활성화할 수 있습니다. 또한 Commandlet 코드에서 SearchAllAssets 작업을 실행하지 않도록 하는 것이 좋습니다. 이 작업도 상당한 시간이 소요됩니다.
그러나 실행 중에 AssetRegistry 데이터에 액세스해야 할 경우에는 필요한 Package의 AssetRegistry 데이터를 대상으로 스캔해야 합니다. 플러그인에서 이미 지원하고 있습니다.
효과
최적화 후 엔진, 클라이언트, 콘텐츠를 모두 SSD에 저장한 경우 엔진의 전체 시작 시간을 약 20초로 줄일 수 있으며, 자원 스캔 과정은 20초 이내로 줄일 수 있어 커밋 시 거의 느껴지지 않습니다.
HDD에서는 엔진 시작 및 스캔 시간이 두 배 이상 느려지며, 이는 IO 병목 현상입니다. 모든 자원을 SSD에 저장하는 것이 좋습니다.
업데이트 로그
2023.03.30 업데이트
- 점진적 스캔 지원
- Cook 시 스캔 구현 최적화, Cook 시 각 자원의 개별 스캔으로 로딩 시간 감소
- 참조 관계 스캔 효율성 최적화, 종속성 관계 및 파생 클래스 캐시 추가**최적화 전최적화 후**
- 스캔 과정 최적화, 불필요한 규칙 실행 방지
- 스캔 결과 직렬화 단계 최적화, CookOnTheFly가 아닌 모드 지원
- Cook 시 Warning/Error 수준 로그 추적 방식 최적화 및 중복 제거
결론
이 글에서는 ResScannerUE를 활용하여 자원의 각 제출 단계에서 검사를 수행하는 구현 및 최적화 방법을 소개했습니다. 실제 개발의 대부분 상황을 다루며, 가능한 한 오류를 조기에 발견하고 해결할 수 있도록 합니다.
(Scripts/CopyBuildToStagingDirectory.Automation.cs):
이는 UAT가 UnrealPak을 실행하기 전에 정리 작업을 수행하고, 그 후 UnrealPak의 결과물을 깨끗한 StagedBuilds 디렉토리에 생성하는 방식입니다.
따라서 우리는 UnrealPak 실행 시점 또는 그 이후에 작업을 수행해야 합니다. 그렇다면 엔진을 수정하지 않고 어떻게 구현할 수 있을까요?
여기서는 UE 모듈 로딩에 대한 설명이 필요합니다.
UE의 Module은 여러 유형을 가질 수 있으며, 일반적으로 Runtime, Editor, Developer 등의 일상적인 개발 용도로 사용됩니다. 하지만 UE는 Program이라는 또 다른 유형을 제공하여 엔진의 특정 Program이 시작될 때 로드할 수 있게 합니다.
HotChunker의 .uplugin 예시를 살펴보겠습니다:
{
"FriendlyName":"HotChunker",
"CreatedBy":"lipengzha",
"CreatedByURL":"<https://imzlp.com/>",
"CanContainContent":false,
"SupportedPrograms":["UnrealPak"],
"Plugins":[
{"Name":"HotPatcher",
"Enabled":true
}
],
"Modules":[
{"Name":"HotChunker",
"Type":"Program",
"LoadingPhase":"PostConfigInit",
"ProgramAllowList":["UnrealPak"]
}
]
}
SupportedPrograms를 지정하여 해당 플러그인이 Program 시작 시 로드될 수 있음을 표시하고, Modules의 ProgramAllowList는 해당 모듈을 어떤 프로그램이 로드할 수 있는지 제어하는 데 사용됩니다.
이는 엔진을 수정하지 않고 UnrealPak이 우리 프로젝트의 특정 모듈을 로드하여 원하는 작업을 수행할 수 있게 해줍니다.
참고: 엔진이 이 메커니즘을 제공하는 본질적인 목적은 비침투적으로 압축 알고리즘을 확장할 수 있도록 하는 것이며, 이 글에서 소개하는 사용법은 합리적인 활용 방식입니다.
물론 Program 시작 과정에서 프로젝트 경로를 전달해야 하는데, UAT가 UnrealPak을 실행할 때 정확히 이렇게 전달합니다.
패키징 시 UAT가 UnrealPak을 실행하는 명령어:
E:\\UnrealProjects\\BlankExample\\BlankExample.uproject E:\\UnrealProjects\\BlankExample\\Saved\\StagedBuilds\\WindowsNoEditor\\BlankExample\\Content\\Paks\\BlankExample-WindowsNoEditor.pak -create=D:\\UnrealEngine\\Source\\UE_4.27.2\\Engine\\Programs\\AutomationTool\\Saved\\Logs\\PakList_BlankExample-WindowsNoEditor.txt -cryptokeys
=E:\\UnrealProjects\\BlankExample\\Saved\\Cooked\\WindowsNoEditor\\BlankExample\\Metadata\\Crypto.json -secondaryOrder=E:\\UnrealProjects\\BlankExample\\Build\\WindowsNoEditor\\FileOpenOrder\\CookerOpenOrder.log -patchpaddingalign=2048 -platform=Windows -compressionformats=Oodle -compressmethod=Kraken -compresslevel=3 -multiprocess -abslog=D:\\UnrealEngine\\Source\\UE_4.27.
2\\Engine\\Programs\\AutomationTool\\Saved\\Logs\\UnrealPak-BlankExample-WindowsNoEditor-2022.10.22-13.58.14.txt -compressionblocksize=256KB
이제 모듈의 StartupModule 또는 ShutdownModule에서 사용자 정의 작업을 수행할 수 있습니다:
voidFHotChunkerModule::StartupModule()
{
bool bIsRunningProgram = FPlatformProperties::IsProgram();
UE_LOG(LogHotChunker,Display,TEXT("HotChunker StartupModule,is Program %s"), bIsRunningProgram ?TEXT("TRUE"):TEXT("FALSE"));
}
주의: 런타임에 모듈이 Program으로 시작되었는지 확인하여 일반 엔진 시작인지 Program 시작인지 구분해야 합니다. FPlatformProperties::IsProgram()을 사용하여 확인할 수 있습니다.
이때 UAT를 통해 프로젝트를 패키징하면 UnrealPak이 실행될 때 프로젝트 모듈이 시작되고, UAT가
UnrealPak을 실행할 때 로그에서 다음을 확인할 수 있습니다:
LogHotChunker: Display: HotChunker StartupModule,is Program TRUE
이 모듈 내에서, CookCommandlet에서 패키징한 Pak 파일을 임시 디렉토리에서 실제 패키지에 포함될 StagedBuilds 디렉토리로 복사하면 됩니다.
범용 Pak 필터링 규칙
UE 핫 업데이트: 기본 패키지 분할 글에서, UE가 Android에 제공하는 Pak을 Obb에 포함시키지 않는 방법과 IOS에 대한 동등한 구현을 소개했습니다.
하지만 단점은 엔진을 수정해야 한다는 점으로, 비침투적이며 완전히 크로스 플랫폼인 범용 구현이 아닙니다.
이 글에서 소개한 분할 방식을 기반으로, UnrealPak이 HotChunker를 실행하여 Copy to StagedBuilds를 수행할 때 필터링하여 기본 패키지에 포함되지 않도록 할 수 있습니다.
하나의 설정만으로 모든 플랫폼에 범용적으로 적용되며, 와일드카드를 지원하여 제외 규칙을 쉽게 지정할 수 있습니다:
Chunk 설정
새로운 분할 방식은 UE의 PrimaryAssetLabel과 유사하게, HotPatcherPrimaryLabel 리소스를 생성하여 설정할 수 있으며, HotPatcher 플러그인의 Chunk 설정과 일치합니다. 디렉토리, 리소스, 의존성 분석 여부, 비리소스 파일 설정, 강제로 무시할 디렉토리 등을 유연하게 설정할 수 있습니다.
또한 프로젝트 설정에서 Chunk 패키징에 사용되는 Pak 구성 템플릿을 수정할 수 있어, HotPatcher의 설정을 완전히 재사용할 수 있습니다:
Pak 생성 이름 규칙 등을 제어할 수 있습니다.
에디터 지원
저는 HotPatcherPrimaryLabel 리소스에 우클릭 메뉴 지원을 추가했습니다:
현재 설정을 바로 패키징하거나 HotPatcher의 Chunk 설정에 추가할 수 있습니다.
또한 참조 뷰를 통해 현재 Pak에 포함된 리소스를 직접 확인할 수 있습니다:
패키지 내 Pak 미리보기
Android 예시로, 이 방식을 사용하여 특정 Pak을 패키지 내에 포함시킨 모습:
더 명확하고 유연한 장점이 있습니다.
결론
이 글에서는 UE 기본 패키지 분할의 단점과 HotPatcher를 활용한 비침투적 기본 패키지 분할 방식 및 구현에 대해 소개했습니다.
엔진의 숨겨진 여러 기능을 활용하여 Commandlet Hook과 Module for Program 방식으로 구현하여 엔진 수정을 피했습니다.
이 방식은 이미 완전히 구현되었으며, 향후 HotPatcher의 Mod로 배포될 예정입니다. 간단한 설정만으로 UE 기본 패키징을 실행하면 전체 과정을 구현할 수 있습니다.
'TECH.ART.FLOW.IO' 카테고리의 다른 글
[번역]UE Plugin and Tool Development: 기본 패키지 자동 (0) | 2025.09.17 |
---|---|
소프트웨어 개발 측면에서 “꼭 일어날 일은 반드시 일어난다”는 개념 (0) | 2025.09.17 |
[번역]UE Plugin and Tool Development: Commandlet (0) | 2025.09.15 |
n8n 자동화 시스템 구축 방안 (0) | 2025.09.15 |
Blender 애드온 개발 학습 순서 (0) | 2025.09.08 |