运行时读取PAK文件

运行时读取PAK文件

https://zhuanlan.zhihu.com/p/79209172

 

运行时读取PAK文件

运行时读取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的内容

因此需要设计一套流程,便于在发布以后的程序中测试,其中包含两个步骤:

  1. Cook内容资源(重点是要Cook所有内容,即使没有用上的也要Cook)
  2. 打包Cook好的内容为Pak
  3. 修改代码后发布新的可执行程序进行测试

下面对每个过程进行阐述:

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进行异步加载。

存在的疑问:

  1. Mount的路径是否只能为Engine?
  2. 除了转换为TSoftObjectPtr,是否有更便捷的使用资源方式?
  3. 是否可以使用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)

是所有其他资产,例如纹理,声音等。这些资产是根据主资产的使用自动加载的

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章