运行时读取PAK文件
https://zhuanlan.zhihu.com/p/79209172
运行时读取PAK文件
安宁
游戏/仿真开发,UE4用户
运行时加载资产的问题
Unreal运行时加载资产有两个问题:
第一,Unreal不会自动加载任何额外的pak文件。
第二,一旦加载了一个pak, 它不会将pak文件中的任何资产添加到内存中的AssetRegistry ..所以即使加载了pak,你也无法访问它的内容
而这段代码中的ScanForModPlugins()函数处理了这两个问题:
https://github.com/smogworks/ModSkeleton/blob/master/Source/ModSkeleton/ModSkeletonRegistry.cpp
github.com
开发和测试流程
两个非常重要的限制:
在PIE模式中加载Pak文件时,只能加载未Cook的内容
在发布以后的程序中加载Pak文件时,只能加载已Cook的内容
因此需要设计一套流程,便于在发布以后的程序中测试,其中包含两个步骤:
Cook内容资源(重点是要Cook所有内容,即使没有用上的也要Cook)打包Cook好的内容为Pak修改代码后发布新的可执行程序进行测试
下面对每个过程进行阐述:
Cook 内容资源
打开内容所在的工程,打开Project Launcher,创建一个新的Profile,仅选择Cook: By the book , 其余Build, Package, Archive, Deploy, Launch都不要。然后运行该Profile即可达到Cook的效果。
一个让以后Cook更简单的办法是,在运行上述Profile以后,复制其命令内容:
粘贴到一个文本文件中,去掉BuildCookRun之前的内容,复制到剪贴板中;
然后搜索到RunUAT.bat文件,找到其路径,cmd定位到该目录下,然后输入 RunUAT.bat 然后将剪贴板中的内容粘贴到该命令后面,即可直接Cook内容。
Cook以后的内容默认情况下位于
工程目录/Saved/Cooked/<Platform Folder>/
打包Pak
搜索UnrealPak.exe文件,CMD定位到该目录下,输入:
UnrealPak.exe Pak文件的完整路径和名称 -create=要打包的文件夹路径
注意-create=后面也可以跟一个文本文件,列出所有需要打包的具体uasset等文件的全名。
发布可执行程序
和Cook内容资源过程相似,区别在于这次需要勾选上Build和Package,并合适地设置。
挂载(Mount)Pak并异步加载内容
代码:
.h:
//pak文件中的文件路径列表 TArray<FSoftObjectPath> ObjectPaths; TArray<TSoftObjectPtr<UObject>> ObjectPtrs;
.cpp:
void APakMount::MountPak(){ //第一步 //FPlatformFileManager::Get()返回单例 //GetPlatformFile()返回相应平台的PlatformFile,即处理相应平台文件读写的对象 //因此在Windows平台,这里返回的是FWindowsPlatformFile的实例 IPlatformFile& InnerPlatform = FPlatformFileManager::Get().GetPlatformFile(); //第二步 //这里创建了一个FPakPlatformFile,但是未指定当前使用什么平台去读写这个文件 FPakPlatformFile* PakPlatformFile = new FPakPlatformFile(); //第三步 //使用相应平台的PlatformFile去初始化PakPlatformFile //第二个参数是命令行参数,一般都为空 PakPlatformFile->Initialize(&InnerPlatform, TEXT(“”)); //第四步 //再将当前PlatformFile设置为”相应平台下pak文件读写”的模式 FPlatformFileManager::Get().SetPlatformFile(*PakPlatformFile); const FString PakFileFullName = TEXT(“D:\\ExamplePak.pak”); //测试用MountPoint FString MountPoint(FPaths::EngineContentDir()); //创建FPakFile对象,同样使用相应平台的PlatformFile初始化 //第二个参数是pak文件的完整路径+名称 //第三个参数是是否有符号? FPakFile* Pak = new FPakFile(&InnerPlatform, *PakFileFullName, false); if (Pak->IsValid()) { //具体Mount方法可以参考函数 FPakPlatformFile::Mount //但是其中有大量多余内容(例如版本编号处理) //Pak->SetMountPoint(*MountPoint); PakPlatformFile->Mount(*PakFileFullName,1000,*MountPoint); TArray<FString> Files; Pak->FindFilesAtPath(Files, *(Pak->GetMountPoint()), true, false, true); for(auto File : Files) { FString Filename, FileExtn; int32 LastSlashIndex; File.FindLastChar(*TEXT(“/”), LastSlashIndex); FString FileOnly = File.RightChop(LastSlashIndex + 1); FileOnly.Split(TEXT(“.”), &Filename, &FileExtn); if (FileExtn == TEXT(“uasset”)) { File = FileOnly.Replace(TEXT(“uasset”), *Filename); File = TEXT(“/Engine/”)+File; ObjectPaths.AddUnique(FSoftObjectPath(File)); //将FSoftObjectPath直接转换为TSoftObjectPtr<UObject>并储存 ObjectPtrs.AddUnique(TSoftObjectPtr<UObject>(ObjectPaths[ObjectPaths.Num()-1])); } } MyGI->GetStreamableManager().RequestAsyncLoad(ObjectPaths,FStreamableDelegate::CreateUObject(this,&APakMount::CreateAllChildren)); }}
挂载的核心方法是 PakPlatformFile->Mount.
挂载之后我们无法直接使用,还需要对其中的资源进行列举,并转换成可以直接加载的格式(包括去掉.uasset后缀名,以及前面加/Engine/)
然后将其存储到FSoftObjectPath数组中,并顺便转换为TSoftObjectPtr并储存到一个数组中,以便后期进行访问。
最后使用StreamableManager进行异步加载。
存在的疑问:
Mount的路径是否只能为Engine?除了转换为TSoftObjectPtr,是否有更便捷的使用资源方式?是否可以使用PrimaryAssetType:PrimaryAssetName这种格式代替传统资源路径格式?使用动态加载的内容
在此例中已知pak中储存的内容均为staticmesh,因此可以直接这样处理,生成新的StaticMeshComponent组件。
存在的疑问:
如果不知道类型,如何判断加载的资源类型?
void APakMount::CreateAllChildren(){ UE_LOG(LogTemp,Log,TEXT(“finished loading assets”)); for (int32 i = 0; i < ObjectPtrs.Num(); ++i) { UStaticMeshComponent* NewComp = NewObject<UStaticMeshComponent>(this); if (!NewComp) { return; } UStaticMesh* staticMesh = Cast<UStaticMesh>(ObjectPtrs[i].Get()); NewComp->SetStaticMesh(staticMesh); NewComp->AttachToComponent(GetRootComponent(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true)); NewComp->SetRelativeLocation(FVector(0, (i + 1) * 100.0f, 0)); NewComp->RegisterComponent(); }} 几个相关概念/类
资产注册表(Asset Registry)
是特定资产的信息存储库,是在保存package时提取的
AssetManager
是一个单例UObject,它提供在运行时扫描和加载主资源的操作。它旨在替换ObjectLibraries当前提供的功能,并包装FStreamableManager以处理实际的异步加载。引擎资产管理器提供基本管理,但更复杂的事情(如缓存)可以由特定于游戏的子类实现。
主要资产(Primary Assets)
是可以根据游戏状态的变化手动加载/卸载的资产。这包括地图和游戏特定对象,例如库存物品或角色类。
辅助资产(Secondary Assets)
是所有其他资产,例如纹理,声音等。这些资产是根据主资产的使用自动加载的