UE4资源热更新

来源:互联网 发布:c语言求逻辑表达式的值 编辑:程序博客网 时间:2024/04/29 00:55

文章出处:http://blog.csdn.net/u012385432

最近发现有些网站复制了我的文章却没有注明出处,表示鄙视...转载可以,还请注明出处...不要剽窃别人的劳动成果...


前面的几篇文章中提及了有关.Pak文件和文件下载的部分,这两部分组合起来,其实就是我们的资源热更新了.当然代码的热更新不在这个讨论范围内.代码的热更新的话就更加麻烦了.这次讨论的只限资源的热更新...

前面文章链接:

1.下载文件链接

2.Pak使用教程链接


我们在做项目的时候,通常会有这样的需求,比如说美术新出了几个模型,需要替换原有工程中的模型.但是包已经发出去了,用户已经安装.这个时候又不可能因为一点东西让用户重新下载整个软件.或者说包体发出来的时候太大,老板要求在下载好主体的情况下,在进入软件的时候才去下载要用到的资源.这几种情况通常就是我们说的资源热更新了.在Unity里面,我们用AssetBunlde来做资源的热更新.不得不说UNITY对热更新的支持还是不错的.UNITY5.0版本以后,AssetBundle会自己生成Manifest.xml文件.不再需要用户去维护复杂的Dependency关系了.但是用UE4做的话就没那么舒服了...就像UNITY一样...在UE中,我们使用PAK来进行热更新.所以一般来说我们的项目发布的时候是没有那些UASSET资源的.加载uasset其实就是加载的pak文件中的uasset...


那么UE4的资源热更新主要有这么几步

1.打包好.Pak文件并将其放置到服务器上,其中需要一份数据文件Version.txt(json,xml或其他的格式都可以)来表示当前的版本信息.文件的内容我目前设计的很简单.主要就只有两个内容,一个是文件的MD5值,另一个是这次打包.Pak文件一共打包了什么Pak文件进去.

2.本地也需要一份Version.txt,用来比对服务器Version和本地的Version的区别,区别的标识就是文件的MD5值.如果发现MD5值不同的话就把服务器上的Version.txt中所涉及到的所有资源都下载下来并覆盖本地的资源,同时将服务器的Version.txt覆盖本地的Version.避免没必要的重复下载.


一.工具的准备  ----   .pak文件批量打包工具

首先,很蛋疼的是似乎在UE里面没办法通过C++来调用UnrealPak.exe工具,所以我用C#写了一个批量打包pak文件的工具.可以一次将多个uasset文件打包成一个.pak文件.或者将多个uasset文件打包成多个.pak文件.同时,在生成pak文件的时候还会生成版本文件.

页面效果如下:


打包出来的东西:


Version.txt内容:


实现这个工具其实非常的简单.几十行代码就可以了.接下来看一下代码...其中选中引擎根目录,选中要打包的文件等代码就不贴了.篇幅太大.以下是打包的具体代码

[csharp] view plain copy
 
在CODE上查看代码片派生到我的代码片
  1. private void Btn_MultipleBuild_Click(object sender, EventArgs e)  
  2. {  
  3.     Btn_MultipleBuild.Enabled = false;  
  4.     Btn_MultipleBuild.Text = "正在打包...";  
  5.   
  6.     //sb,sw,textWirter均是为了生成Json字符串而使用的  
  7.     StringBuilder sb = new StringBuilder();  
  8.     StringWriter sw = new StringWriter(sb);  
  9.     JsonTextWriter textWriter = new JsonTextWriter(sw);  
  10.     textWriter.Formatting = Formatting.Indented;  
  11.     DateTime Today = DateTime.UtcNow;  
  12.     int second = Today.Second;  
  13.   
  14.     //生成文件的MD5值  
  15.     string fileMD5 = StrToMD5(second.ToString());  
  16.   
  17.     textWriter.WriteStartObject();  
  18.     textWriter.WritePropertyName("FileVersion");  
  19.     textWriter.WriteStartObject();  
  20.     textWriter.WritePropertyName("MD5");  
  21.     textWriter.WriteValue(fileMD5);  
  22.     textWriter.WriteEndObject();  
  23.   
  24.   
  25.       
  26.     // 检查选中的引擎根目录,其目录下是否包含有UnralPak.exe文件  
  27.     if (!File.Exists(TextBox_MultipleEnginePath.Text + @"\Engine\Binaries\Win64\UnrealPak.exe"))  
  28.     {  
  29.         MessageBox.Show("打包失败,没有找到 UnrealPak.exe,引擎路径不存在!");  
  30.         Btn_MultipleBuild.Enabled = true;  
  31.         Btn_MultipleBuild.Text = "打包";  
  32.         return;  
  33.     }  
  34.   
  35.     textWriter.WritePropertyName("Files");  
  36.     textWriter.WriteStartArray();  
  37.   
  38.     //根据多选框选中的文件来对文件进行打包  
  39.     string[] assetNameArray = TextBox_MultipleUassetPath.Text.Split(' ');  
  40.     for (int i = 0; i < assetNameArray.Length; i++)  
  41.     {  
  42.         string assetFullName = assetNameArray[i].Replace('\\','/');  
  43.         string[] assetArray = assetFullName.Split('/');  
  44.         string assetName = assetArray[assetArray.Length - 1].Replace(".uasset""");  
  45.         string assetMD5 = StrToMD5(assetName + second.ToString());  
  46.         string outPath = TextBox_MultipleOutPath.Text + "\\" + assetName + ".pak";  
  47.   
  48.         //通过Process相关类来多次调用UnrealPak.exe程序来打包  
  49.         ProcessStartInfo info = new ProcessStartInfo();  
  50.         info.FileName = TextBox_MultipleEnginePath.Text + @"\Engine\Binaries\Win64\UnrealPak.exe";  
  51.         info.Arguments = @outPath + @" " + @assetFullName;  
  52.         info.WindowStyle = ProcessWindowStyle.Minimized;  
  53.         Process process = Process.Start(info);  
  54.         process.WaitForExit();  
  55.   
  56.         //将文件的信息写入到Json文件中  
  57.         textWriter.WriteStartObject();  
  58.         textWriter.WritePropertyName("FileName");  
  59.         textWriter.WriteValue(assetName);  
  60.         textWriter.WritePropertyName("MD5");  
  61.         textWriter.WriteValue(assetMD5);  
  62.         textWriter.WriteEndObject();  
  63.     }  
  64.     MessageBox.Show("生成pak完毕!");  
  65.     textWriter.WriteEndArray();  
  66.     textWriter.WriteEndObject();  
  67.       
  68.     Btn_MultipleBuild.Text = "打包";  
  69.     Btn_MultipleBuild.Enabled = true;  
  70.   
  71.   
  72.     string saveData =  
  73.         TextBox_MultipleEnginePath.Text + ";" +  
  74.         TextBox_MultipleUassetPath.Text + ";" +  
  75.         TextBox_MultipleOutPath.Text;  
  76.     File.WriteAllText(Environment.CurrentDirectory + "/save.txt", saveData);  
  77.   
  78.     //生成Version.txt文件  
  79.     File.WriteAllText(TextBox_MultipleOutPath.Text + "/Version.txt",sb.ToString());  
  80. }  
[csharp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void Btn_MultipleBuild_Click(object sender, EventArgs e)  
  2. {  
  3.     Btn_MultipleBuild.Enabled = false;  
  4.     Btn_MultipleBuild.Text = "正在打包...";  
  5.   
  6.     //sb,sw,textWirter均是为了生成Json字符串而使用的  
  7.     StringBuilder sb = new StringBuilder();  
  8.     StringWriter sw = new StringWriter(sb);  
  9.     JsonTextWriter textWriter = new JsonTextWriter(sw);  
  10.     textWriter.Formatting = Formatting.Indented;  
  11.     DateTime Today = DateTime.UtcNow;  
  12.     int second = Today.Second;  
  13.   
  14.     //生成文件的MD5值  
  15.     string fileMD5 = StrToMD5(second.ToString());  
  16.   
  17.     textWriter.WriteStartObject();  
  18.     textWriter.WritePropertyName("FileVersion");  
  19.     textWriter.WriteStartObject();  
  20.     textWriter.WritePropertyName("MD5");  
  21.     textWriter.WriteValue(fileMD5);  
  22.     textWriter.WriteEndObject();  
  23.   
  24.   
  25.       
  26.     // 检查选中的引擎根目录,其目录下是否包含有UnralPak.exe文件  
  27.     if (!File.Exists(TextBox_MultipleEnginePath.Text + @"\Engine\Binaries\Win64\UnrealPak.exe"))  
  28.     {  
  29.         MessageBox.Show("打包失败,没有找到 UnrealPak.exe,引擎路径不存在!");  
  30.         Btn_MultipleBuild.Enabled = true;  
  31.         Btn_MultipleBuild.Text = "打包";  
  32.         return;  
  33.     }  
  34.   
  35.     textWriter.WritePropertyName("Files");  
  36.     textWriter.WriteStartArray();  
  37.   
  38.     //根据多选框选中的文件来对文件进行打包  
  39.     string[] assetNameArray = TextBox_MultipleUassetPath.Text.Split(' ');  
  40.     for (int i = 0; i < assetNameArray.Length; i++)  
  41.     {  
  42.         string assetFullName = assetNameArray[i].Replace('\\','/');  
  43.         string[] assetArray = assetFullName.Split('/');  
  44.         string assetName = assetArray[assetArray.Length - 1].Replace(".uasset""");  
  45.         string assetMD5 = StrToMD5(assetName + second.ToString());  
  46.         string outPath = TextBox_MultipleOutPath.Text + "\\" + assetName + ".pak";  
  47.   
  48.         //通过Process相关类来多次调用UnrealPak.exe程序来打包  
  49.         ProcessStartInfo info = new ProcessStartInfo();  
  50.         info.FileName = TextBox_MultipleEnginePath.Text + @"\Engine\Binaries\Win64\UnrealPak.exe";  
  51.         info.Arguments = @outPath + @" " + @assetFullName;  
  52.         info.WindowStyle = ProcessWindowStyle.Minimized;  
  53.         Process process = Process.Start(info);  
  54.         process.WaitForExit();  
  55.   
  56.         //将文件的信息写入到Json文件中  
  57.         textWriter.WriteStartObject();  
  58.         textWriter.WritePropertyName("FileName");  
  59.         textWriter.WriteValue(assetName);  
  60.         textWriter.WritePropertyName("MD5");  
  61.         textWriter.WriteValue(assetMD5);  
  62.         textWriter.WriteEndObject();  
  63.     }  
  64.     MessageBox.Show("生成pak完毕!");  
  65.     textWriter.WriteEndArray();  
  66.     textWriter.WriteEndObject();  
  67.       
  68.     Btn_MultipleBuild.Text = "打包";  
  69.     Btn_MultipleBuild.Enabled = true;  
  70.   
  71.   
  72.     string saveData =  
  73.         TextBox_MultipleEnginePath.Text + ";" +  
  74.         TextBox_MultipleUassetPath.Text + ";" +  
  75.         TextBox_MultipleOutPath.Text;  
  76.     File.WriteAllText(Environment.CurrentDirectory + "/save.txt", saveData);  
  77.   
  78.     //生成Version.txt文件  
  79.     File.WriteAllText(TextBox_MultipleOutPath.Text + "/Version.txt",sb.ToString());  
  80. }  
代码的话实在是太简单了..以至于没什么好说的.其中生成MD5值的代码是这样的:

[csharp] view plain copy
 
在CODE上查看代码片派生到我的代码片
  1. <span style="white-space:pre">    </span>public string StrToMD5(string str)  
  2.         {  
  3.             byte[] data = Encoding.GetEncoding("GB2312").GetBytes(str);  
  4.             MD5 md5 = new MD5CryptoServiceProvider();  
  5.             byte[] OutBytes = md5.ComputeHash(data);  
  6.   
  7.             string OutString = "";  
  8.             for (int i = 0; i < OutBytes.Length; i++)  
  9.             {  
  10.                 OutString += OutBytes[i].ToString("x2");  
  11.             }  
  12.             return OutString.ToLower();  
  13.         }  
[csharp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <span style="white-space:pre">    </span>public string StrToMD5(string str)  
  2.         {  
  3.             byte[] data = Encoding.GetEncoding("GB2312").GetBytes(str);  
  4.             MD5 md5 = new MD5CryptoServiceProvider();  
  5.             byte[] OutBytes = md5.ComputeHash(data);  
  6.   
  7.             string OutString = "";  
  8.             for (int i = 0; i < OutBytes.Length; i++)  
  9.             {  
  10.                 OutString += OutBytes[i].ToString("x2");  
  11.             }  
  12.             return OutString.ToLower();  
  13.         }  
那么既然已经能批量打包pak文件并生成Version.txt了.那么我们就把生成出来的这堆东西都丢到服务器上去就可以了.第一步完成.下面在UE4中完成第二部.资源的更新


二. 资源更新

1.首先我们在代码里面增加一个状态叫做GameUpdateResourcesState,资源更新状态,用来处理资源的更新

GameUpdateResourcesState.h:

[cpp] view plain copy
 
在CODE上查看代码片派生到我的代码片
  1. // Fill out your copyright notice in the Description page of Project Settings.  
  2.   
  3. #include "GameBaseState.h"  
  4. #include "IDHManagers/HttpLoader.h"  
  5. #pragma once  
  6.   
  7. /** 
  8.  * 
  9.  */  
  10.   
  11.  //class AHttpLoader;  
  12. class IDHOME_API GameUpdateResourcesState :public GameBaseState  
  13. {  
  14. private:  
  15.     struct FileMessage  
  16.     {  
  17.         FString FileName;  
  18.         FString FileMD5;  
  19.     };  
  20.   
  21. public:  
  22.     GameUpdateResourcesState();  
  23.     ~GameUpdateResourcesState();  
  24.   
  25.     void OnEnter(TArray<void*> Params) override;  
  26.     void OnExit() override;  
  27.   
  28. private:  
  29.     void GetServerResoucesVersionFile();  
  30.   
  31.     void CompareServerAndLocalVersion(bool bSuccess, FHttpResponsePtr Response);  
  32.   
  33.     const FString LocalVersionFileLocation = FPaths::GameContentDir() + TEXT("Data/Version.txt");  
  34.     const FString ServerPakDirectory = TEXT("http://localhost:80/icons/Data/");  
  35.     const FString SavePakDirectory = FPaths::GameContentDir() + TEXT("DownLoadPaks/");  
  36.   
  37.     bool GetVersionMessageFromString(FString JsonString, FString& VersionFileMD5, TArray<FileMessage>& FileMessages);  
  38.   
  39.     void UpdateResources(const TArray<FileMessage>& Files);  
  40.   
  41.     void DownloadFileComplete(FHttpResponsePtr Response, FString SavePath, FString FileName);  
  42.   
  43.     AHttpLoader* HttpLoader = nullptr;  
  44.   
  45. private:  
  46.     int DownloadCompleteNumber = 0;  
  47.     int NeedDownloadNumber = 0;  
  48.   
  49. };  
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // Fill out your copyright notice in the Description page of Project Settings.  
  2.   
  3. #include "GameBaseState.h"  
  4. #include "IDHManagers/HttpLoader.h"  
  5. #pragma once  
  6.   
  7. /** 
  8.  * 
  9.  */  
  10.   
  11.  //class AHttpLoader;  
  12. class IDHOME_API GameUpdateResourcesState :public GameBaseState  
  13. {  
  14. private:  
  15.     struct FileMessage  
  16.     {  
  17.         FString FileName;  
  18.         FString FileMD5;  
  19.     };  
  20.   
  21. public:  
  22.     GameUpdateResourcesState();  
  23.     ~GameUpdateResourcesState();  
  24.   
  25.     void OnEnter(TArray<void*> Params) override;  
  26.     void OnExit() override;  
  27.   
  28. private:  
  29.     void GetServerResoucesVersionFile();  
  30.   
  31.     void CompareServerAndLocalVersion(bool bSuccess, FHttpResponsePtr Response);  
  32.   
  33.     const FString LocalVersionFileLocation = FPaths::GameContentDir() + TEXT("Data/Version.txt");  
  34.     const FString ServerPakDirectory = TEXT("http://localhost:80/icons/Data/");  
  35.     const FString SavePakDirectory = FPaths::GameContentDir() + TEXT("DownLoadPaks/");  
  36.   
  37.     bool GetVersionMessageFromString(FString JsonString, FString& VersionFileMD5, TArray<FileMessage>& FileMessages);  
  38.   
  39.     void UpdateResources(const TArray<FileMessage>& Files);  
  40.   
  41.     void DownloadFileComplete(FHttpResponsePtr Response, FString SavePath, FString FileName);  
  42.   
  43.     AHttpLoader* HttpLoader = nullptr;  
  44.   
  45. private:  
  46.     int DownloadCompleteNumber = 0;  
  47.     int NeedDownloadNumber = 0;  
  48.   
  49. };  
GameUpdateResourcesState.cpp:

[cpp] view plain copy
 
在CODE上查看代码片派生到我的代码片
  1. // Fill out your copyright notice in the Description page of Project Settings.  
  2.   
  3. #include "IDHome.h"  
  4. #include "GameUpdateResoucesState.h"  
  5. #include "IDHManagers/AssetManager.h"  
  6. #include "IDHGameState.h"  
  7.   
  8. GameUpdateResourcesState::GameUpdateResourcesState()  
  9. {  
  10. }  
  11.   
  12. GameUpdateResourcesState::~GameUpdateResourcesState()  
  13. {  
  14. }  
  15.   
  16. void GameUpdateResourcesState::OnEnter(TArray<void*> Params)  
  17. {  
  18.     //获取服务器上的资源版本文件  
  19.     GetServerResoucesVersionFile();  
  20. }  
  21.   
  22. void GameUpdateResourcesState::OnExit()  
  23. {  
  24.   
  25. }  
  26.   
  27. void GameUpdateResourcesState::GetServerResoucesVersionFile()  
  28. {  
  29.     if (HttpLoader == nullptr)  
  30.     {  
  31.         TArray<AActor*> Actors;  
  32.         UGameplayStatics::GetAllActorsOfClass(World, AHttpLoader::StaticClass(), Actors);  
  33.         HttpLoader = Cast<AHttpLoader>(Actors[0]);  
  34.     }  
  35.   
  36.     FString Url = TEXT("http://localhost:80/icons/Data/Version.txt");  
  37.     FString SendDataString;  
  38.     FDownloadDelegate DownloadServerFileDelegate;  
  39.     DownloadServerFileDelegate.BindRaw(this, &GameUpdateResourcesState::CompareServerAndLocalVersion);  
  40.       
  41.     HttpLoader->OnHttpRequest(Url, SendDataString, DownloadServerFileDelegate);  
  42. }  
  43.   
  44. void GameUpdateResourcesState::CompareServerAndLocalVersion(bool bSuccess, FHttpResponsePtr Response)  
  45. {  
  46.     //访问不到服务器或者服务器上没有该文件等...  
  47.     if (!bSuccess)  
  48.     {  
  49.         UE_LOG(LogClass, Log, TEXT("服务器上没有Version.txt文件,无需更新..."));  
  50.         AIDHGameState::Singleton()->ChangeState(GameStateEnum::LoginState);  
  51.         return;  
  52.     }  
  53.   
  54.   
  55.     FString LocalFile;  
  56.     FString LocalMD5;  
  57.     TArray<FileMessage> LocalFileMessages;  
  58.   
  59.     FString ServerFile = Response.Get()->GetContentAsString();  
  60.     FString ServerMD5;  
  61.     TArray<FileMessage> ServerFileMessages;  
  62.   
  63.     //获取服务器版本信息  
  64.     GetVersionMessageFromString(ServerFile, ServerMD5, ServerFileMessages);  
  65.   
  66.     //解析出来的MD5值非法或者没有需要下载的文件  
  67.     if(ServerMD5.IsEmpty() || ServerFileMessages.Num() == 0)  
  68.     {  
  69.         AIDHGameState::Singleton()->ChangeState(GameStateEnum::LoginState);  
  70.         return;  
  71.     }  
  72.   
  73.     //加载本地的资源版本文件  
  74.     if (FFileHelper::LoadFileToString(LocalFile, *LocalVersionFileLocation))  
  75.     {  
  76.         //获取本地版本信息  
  77.         GetVersionMessageFromString(LocalFile, LocalMD5, LocalFileMessages);  
  78.         if (LocalMD5.Equals(ServerMD5))  
  79.         {  
  80.             UE_LOG(LogClass, Log, TEXT("版本文件MD5值相同.无需更新"));  
  81.             AIDHGameState::Singleton()->ChangeState(GameStateEnum::LoginState);  
  82.             return;  
  83.         }  
  84.     }  
  85.     //覆盖本地的资源版本文件  
  86.     FFileHelper::SaveStringToFile(ServerFile, *LocalVersionFileLocation);  
  87.     //开始更新资源  
  88.     UpdateResources(ServerFileMessages);  
  89. }  
  90.   
  91. bool GameUpdateResourcesState::GetVersionMessageFromString(FString JsonString, FString& VersionFileMD5, TArray<FileMessage>& FileMessages)  
  92. {  
  93.     TSharedPtr<FJsonObject> JsonObject;  
  94.     TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);  
  95.     //将文件中的内容变成你需要的数据格式  
  96.     if (FJsonSerializer::Deserialize(Reader, JsonObject))  
  97.     {  
  98.         TSharedPtr<FJsonObject> FileObject = JsonObject->GetObjectField("FileVersion");  
  99.         VersionFileMD5 = FileObject->GetStringField("MD5");  
  100.         FileMessages.Empty();  
  101.         const TArray<TSharedPtr<FJsonValue>> Files = JsonObject->GetArrayField("Files");  
  102.         for (int i = 0; i < Files.Num(); i++)  
  103.         {  
  104.             const TSharedPtr<FJsonObject>* FileMessageObject;  
  105.             if (Files[i].Get()->TryGetObject(FileMessageObject))  
  106.             {  
  107.                 FileMessage* FileMes = new FileMessage();  
  108.                 FileMes->FileName = FileMessageObject->Get()->GetStringField("FileName");  
  109.                 FileMes->FileMD5 = FileMessageObject->Get()->GetStringField("MD5");  
  110.                 FileMessages.Add(*FileMes);  
  111.             }  
  112.         }  
  113.         return true;  
  114.     }  
  115.     else  
  116.     {  
  117.         UE_LOG(LogClass, Error, TEXT("无法解析json数据,Json数据可能有误..."));  
  118.         return false;  
  119.     }  
  120. }  
  121.   
  122. void GameUpdateResourcesState::UpdateResources(const TArray<FileMessage>& Files)  
  123. {  
  124.     NeedDownloadNumber = Files.Num();  
  125.     DownloadCompleteNumber = 0;  
  126.   
  127.     //一个个文件进行下载  
  128.     for (int i = 0; i < Files.Num(); i++)  
  129.     {  
  130.         FString FileURL = ServerPakDirectory + Files[i].FileName + TEXT(".pak");  
  131.         FString SaveURL = SavePakDirectory + Files[i].FileName + TEXT(".pak");  
  132.         FRequestDelegate DownloadCompleteDelegate;  
  133.         DownloadCompleteDelegate.BindRaw(this, &GameUpdateResourcesState::DownloadFileComplete, SaveURL, Files[i].FileName);  
  134.         AAssetManager::Singleton()->DownloadPakFile(FileURL, DownloadCompleteDelegate);  
  135.     }  
  136. }  
  137.   
  138. //文件下载完成回调  
  139. void GameUpdateResourcesState::DownloadFileComplete(FHttpResponsePtr Response, FString SavePath, FString FileName)  
  140. {  
  141.     FFileHelper::SaveArrayToFile(Response->GetContent(), *SavePath);  
  142.     UE_LOG(LogClass, Log, TEXT("文件:%s 已经下载完成,保存在%s"), *FileName, *SavePath);  
  143.     DownloadCompleteNumber++;  
  144.     //如果所有文件都下载完了.那么就进行下一个游戏状态.  
  145.     if (DownloadCompleteNumber == NeedDownloadNumber)  
  146.     {  
  147.         UE_LOG(LogClass, Log, TEXT("资源已全部更新完毕,跳转到登录状态"));  
  148.         AIDHGameState::Singleton()->ChangeState(GameStateEnum::LoginState);  
  149.     }  
  150. }  
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // Fill out your copyright notice in the Description page of Project Settings.  
  2.   
  3. #include "IDHome.h"  
  4. #include "GameUpdateResoucesState.h"  
  5. #include "IDHManagers/AssetManager.h"  
  6. #include "IDHGameState.h"  
  7.   
  8. GameUpdateResourcesState::GameUpdateResourcesState()  
  9. {  
  10. }  
  11.   
  12. GameUpdateResourcesState::~GameUpdateResourcesState()  
  13. {  
  14. }  
  15.   
  16. void GameUpdateResourcesState::OnEnter(TArray<void*> Params)  
  17. {  
  18.     //获取服务器上的资源版本文件  
  19.     GetServerResoucesVersionFile();  
  20. }  
  21.   
  22. void GameUpdateResourcesState::OnExit()  
  23. {  
  24.   
  25. }  
  26.   
  27. void GameUpdateResourcesState::GetServerResoucesVersionFile()  
  28. {  
  29.     if (HttpLoader == nullptr)  
  30.     {  
  31.         TArray<AActor*> Actors;  
  32.         UGameplayStatics::GetAllActorsOfClass(World, AHttpLoader::StaticClass(), Actors);  
  33.         HttpLoader = Cast<AHttpLoader>(Actors[0]);  
  34.     }  
  35.   
  36.     FString Url = TEXT("http://localhost:80/icons/Data/Version.txt");  
  37.     FString SendDataString;  
  38.     FDownloadDelegate DownloadServerFileDelegate;  
  39.     DownloadServerFileDelegate.BindRaw(this, &GameUpdateResourcesState::CompareServerAndLocalVersion);  
  40.       
  41.     HttpLoader->OnHttpRequest(Url, SendDataString, DownloadServerFileDelegate);  
  42. }  
  43.   
  44. void GameUpdateResourcesState::CompareServerAndLocalVersion(bool bSuccess, FHttpResponsePtr Response)  
  45. {  
  46.     //访问不到服务器或者服务器上没有该文件等...  
  47.     if (!bSuccess)  
  48.     {  
  49.         UE_LOG(LogClass, Log, TEXT("服务器上没有Version.txt文件,无需更新..."));  
  50.         AIDHGameState::Singleton()->ChangeState(GameStateEnum::LoginState);  
  51.         return;  
  52.     }  
  53.   
  54.   
  55.     FString LocalFile;  
  56.     FString LocalMD5;  
  57.     TArray<FileMessage> LocalFileMessages;  
  58.   
  59.     FString ServerFile = Response.Get()->GetContentAsString();  
  60.     FString ServerMD5;  
  61.     TArray<FileMessage> ServerFileMessages;  
  62.   
  63.     //获取服务器版本信息  
  64.     GetVersionMessageFromString(ServerFile, ServerMD5, ServerFileMessages);  
  65.   
  66.     //解析出来的MD5值非法或者没有需要下载的文件  
  67.     if(ServerMD5.IsEmpty() || ServerFileMessages.Num() == 0)  
  68.     {  
  69.         AIDHGameState::Singleton()->ChangeState(GameStateEnum::LoginState);  
  70.         return;  
  71.     }  
  72.   
  73.     //加载本地的资源版本文件  
  74.     if (FFileHelper::LoadFileToString(LocalFile, *LocalVersionFileLocation))  
  75.     {  
  76.         //获取本地版本信息  
  77.         GetVersionMessageFromString(LocalFile, LocalMD5, LocalFileMessages);  
  78.         if (LocalMD5.Equals(ServerMD5))  
  79.         {  
  80.             UE_LOG(LogClass, Log, TEXT("版本文件MD5值相同.无需更新"));  
  81.             AIDHGameState::Singleton()->ChangeState(GameStateEnum::LoginState);  
  82.             return;  
  83.         }  
  84.     }  
  85.     //覆盖本地的资源版本文件  
  86.     FFileHelper::SaveStringToFile(ServerFile, *LocalVersionFileLocation);  
  87.     //开始更新资源  
  88.     UpdateResources(ServerFileMessages);  
  89. }  
  90.   
  91. bool GameUpdateResourcesState::GetVersionMessageFromString(FString JsonString, FString& VersionFileMD5, TArray<FileMessage>& FileMessages)  
  92. {  
  93.     TSharedPtr<FJsonObject> JsonObject;  
  94.     TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);  
  95.     //将文件中的内容变成你需要的数据格式  
  96.     if (FJsonSerializer::Deserialize(Reader, JsonObject))  
  97.     {  
  98.         TSharedPtr<FJsonObject> FileObject = JsonObject->GetObjectField("FileVersion");  
  99.         VersionFileMD5 = FileObject->GetStringField("MD5");  
  100.         FileMessages.Empty();  
  101.         const TArray<TSharedPtr<FJsonValue>> Files = JsonObject->GetArrayField("Files");  
  102.         for (int i = 0; i < Files.Num(); i++)  
  103.         {  
  104.             const TSharedPtr<FJsonObject>* FileMessageObject;  
  105.             if (Files[i].Get()->TryGetObject(FileMessageObject))  
  106.             {  
  107.                 FileMessage* FileMes = new FileMessage();  
  108.                 FileMes->FileName = FileMessageObject->Get()->GetStringField("FileName");  
  109.                 FileMes->FileMD5 = FileMessageObject->Get()->GetStringField("MD5");  
  110.                 FileMessages.Add(*FileMes);  
  111.             }  
  112.         }  
  113.         return true;  
  114.     }  
  115.     else  
  116.     {  
  117.         UE_LOG(LogClass, Error, TEXT("无法解析json数据,Json数据可能有误..."));  
  118.         return false;  
  119.     }  
  120. }  
  121.   
  122. void GameUpdateResourcesState::UpdateResources(const TArray<FileMessage>& Files)  
  123. {  
  124.     NeedDownloadNumber = Files.Num();  
  125.     DownloadCompleteNumber = 0;  
  126.   
  127.     //一个个文件进行下载  
  128.     for (int i = 0; i < Files.Num(); i++)  
  129.     {  
  130.         FString FileURL = ServerPakDirectory + Files[i].FileName + TEXT(".pak");  
  131.         FString SaveURL = SavePakDirectory + Files[i].FileName + TEXT(".pak");  
  132.         FRequestDelegate DownloadCompleteDelegate;  
  133.         DownloadCompleteDelegate.BindRaw(this, &GameUpdateResourcesState::DownloadFileComplete, SaveURL, Files[i].FileName);  
  134.         AAssetManager::Singleton()->DownloadPakFile(FileURL, DownloadCompleteDelegate);  
  135.     }  
  136. }  
  137.   
  138. //文件下载完成回调  
  139. void GameUpdateResourcesState::DownloadFileComplete(FHttpResponsePtr Response, FString SavePath, FString FileName)  
  140. {  
  141.     FFileHelper::SaveArrayToFile(Response->GetContent(), *SavePath);  
  142.     UE_LOG(LogClass, Log, TEXT("文件:%s 已经下载完成,保存在%s"), *FileName, *SavePath);  
  143.     DownloadCompleteNumber++;  
  144.     //如果所有文件都下载完了.那么就进行下一个游戏状态.  
  145.     if (DownloadCompleteNumber == NeedDownloadNumber)  
  146.     {  
  147.         UE_LOG(LogClass, Log, TEXT("资源已全部更新完毕,跳转到登录状态"));  
  148.         AIDHGameState::Singleton()->ChangeState(GameStateEnum::LoginState);  
  149.     }  
  150. }  
这里面,AHttpLoader是我写的一个发起HttpRequest的一个单例类,主要用于和服务器交互,请求数据或者是下载文件等..还有几个DownLoadDelegate之类的委托.整体思路很简单

1.进入该状态以后,去下载服务器上的版本信息文件

2.如果连接不上服务器或者没有服务器上没有这个文件,那么就直接执行下一个游戏状态的逻辑.如果能连接上并下载到了版本信息文件.进行下一步

3.获取服务器版本文件信息,如果数据合法,那么开始加载本地信息文件,对两个文件的MD5值进行比较.如果相同,那么就进行下一游戏状态的逻辑,否则执行下一步

4.如果MD5值不同,那么就先覆盖本地的版本信息文件,然后开始更新资源

5.一个一个资源的下载,下载完最后一个文件的时候,资源更新状态的逻辑就已经都做完了,那么可以执行接下来的逻辑啦.

整个资源的热更新思路就是这么简单.


文章里面涉及到的Pak文件和下载部分的教程我之前都已经写过了.串在一起就是一份完整的热更新思路了.至于你拿到了pak文件以后要怎么使用,那是你的事,不在这次的讨论范围内.

但是有一点值得注意的,经过我测试,目前UE4的pak文件是没有依赖关系的.也就是说,如果你打包了一个UBlueprint,里面包含了一个UStaticMesh的话,那么当你直接加载这个UBlueprint到世界中的时候,他的Mesh是会丢失的.在Unity里面我们通过AssetBundle,可以先将Mesh加载进内存,这种情况下就能正常加载出来Prefab了..但是在UE4里面不行.所以可能我们需要自己去维护这一个依赖关系.甚至可能对于UBlueprint这些要生成一个文件来记录他包含了什么依赖,就像UNITY中做的那样...处理依赖这个问题在UE4里面估计会是一个比较复杂的问题...这一块就先不讨论了..mmmm...有可能以后也不会讨论...


整篇文章写下来发现好像没有什么特别的东西...最难搞的Pak加载都写在上一篇文章了...但是主要是提供一个思路给那些没有做过热更新的小伙伴吧.或者是不知道怎么批量打包pak的小伙伴...

这次的热更新到这里应该就差不多全部解决了...

下次见...

0 0