UE5热更新(Pak包的Cook,打包,加载,踩过的一些坑)

Posted Monkey X Luffy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UE5热更新(Pak包的Cook,打包,加载,踩过的一些坑)相关的知识,希望对你有一定的参考价值。

	这几天弄UE5的Pak包加载,弄得晕头转向,网上都是UE4.27以下的教程,UE4.27, UE5修改了一些东西,导致按照[虚幻官方直播第四期的教程](https://www.bilibili.com/video/BV1Ut411A7sk?spm_id_from=333.337.search-card.all.click&vd_source=f7b2defde4971310a19a6e9e40c36b90)无法成功加载,废话不多说,先说一下踩的坑。

一·UE5加载Pak和UE4不同的地方

1.取消Use Iostore(使用Io保存)的勾选

![在这里插入图片描述](https://img-blog.csdnimg.cn/15bd946face648ee80a2ad4cfc787f90.png)

UE5的打包设置中自动勾选了使用Io保存,还是试用功能,引擎的Mount函数中有对此的判断,

如果启用IO保存,会检测是否存在对应的.utoc文件,如果没有,返回false,Mount失败。
我们自己使用UE5的Cook,打包是不会生成.utoc文件的,导致一直加载失败。至于如何正确使用iostore之后有时间再看看。

2.取消共享材质着色器代码(Share Material Shader Code)勾选

UE5默认启动共享材质着色器代码,导致加载出来的Actor材质丢失,取消勾选可以解决问题。

3.蓝图调用C++路径名莫名其妙的多了一个空白的字符

这个可能大家没遇到,我遇到了,在调试的时候发现,FString类型的参数莫名其妙多了一个空白的字符,导致一直无法找到Pak包。教程使用命令行调用该函数不存在这个问题,我将参数在C++里写死了,规避了这个问题,以后有时间再看看。

主要踩了这三个坑,下面再说一下整个Pak的使用流程。

二、UE5 Pak包的Cook,打包,加载流程

1.cook

先创建DLC文件夹,有一个Actor,一个贴图,一个材质,一个Mesh,

在Actor里面添加测试代码,每秒打印一次“耶,我被成功加载了”

将DLC文件夹添加到要烘焙的额外资产目录

在平台中启用烘焙,这里要注意,UE4的烘焙按钮是在文件里面,UE5的烘焙按钮在平台

烘焙成功后,可以看到G:\\UE5Demo\\PakTest\\Saved\\Cooked\\Windows\\PakTest\\Content\\DLC里面有DLC的.uasset文件

2 打Pak包

找到引擎目录的UnrealPak.exe,教程说可以将它不依赖UE的库,可以将它移到任意地方运行,试了一下,不行。老老实实使用cmd运行,cd到该目录下,运行UnrealPak.exe。

然后输入命令unrealpak pak目录 -create=cook文件的目录 打pak包
unrealpak G:\\dlc.pak -create=G:\\UE5Demo\\PakTest\\Saved\\Cooked\\Windows\\PakTest\\Content\\DLC
这是普通的打pak的方式,加密或者压缩Pak包我就不演示。
输入命令unrealpak pak目录 -list 查看pak包
unrealpak G:\\dlc.pak -list

3. 加载Pak包

创建一个C++类继承自Actor,在工程build.cs里面添加模块"PakFile"

PublicDependencyModuleNames.AddRange(new string[] 
 "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "PakFile" );

在.h里声明OldPlatform和PakPlatform,这里不求甚解,不用理会到底是干嘛的,花大代价搞明白了估计也过几天就忘。
声明TestLoadPak函数,这里使用蓝图调用,也可以学习教程使用命令行调用,将UFUNCTION(BlueprintCallable)改为UFUNCTION(Exec)即可。

	TSharedPtr<class FPakPlatformFile> PakPlatform;
	class IPlatformFile* OldPlatform;
	UFUNCTION(BlueprintCallable)
	bool TestLoadPak(const FString& InPakFullPath);

在.cpp文件中引用头文件

#include "MyActor.h"
#include "IPlatformFilePak.h"
#include "HAL/PlatformFilemanager.h"
#include "Runtime/Engine/Classes/Engine/StreamableManager.h"
#include "Runtime/Engine/Classes/Engine/AssetManager.h"
#include "Runtime/Engine/Classes/Engine/StaticMeshActor.h"
#include "Kismet/KismetStringLibrary.h"

实现BeginPlay函数,初始化

	Super::BeginPlay();
	OldPlatform = &FPlatformFileManager::Get().GetPlatformFile();
	PakPlatform = MakeShareable(new FPakPlatformFile());
	PakPlatform->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), TEXT(""));

实现TestLoadPak函数

bool AMyActor::TestLoadPak(const FString& InPakFullPath)


	FPlatformFileManager::Get().SetPlatformFile(*PakPlatform.Get());

	FString PakFileFullPath = L"g:/dlc.pak";
	if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*PakFileFullPath))
		return false;
	//FString PakName = GetPakFileName(PakFileFullPath);

	TRefCountPtr<FPakFile> TmpPak = new FPakFile(PakPlatform.Get(), *PakFileFullPath, false);
	FString OldPakMountPoint = TmpPak->GetMountPoint();

	int32 ContentPos = OldPakMountPoint.Find("Content/");
	FString NewMountPath = OldPakMountPoint.RightChop(ContentPos);

	FString ProjectPath = FPaths::ProjectDir();
	//ProjectPath = "../../../PakTest/";
	NewMountPath = ProjectPath + NewMountPath;
	TmpPak->SetMountPoint(*NewMountPath);

	if (PakPlatform->Mount(*PakFileFullPath, 1, *NewMountPath))
	
		//StaticMesh'/Game/DLC/SM_Cube.SM_Cube_C'
		//Blueprint'/Game/DLC/DLC_Cube.DLC_Cube'
		//World'/Game/ThirdPerson/Maps/ThirdPersonMap.ThirdPersonMap'
		TArray<FString> FoundFilenames;
		TmpPak->FindFilesAtPath(FoundFilenames, *TmpPak->GetMountPoint(), true, false, false);
		if (FoundFilenames.Num() > 0)
		
			if (GetWorld()->WorldType == EWorldType::Game)
			
				for (FString& Filename : FoundFilenames)
				
					if (Filename.EndsWith(TEXT(".uasset")))
					
						FString NewFileName = Filename;
						FString PathDir = FPaths::ProjectContentDir();
						NewFileName.ReplaceInline(*PathDir, TEXT("/Game/"));
						FString File = FPaths::GetBaseFilename(Filename);
						NewFileName.ReplaceInline(TEXT("uasset"), *File);
						FString blueprint = TEXT("Blueprint'");
						NewFileName.Append(TEXT("_C'"));
						GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Red, *NewFileName);

						NewFileName=UKismetStringLibrary::Concat_StrStr(TEXT("Blueprint'"), NewFileName);
						UClass* Class = LoadClass<AActor>(NULL, *NewFileName);
						//UClass* Class = LoadClass<AActor>(NULL, TEXT("Blueprint'/Game/DLC/DLC_Cube.DLC_Cube_C'"));
						if (Class == nullptr)
						
							GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Red, TEXT("Load Class Error"));
						
						else
						
							AActor* MeshActor = GetWorld()->SpawnActor<AActor>(Class, FVector(100, 100, 400), FRotator(0, 0, 0));
						
					
				
				
		
	
	//设置回原来的读取方式,不然包内的资源可能访问不了
	FPlatformFileManager::Get().SetPlatformFile(*OldPlatform);
	return true;

这个例子指的注意的是有三个路径,
1.Pak包的路径-PakFileFullPath,我将Pak包的路径写死的原因是之前提到,蓝图传参的时候,莫名其妙多了一个空字符,导致路径一直不对。然后生成一个FPakFile对象,注意UE5将FPakFile的析构函数私有化了,不能使用共享智能指针。

TRefCountPtr<FPakFile> TmpPak = new FPakFile(PakPlatform.Get(), *PakFileFullPath, false);

2.挂载点的路径-NewMountPath,NewMountPath=“…/…/…/PakTest/Content/DLC”,要想办法拼对这个路径,具体需要在打包之后调试一下,查看各个路径的值,最终拼成上述值的结构"…/…/…/项目名/Content/DLC目录"。然后设置挂载点并挂载

TmpPak->SetMountPoint(*NewMountPath);
PakPlatform->Mount(*PakFileFullPath, 1, *NewMountPath)

3.Pak包资源的虚拟路径-NewFileName,我的是"Blueprint’/Game/DLC/DLC_Cube.DLC_Cube_C’",在我们烘培的时候,直接在UE5编辑器选择资源-》复制引用,后面加上”_C“,即可得到该路径。我们也是要把从Pak包里读出的文件名拼成这样的路径。最后通过LoadClass加载虚拟路径的资源,并生成,

UClass* Class = LoadClass<AActor>(NULL, *NewFileName);
AActor* MeshActor = GetWorld()->SpawnActor<AActor>(Class, FVector(100, 100, 400), FRotator(0, 0, 0));

最后派生一个Actor,调用该函数,放到场景中,将UE5编辑器的DLC文件夹删除,注意每次执行删除资源或者移动资源时,要选择内容右键修复文件夹中的重定向器,这样才能将引用,资源处理完成。

最后打包项目,运行

最后能看到左上角的打印,前方的带纹理的小方块,终于成功啦,普天同庆,完结撒花。

如果没有看到上述效果,可以将程序附加到VS的进程进行调试,查看各个路径对不对。

UE4 Pak打包挂载加载

首先,必须得明确的一点就是如果想要加载Pak内资源,那么这些资源必须是经过Cook的。如果打包的是未Cook的资源,那么即使Pak挂载成功,也不可能会成功加载Pak内资源。

不知道怎么生成Cook资源,可以看我前一篇 ​​​​​​UE4 Cook指定平台资源_来梦学长i的博客-CSDN博客

打包Pak

首先,将引擎目录下"Engine\\Binaries\\Win64"添加到系统环境变量中

方式一:使用命令行方式打包

打包Pak的命令行格式

UnrealPak “存储Pak的路径+Pak包名” -create="Cook好的资源的路径"

打开CMD键入打包命令,执行打包Pak

示例一:打包整个Content文件

UnrealPak "E:\\ProjectName\\Pak\\ContentPak.pak" -create="E:\\ProjectName\\Saved\\Cooked\\Content"

示例二:打包单个指定文件

UnrealPak "E:\\ProjectName\\Pak\\SiglePak.pak" -create="E:\\ProjectName\\Saved\\Cooked\\Content\\FileName\\FileName.uasset"

方式二:使用批处理方式打包

@ECHO OFF
ECHO ---------------------------------------------------------------
ECHO		UnrealPak Strart Create Pak
ECHO ---------------------------------------------------------------

set PakFilename="E:\\AndroidVR\\Pak\\ContentPak.pak"
set CookedFileName="E:\\AndroidVR\\Saved\\Cooked\\Newfile"
set UnrealPak="E:\\UE4.26\\Engine\\Binaries\\Win64\\UnrealPak.exe"
CALL %UnrealPak% %PakFilename%  -create=%CookedFileName%


ECHO ---------------------------------------------------------------
ECHO		UnrealPak Finished
ECHO ---------------------------------------------------------------
PAUSE

  打包完成,即可在你自己指定的目录下找到打包好的Pak文件

挂载Pak

如果手动将Pak放在以下这些项目路径中,引擎将会自动加载Pak

  • [ProjectName]/Content/Paks
  • [ProjectName]/Saved/Paks
  • Engine/Content/Paks

也可以使用以下方法,手动挂载Pak

挂载Pak的功能需要C++代码实现,项目Build.cs中加入"PakFile"模块

包含必要头文件

#include "HAL/PlatformFilemanager.h"
#include "IPlatformFilePak.h"

创建函数,用于获取平台文件

FPakPlatformFile* FSimpleHotPakModule::GetPakPlatformFile()

	if (!PakPlatformFile)
	
		if (IPlatformFile* InPlatformFile = FPlatformFileManager::Get().FindPlatformFile(TEXT("PakFile")))
		
			PakPlatformFile = static_cast<FPakPlatformFile*>(InPlatformFile);
		
		else
		
			PakPlatformFile = new FPakPlatformFile();
			if (PakPlatformFile->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), TEXT("")))
			
				FPlatformFileManager::Get().SetPlatformFile(*PakPlatformFile);
			
		
	

	return PakPlatformFile;

创建函数,用于判断是否已经挂载指定Pak,防止重复挂载

@PakFilename       Pak包名

bool USimpleHotPakBPLibrary::IsMounted(const FString& PakFilename)

	if (FPakPlatformFile* InPakFile = GetPakPlatformFile())
	
		TArray<FString> PakFilenames;
		//获取已经挂载的Pak数组容器
		InPakFile->GetMountedPakFilenames(PakFilenames);
		//判断其中是否已经包含Pak
		return PakFilenames.Contains(PakFilename);
	
	return false;

 创建函数,用于挂载Pak

@PakFilename       Pak包名
@PakOrder          Pak加载优先级
@MountPoint        Pak挂载到哪个路径

bool AHotUpdateTestCharacter::MountPak(const FString& PakFilename, int32 PakOrder, const FString& MountPoint)

	if (FPakPlatformFile *InPakFile = GetPakPlatformFile())
	
		if (!IsMounted(PakFilename))
		
            //返回挂载结果
			return InPakFile->Mount(*PakFilename, PakOrder, *MountPoint);
		
	
	return false;

创建函数,用于卸载Pak

@PakFilename       Pak包名

bool AHotUpdateTestCharacter::UnmountPak(const FString& PakFilename)

	if (FPakPlatformFile* InPakFile = GetPakPlatformFile())
	
		return InPakFile->Unmount(*PakFilename);
	

	return false;

方法都准备好了,开始挂载Pak

值得说明的是,MountPoint路径是你所打包的Cook资源的相对路径

例如打包了一个"ProjectName/Saved/Cooked/Content/Asseset"路径下的资源,那么这个资源的挂载点则是"ProjectName/Content/Asseset"

//Pak文件路径
FString PakFileName = FPaths::ProjectDir() / TEXT("Pak/Asset.pak");
//Pak挂载点
FString PakMountPoint = FPaths::ProjectContentDir() / TEXT("Asset/");

if(MountPak(*PakFileName,3,*PakMountPoint))

    

这样就已经将Pak挂载到引擎中了,接下来则是加载Pak中的资源

资源加载

动态加载资源

//Pak文件路径
FString PakFileName = FPaths::ProjectDir() / TEXT("Pak/Asset.pak");
//Pak挂载点
FString PakMountPoint = FPaths::ProjectContentDir() / TEXT("Asset/");

if(MountPak(*PakFileName,3,*PakMountPoint))

    FString AssetPath = "'Blueprint'/Gmae/Asseset/AssetName.AssetName_C'";
    if(AActor *TheActor = Cast<AActor>(StaticLoadObject(UObject::StaticClass(),nullptr,*AssetPath)))
    
        GetWorld()->SpawnActor(TheActor,FVector(),FRotator());
    


异步加载资源

//Pak文件路径
	FString PakFileName = FPaths::ProjectDir() / TEXT("Pak/Asset.pak");
	//Pak挂载点
	FString PakMountPoint = FPaths::ProjectContentDir() / TEXT("Asset/");

	if(MountPak(*PakFileName,3,*PakMountPoint))
	
		if (FPakPlatformFile*InPakPlatformFile = GetPakPlatformFile())
		
			FPakFile*InPakFile = new FPakFile(InPakPlatformFile,*PakFileName,false);
			
			TArray<FSoftObjectPath> AllReourcePath;
			TArray<FString> Files;
			InPakFile->FindFilesAtPath(Files,*InPakFile->GetMountPoint(),true,false,true);
			for (auto &InPak : Files)
			
				AllReourcePath.AddUnique(InPak);
			
			UAssetManager::GetStreamableManager().RequestAsyncLoad(AllReourcePath,FStreamableDelegate::CreateLambda([&]()
			
				FString FileName = "'Blueprint'/Gmae/Asseset/AssesetName.AssesetName'";
				AActor*TheActor = FindObject<AActor>(nullptr,*FileName);
			));
		
	

以上是关于UE5热更新(Pak包的Cook,打包,加载,踩过的一些坑)的主要内容,如果未能解决你的问题,请参考以下文章

UE5 热更新方案

UE5 热更新方案

小米手机assetbundle_0.pak是啥文件

ue5关卡序列怎么打开

unity打包AssetBundle包的几种压缩方式介绍

ue5打包效果很差