運行時讀取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)

是所有其他資產,例如紋理,聲音等。這些資產是根據主資產的使用自動加載的

 

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