Bootstrap

UE5热更新加载pak资源

基于UE5.5,以一个完整案例总结一下UE打包后热更新加载pak资源的方法。

1、cook生成包含目标资源的pak包

在这个案例里我们首先生成一个包含目标资源的pak包,包含我们需要的目标资源(贴图、材质、模型、蓝图、关卡...),供我们的主工程在打包后可以挂载该pak,并在运行时加载pak内的资源。

这里我们的目标资源是一辆车辆的static mesh模型以及相应的材质和贴图资源。我们新建一个DLC工程,并把车辆资产放在Content/Norway/Meshes目录下。

接着我们cook该资源,点击Platforms->Windows->Cook Content, 我们可以自动cook工程content目录下所有资源(对于plugins的内容我们可以把要cook的路径加入Additional Asset Directories to Cook)

cook结束后资源会出现在Saved\Cooked路径下

有了cook过的资源后,我们用脚本将cook后的资源打包为Pak文件,在引擎目录下找到UnrealPak.exe所在地址,cmd打开命令行,输入打包命令

unrealpak D:\pak的目标路径 -create=cook的资源路径

顺利生成的话我们会在目标路径下找到生成的pak文件

接着我们可以用unrealpak a1.pak -list命令检查一下生成的pak内容,里面应该包含我们需要的资产信息,没有的话说明pak没有打包成功

上面显示了我们车辆模型的asset已在pak中

2、运行时挂载Pak并导入需要的资源

这里我们在项目里新建一个UDLCLoadSubsystem继承WorldSubsystem用于加载pak资源,代码如下:

DLCLoadSubsystem.h

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"
#include "DLCLoadSubsystem.generated.h"

/**
 * 
 */
UCLASS()
class DLCLOADER_API UDLCLoadSubsystem : public UWorldSubsystem
{
	GENERATED_BODY()
	
public:
	UFUNCTION(BlueprintCallable, Category = "DLCLoadSubsystem")
	static UDLCLoadSubsystem* Get(const UWorld* InWorld);

	virtual bool DoesSupportWorldType(EWorldType::Type WorldType) const override;
	 
	UFUNCTION(BlueprintCallable, Category = "DLCLoadSubsystem")
	bool ImportMeshAsset(const FString& PakPath, FString TargetFile, FVector Location);
	
	
};

DLCLoadSubsystem.cpp

#include "DLCLoadSubsystem.h"
#include "IPlatformFilePak.h"
#include "Engine/LevelStreamingDynamic.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetStringLibrary.h"
#include "Engine/StaticMeshActor.h"



UDLCLoadSubsystem* UDLCLoadSubsystem::Get(const UWorld* InWorld)
{
	if (InWorld)
	{
		return InWorld->GetSubsystem<UDLCLoadSubsystem>();
	}
	return nullptr;
}

bool UDLCLoadSubsystem::DoesSupportWorldType(EWorldType::Type WorldType) const
{
	return WorldType == EWorldType::Game || WorldType == EWorldType::Editor || WorldType == EWorldType::PIE;
}
 
bool UDLCLoadSubsystem::ImportMeshAsset(const FString& PakPath,FString TargetFile, FVector Location)
{ 
	
	IPlatformFile* PakPlatformFile = &FPlatformFileManager::Get().GetPlatformFile();

	FPakPlatformFile* PakPlatform = static_cast<FPakPlatformFile*>(FPlatformFileManager::Get().FindPlatformFile(FPakPlatformFile::GetTypeName()));
	if (!PakPlatform)
	{
		PakPlatform = static_cast<FPakPlatformFile*>(FPlatformFileManager::Get().GetPlatformFile(FPakPlatformFile::GetTypeName()));
		PakPlatform->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), TEXT(""));
	} 

	if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*PakPath))
		return false;

	TRefCountPtr<FPakFile> TmpPak = new FPakFile(PakPlatformFile, *PakPath, false);
	const FString MountPoint = TmpPak->GetMountPoint(); 
	FString MountPath = MountPoint.RightChop(MountPoint.Find("Content/"));
	MountPath = FPaths::Combine(FPaths::ProjectDir(), MountPath);
	TmpPak->SetMountPoint(*MountPath);

	if (PakPlatform->Mount(*PakPath, 1, *MountPath))
	{
		TArray<FString> FoundFilenames;
		//最后一位bRecursive选择是否递归查询,这里选true
		TmpPak->FindPrunedFilesAtPath(FoundFilenames, *TmpPak->GetMountPoint(), true, false, true);
		if (FoundFilenames.Num() > 0)
		{
			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);
					//在场景中加载包含我们目标资源名称的asset
					if (NewFileName.Contains(TargetFile))
					{
						UObject* LoadedObj = StaticLoadObject(UObject::StaticClass(), NULL, *NewFileName);
						//我们以StaticMesh资源为例,这里还可以cast为材质、蓝图资产获取其他资源
						UStaticMesh* Mesh = Cast<UStaticMesh>(LoadedObj);
						if (Mesh)
						{
							AStaticMeshActor* StaticMeshActor = GetWorld()->SpawnActor<AStaticMeshActor>();
							if (StaticMeshActor)
							{
								StaticMeshActor->SetMobility(EComponentMobility::Movable);
								StaticMeshActor->GetStaticMeshComponent()->SetStaticMesh(Mesh);
								StaticMeshActor->SetActorLocation(Location);
								return true;
							}
								
						}
					}
				}
			}
		}
	}	 
	return false;
}

在蓝图中调用ImportMeshAsset,将模型资源加载在目标位置

最后我们打包项目进行测试,这里需要勾选use Pak File,不勾选Use Io Store

打包点击button,在场景中心加载了我们需要的车辆模型