ue4热更新
UE4的热更新,目的就是更新Pak包,生成Pak包的方法网上很多,根据需求看使用UE4自带的(搜索DLC),还是自己根据自己的规则打pak都是可以的(搜索UnrealPak.exe)
可以通过HotPatcher插件来生成pak文件,地址:https://github.com/hxhb/HotPatcher
ue4热更新主要还是和unity一样,可以打单独的包,但是加载其中的资源是要通过路径加载的,不像unity是直接可以拿到对象,
而ue4只能通过mount挂载pak,但是拿不到对象,pak挂载完成之后,就可以通过此资源在ue4下content下得路径来加载
另外一点,pak内置路径是包含项目名称得,所以如果想从A项目资源打包给B项目用,那么A项目得名称一定要和B项目一样,不然没法加载pak,具体有没有什么解决方案,待研究!
Mount是读取pak文件中的文件信息,就是获取pak文件中有哪些文件,以便后续查找文件的时候,确定pak中是否有指定的文件。
pak解包方案:
1、https://zhuanlan.zhihu.com/p/163501071?utm_source=wechat_session
2、使用命令:unrealPak.exe need.pak -extract toFloder
pak相关资料:
https://www.cnblogs.com/bodboy/p/6110528.html
https://www.dazhuanlan.com/2019/12/08/5dec71c68a74a/
https://zhuanlan.zhihu.com/p/34617967
https://blog.ch-wind.com/unrealpak-note/
https://github.com/hxhb/HotPatcher
热更新资料:
https://bajiaobujie.github.io/categories/UE4/
https://blog.csdn.net/liulong1567/article/details/71597892/
打pak的命令:
UnrealPak.exe D:\UE4\ueProjects\TestPak\TestHotPatcherRes\Paks\1.pak -create=D:\UE4\ueProjects\TestPak\TestHotPatcherRes\Paks\1.0\Android_ASTC\1.0_Android_ASTC_PakCommands.txt -platform=Android -compress -utf8output -multiprocess -encrypt -encryptindex -aes=11111111112222222222333333333344
对此pak进行加密:-aes=后面跟着32位密钥,代码中mount此pak前,需要解密,代码如InitEncrypt方法
"D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/bike.uasset" "../../../TestDLC/Content/model/bike.uasset" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/bike.uexp" "../../../TestDLC/Content/model/bike.uexp" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/car.uasset" "../../../TestDLC/Content/model/car.uasset" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/car.uexp" "../../../TestDLC/Content/model/car.uexp" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/123.uasset" "../../../TestDLC/Content/model/123.uasset" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/123.uexp" "../../../TestDLC/Content/model/123.uexp" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/carMat.uasset" "../../../TestDLC/Content/model/carMat.uasset" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/carMat.uexp" "../../../TestDLC/Content/model/carMat.uexp" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/bikeMat1.uasset" "../../../TestDLC/Content/model/bikeMat1.uasset" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/bikeMat1.uexp" "../../../TestDLC/Content/model/bikeMat1.uexp" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/bikeMat2.uasset" "../../../TestDLC/Content/model/bikeMat2.uasset" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/bikeMat2.uexp" "../../../TestDLC/Content/model/bikeMat2.uexp" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/2.uasset" "../../../TestDLC/Content/model/2.uasset" "D:/UE4/ueProjects/TestPak/TestHotPatcherRes/Saved/Cooked/WindowsNoEditor/TestDLC/Content/model/2.uexp" "../../../TestDLC/Content/model/2.uexp"
HotPatcher:
pak挂载代码:
using UnrealBuildTool; public class TestDLC : ModuleRules { public TestDLC(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore","UMG", "PakFile" }); PrivateDependencyModuleNames.AddRange(new string[] { }); } }
#pragma once #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "UMG/Public/Components/Button.h" #include "UMG/Public/Components/EditableText.h" #include "Engine/Classes/Engine/StaticMeshActor.h" #include "UObject/ConstructorHelpers.h" #include "Runtime/Core/Public/HAL/PlatformFilemanager.h" #include "Runtime/PakFile/Public/IPlatformFilePak.h" #include "Runtime/Engine/Classes/Components/StaticMeshComponent.h" #include "Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h" #include "Runtime/Core/Public/Misc/FileHelper.h" #include "Core/Public/Misc/CoreDelegates.h" #include "MyUserWidget.generated.h" UCLASS() class TESTDLC_API UMyUserWidget : public UUserWidget { GENERATED_BODY() public: UMyUserWidget(const FObjectInitializer& ObjectInitializer); virtual void NativeConstruct() override; UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) UEditableText* et_modelPath; UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) UEditableText* et_pakDir; UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) UButton* btn_mount; UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) UButton* btn_spawn; UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) UButton* btn_unmount; UPROPERTY(BlueprintReadOnly, meta = (BindWidget)) UButton* btn_destroy; UFUNCTION() void BtnClickMountEvent(); UFUNCTION() void BtnClickSpawnEvent(); UFUNCTION() void BtnClickUnmountEvent(); UFUNCTION() void BtnClickDestroyEvent(); TArray<AStaticMeshActor*> meshActorList; void InitEncrypt(uint8* key); void MountPak(const FString &paksDir); void UnmountPak(const FString &paksDir); static FPakPlatformFile* pakPlatformFile; static FPakPlatformFile * GetPakPlatformFile(); };
#include "MyUserWidget.h" UMyUserWidget::UMyUserWidget(const FObjectInitializer& ObjectInitializer) :Super(ObjectInitializer) { } void UMyUserWidget::NativeConstruct() { Super::NativeConstruct(); FString localPaksDir; FString apkPaksDir; #if PLATFORM_ANDROID localPaksDir = FString(TEXT("/storage/emulated/0/Paks")); apkPaksDir = UKismetSystemLibrary::GetProjectContentDirectory() + "/Pak"; #else localPaksDir = FString(TEXT("d:/Paks")); apkPaksDir = UKismetSystemLibrary::GetProjectContentDirectory() + "/Pak"; #endif TArray<FString> localPakFiles; IPlatformFile& platformFile = FPlatformFileManager::Get().GetPlatformFile(); platformFile.FindFiles(localPakFiles, *localPaksDir, *FString("pak")); if (localPakFiles.Num() > 0) { /* const FString Hash1 = LexToString(FMD5Hash::HashFile(*pakfile)); const FString Hash2 = LexToString(FMD5Hash::HashFile(*filePath)); if (Hash1 == Hash2) { GEngine->AddOnScreenDebugMessage(-1, 2, FColor::Red, TEXT("MD5一样")); } else { GEngine->AddOnScreenDebugMessage(-1, 2, FColor::Red, TEXT("MD5不一样解压")); }*/ } else { GEngine->AddOnScreenDebugMessage(-1, 2, FColor::Red, TEXT("不存在pak,解压")); TArray<FString> apkPakFiles; platformFile.FindFiles(apkPakFiles, *apkPaksDir, *FString("pak")); if (apkPakFiles.Num() > 0) { for (size_t i = 0; i < apkPakFiles.Num(); i++) { FString filePath = apkPakFiles[i]; TArray<uint8> data; FFileHelper::LoadFileToArray(data, *filePath); FString savePath = localPaksDir + "/" + FPaths::GetCleanFilename(filePath); FFileHelper::SaveArrayToFile(data, *savePath); } } } et_modelPath->SetText(FText::FromString("StaticMesh'/Game/model/bike.bike'")); et_pakDir->SetText(FText::FromString(localPaksDir)); btn_mount->OnClicked.AddDynamic(this, &UMyUserWidget::BtnClickMountEvent); btn_spawn->OnClicked.AddDynamic(this, &UMyUserWidget::BtnClickSpawnEvent); btn_unmount->OnClicked.AddDynamic(this, &UMyUserWidget::BtnClickUnmountEvent); btn_destroy->OnClicked.AddDynamic(this, &UMyUserWidget::BtnClickDestroyEvent); } void UMyUserWidget::BtnClickMountEvent() { FString pakDir = et_pakDir->GetText().ToString(); MountPak(pakDir); } void UMyUserWidget::BtnClickSpawnEvent() { FString content = et_modelPath->GetText().ToString(); UStaticMesh* mesh = LoadObject<UStaticMesh>(NULL, *content); AStaticMeshActor* staticMeshActor = GetWorld()->SpawnActor<AStaticMeshActor>(FVector(0, 0, 130), FRotator::ZeroRotator); staticMeshActor->SetMobility(EComponentMobility::Movable); UStaticMeshComponent* staticMeshComponent = staticMeshActor->GetStaticMeshComponent(); staticMeshComponent->SetStaticMesh(mesh); staticMeshComponent->RegisterComponent(); meshActorList.Add(staticMeshActor); } void UMyUserWidget::BtnClickUnmountEvent() { FString pakDir = et_pakDir->GetText().ToString(); UnmountPak(pakDir); } void UMyUserWidget::BtnClickDestroyEvent() { if (meshActorList.Num() > 0) { for (auto item : meshActorList) { if (item != nullptr) item->Destroy(); } meshActorList.Empty(); } } void UMyUserWidget::InitEncrypt(uint8* key) { FString tKey = TEXT("11111111112222222222333333333344"); char* myKey = TCHAR_TO_UTF8(*tKey); FMemory::Memcpy(key, myKey, 32); } //第一种方法 void UMyUserWidget::MountPak(const FString &paksDir) { FCoreDelegates::GetPakEncryptionKeyDelegate().BindUObject(this, &UMyUserWidget::InitEncrypt); TArray<FString> pakFiles; FPlatformFileManager::Get().GetPlatformFile().FindFiles(pakFiles, *paksDir, *FString("pak")); for (size_t i = 0; i < pakFiles.Num(); i++) { UE_LOG(LogTemp, Log, TEXT("Mount pak file: %s"), *pakFiles[i]); FPakPlatformFile* tPakPlatformFile = GetPakPlatformFile(); if (tPakPlatformFile != nullptr) tPakPlatformFile->Mount(*pakFiles[i], 0); } } void UMyUserWidget::UnmountPak(const FString &paksDir) { TArray<FString> pakFiles; FPlatformFileManager::Get().GetPlatformFile().FindFiles(pakFiles, *paksDir, *FString("pak")); for (size_t i = 0; i < pakFiles.Num(); i++) { UE_LOG(LogTemp, Log, TEXT("Unmount pak file: %s"), *pakFiles[i]); FPakPlatformFile* tPakPlatformFile = GetPakPlatformFile(); if (tPakPlatformFile != nullptr) tPakPlatformFile->Unmount(*pakFiles[i]); } } FPakPlatformFile * UMyUserWidget::pakPlatformFile = nullptr; FPakPlatformFile * UMyUserWidget::GetPakPlatformFile() { /*if (pakPlatformFile == nullptr) { pakPlatformFile = (FPakPlatformFile*)FPlatformFileManager::Get().FindPlatformFile(FPakPlatformFile::GetTypeName()); } return pakPlatformFile;*/ if (pakPlatformFile == nullptr) { FString PlatformFileName = FPlatformFileManager::Get().GetPlatformFile().GetName(); if (PlatformFileName.Equals(FString(TEXT("PakFile")))) { pakPlatformFile = static_cast<FPakPlatformFile*>(&FPlatformFileManager::Get().GetPlatformFile()); } else { pakPlatformFile = new FPakPlatformFile; if (!pakPlatformFile->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), TEXT(""))) { UE_LOG(LogTemp, Error, TEXT("FPakPlatformFile failed to initialize")); return nullptr; } FPlatformFileManager::Get().SetPlatformFile(*pakPlatformFile); } } return pakPlatformFile; } //第二种方法 //class FMyFileVisitor : public IPlatformFile::FDirectoryVisitor //{ //public: // virtual bool Visit(const TCHAR *FilenameOrDirectory, bool bIsDirectory) override // { // FString StandardizedFilenameOrDirectory(FilenameOrDirectory); // FPaths::MakeStandardFilename(StandardizedFilenameOrDirectory); // if (!bIsDirectory) // { // Files.Add(StandardizedFilenameOrDirectory); // } // else // { // Directories.Add(StandardizedFilenameOrDirectory); // } // // return true; // } // TArray<FString> Files; // TArray<FString> Directories; //}; //void UMyUserWidget::MountPak(const FString &paksDir) //{ // FPaths::MakeStandardFilename(paksDir); // FMyFileVisitor myFileVisitor; // FPlatformFileManager::Get().GetPlatformFile().IterateDirectoryRecursively(*paksDir, myFileVisitor); // for (auto &file : myFileVisitor.Files) // { // if (FPaths::GetExtension(file) == "pak") // { // UE_LOG(LogTemp, Log, TEXT("Find and mount pak file: %s"), *file); // GetPakPlatformFile()->Mount(*file, 0); // } // } //} // //FPakPlatformFile * UMyUserWidget::pakPlatformFile = nullptr; //FPakPlatformFile * UMyUserWidget::GetPakPlatformFile() //{ // if (pakPlatformFile == nullptr) // { // pakPlatformFile = new FPakPlatformFile(); // pakPlatformFile->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), TEXT("")); // FPlatformFileManager::Get().SetPlatformFile(*pakPlatformFile); // } // return pakPlatformFile; //}
注意:Content下放pak的文件夹名称不能是Paks,不然UE4会在程序启动后自动挂载此目录下的pak,要把Content下的资源拷贝到本地硬盘,需要在ProjectSettings设置要复制的目录:
从目前来看,UE4不支持在Editor模式下挂载pak,那么资源不能独立出来作为一个项目了(资源和代码独立的两个项目),只能放在一个项目里了,那么怎么排除这个资源文件夹呢?
不能使用List of maps to include in a packaged build去设置场景,因为不确定哪些模型资源会被打包进去,以及代码中的静态引用和动态引用的资源是否会被检测打包进去,这也是不确定的,所以慎用
我们可以使用黑名单的方式排除文件夹,具体使用说明如下:
可以将一个文本文件放在项目的 Build/(目标平台文件夹) 目录中,以指示烘培程序排除部分或完整文件路径,而不要将它们打包到项目中。
一个项目可以有多个分别用于调试、开发、测试和交付构建的黑名单文件,这些文件可设置为包括或排除任意项目数据。
您甚至可以针对项目所支持的各个平台使用不同的黑名单文件,例如针对 Android 使用一个黑名单文件,针对 iOS 使用另一个黑名单文件,等等。