基于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,在场景中心加载了我们需要的车辆模型