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 使用另一个黑名单文件,等等。

 

posted @ 2020-11-12 17:59  MrZivChu  阅读(518)  评论(0编辑  收藏  举报
分享按钮