运行时加载资产的问题
Unreal运行时加载资产有两个问题:
第一,Unreal不会自动加载任何额外的pak文件。
第二,一旦加载了一个pak, 它不会将pak文件中的任何资产添加到内存中的AssetRegistry ..所以即使加载了pak,你也无法访问它的内容
而这段代码中的ScanForModPlugins()函数处理了这两个问题:
https://github.com/smogworks/ModSkeleton/blob/master/Source/ModSkeleton/ModSkeletonRegistry.cppgithub.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)
是所有其他资产,例如纹理,声音等。这些资产是根据主资产的使用自动加载的