jjzjj

UE5热更新(Pak包的Cook,打包,加载,踩过的一些坑)

Monkey X Luffy 2023-05-26 原文
	这几天弄UE5的Pak包加载,弄得晕头转向,网上都是UE4.27以下的教程,UE4.27, UE5修改了一些东西,导致按照[虚幻官方直播第四期的教程](https://www.bilibili.com/video/BV1Ut411A7sk?spm_id_from=333.337.search-card.all.click&vd_source=f7b2defde4971310a19a6e9e40c36b90)无法成功加载,废话不多说,先说一下踩的坑。

一·UE5加载Pak和UE4不同的地方

1.取消Use Iostore(使用Io保存)的勾选

![在这里插入图片描述](https://img-blog.csdnimg.cn/15bd946face648ee80a2ad4cfc787f90.png)

UE5的打包设置中自动勾选了使用Io保存,还是试用功能,引擎的Mount函数中有对此的判断,

如果启用IO保存,会检测是否存在对应的.utoc文件,如果没有,返回false,Mount失败。
我们自己使用UE5的Cook,打包是不会生成.utoc文件的,导致一直加载失败。至于如何正确使用Iostore之后有时间再看看。

2.取消共享材质着色器代码(Share Material Shader Code)勾选

UE5默认启动共享材质着色器代码,导致加载出来的Actor材质丢失,取消勾选可以解决问题。

3.蓝图调用C++路径名莫名其妙的多了一个空白的字符

这个可能大家没遇到,我遇到了,在调试的时候发现,FString类型的参数莫名其妙多了一个空白的字符,导致一直无法找到Pak包。教程使用命令行调用该函数不存在这个问题,我将参数在C++里写死了,规避了这个问题,以后有时间再看看。

主要踩了这三个坑,下面再说一下整个Pak的使用流程。

二、UE5 Pak包的Cook,打包,加载流程

1.cook

先创建DLC文件夹,有一个Actor,一个贴图,一个材质,一个Mesh,

在Actor里面添加测试代码,每秒打印一次“耶,我被成功加载了”

将DLC文件夹添加到要烘焙的额外资产目录

在平台中启用烘焙,这里要注意,UE4的烘焙按钮是在文件里面,UE5的烘焙按钮在平台

烘焙成功后,可以看到G:\UE5Demo\PakTest\Saved\Cooked\Windows\PakTest\Content\DLC里面有DLC的.uasset文件

2 打Pak包

找到引擎目录的UnrealPak.exe,教程说可以将它不依赖UE的库,可以将它移到任意地方运行,试了一下,不行。老老实实使用cmd运行,cd到该目录下,运行UnrealPak.exe。

然后输入命令unrealpak {pak目录} -create={cook文件的目录} 打pak包
unrealpak G:\dlc.pak -create=G:\UE5Demo\PakTest\Saved\Cooked\Windows\PakTest\Content\DLC
这是普通的打pak的方式,加密或者压缩Pak包我就不演示。
输入命令unrealpak {pak目录} -list 查看pak包
unrealpak G:\dlc.pak -list

3. 加载Pak包

创建一个C++类继承自Actor,在工程build.cs里面添加模块"PakFile"

PublicDependencyModuleNames.AddRange(new string[] 
{ "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "PakFile" });

在.h里声明OldPlatform和PakPlatform,这里不求甚解,不用理会到底是干嘛的,花大代价搞明白了估计也过几天就忘。
声明TestLoadPak函数,这里使用蓝图调用,也可以学习教程使用命令行调用,将UFUNCTION(BlueprintCallable)改为UFUNCTION(Exec)即可。

	TSharedPtr<class FPakPlatformFile> PakPlatform;
	class IPlatformFile* OldPlatform;
	UFUNCTION(BlueprintCallable)
	bool TestLoadPak(const FString& InPakFullPath);

在.cpp文件中引用头文件

#include "MyActor.h"
#include "IPlatformFilePak.h"
#include "HAL/PlatformFilemanager.h"
#include "Runtime/Engine/Classes/Engine/StreamableManager.h"
#include "Runtime/Engine/Classes/Engine/AssetManager.h"
#include "Runtime/Engine/Classes/Engine/StaticMeshActor.h"
#include "Kismet/KismetStringLibrary.h"

实现BeginPlay函数,初始化

	Super::BeginPlay();
	OldPlatform = &FPlatformFileManager::Get().GetPlatformFile();
	PakPlatform = MakeShareable(new FPakPlatformFile());
	PakPlatform->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), TEXT(""));

实现TestLoadPak函数

bool AMyActor::TestLoadPak(const FString& InPakFullPath)
{

	FPlatformFileManager::Get().SetPlatformFile(*PakPlatform.Get());

	FString PakFileFullPath = L"g:/dlc.pak";
	if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*PakFileFullPath))
		return false;
	//FString PakName = GetPakFileName(PakFileFullPath);

	TRefCountPtr<FPakFile> TmpPak = new FPakFile(PakPlatform.Get(), *PakFileFullPath, false);
	FString OldPakMountPoint = TmpPak->GetMountPoint();

	int32 ContentPos = OldPakMountPoint.Find("Content/");
	FString NewMountPath = OldPakMountPoint.RightChop(ContentPos);

	FString ProjectPath = FPaths::ProjectDir();
	//ProjectPath = "../../../PakTest/";
	NewMountPath = ProjectPath + NewMountPath;
	TmpPak->SetMountPoint(*NewMountPath);

	if (PakPlatform->Mount(*PakFileFullPath, 1, *NewMountPath))
	{
		//StaticMesh'/Game/DLC/SM_Cube.SM_Cube_C'
		//Blueprint'/Game/DLC/DLC_Cube.DLC_Cube'
		//World'/Game/ThirdPerson/Maps/ThirdPersonMap.ThirdPersonMap'
		TArray<FString> FoundFilenames;
		TmpPak->FindFilesAtPath(FoundFilenames, *TmpPak->GetMountPoint(), true, false, false);
		if (FoundFilenames.Num() > 0)
		{
			if (GetWorld()->WorldType == EWorldType::Game)
			{
				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);
						FString blueprint = TEXT("Blueprint'");
						NewFileName.Append(TEXT("_C'"));
						GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Red, *NewFileName);

						NewFileName=UKismetStringLibrary::Concat_StrStr(TEXT("Blueprint'"), NewFileName);
						UClass* Class = LoadClass<AActor>(NULL, *NewFileName);
						//UClass* Class = LoadClass<AActor>(NULL, TEXT("Blueprint'/Game/DLC/DLC_Cube.DLC_Cube_C'"));
						if (Class == nullptr)
						{
							GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Red, TEXT("Load Class Error"));
						}
						else
						{
							AActor* MeshActor = GetWorld()->SpawnActor<AActor>(Class, FVector(100, 100, 400), FRotator(0, 0, 0));
						}
					}
				}
			}	
		}
	}
	//设置回原来的读取方式,不然包内的资源可能访问不了
	FPlatformFileManager::Get().SetPlatformFile(*OldPlatform);
	return true;
}

这个例子指的注意的是有三个路径,
1.Pak包的路径-PakFileFullPath,我将Pak包的路径写死的原因是之前提到,蓝图传参的时候,莫名其妙多了一个空字符,导致路径一直不对。然后生成一个FPakFile对象,注意UE5将FPakFile的析构函数私有化了,不能使用共享智能指针。

TRefCountPtr<FPakFile> TmpPak = new FPakFile(PakPlatform.Get(), *PakFileFullPath, false);

2.挂载点的路径-NewMountPath,NewMountPath=“…/…/…/PakTest/Content/DLC”,要想办法拼对这个路径,具体需要在打包之后调试一下,查看各个路径的值,最终拼成上述值的结构"…/…/…/{项目名}/Content/{DLC目录}"。然后设置挂载点并挂载

TmpPak->SetMountPoint(*NewMountPath);
PakPlatform->Mount(*PakFileFullPath, 1, *NewMountPath)

3.Pak包资源的虚拟路径-NewFileName,我的是"Blueprint’/Game/DLC/DLC_Cube.DLC_Cube_C’",在我们烘培的时候,直接在UE5编辑器选择资源-》复制引用,后面加上”_C“,即可得到该路径。我们也是要把从Pak包里读出的文件名拼成这样的路径。最后通过LoadClass加载虚拟路径的资源,并生成,

UClass* Class = LoadClass<AActor>(NULL, *NewFileName);
AActor* MeshActor = GetWorld()->SpawnActor<AActor>(Class, FVector(100, 100, 400), FRotator(0, 0, 0));

最后派生一个Actor,调用该函数,放到场景中,将UE5编辑器的DLC文件夹删除,注意每次执行删除资源或者移动资源时,要选择内容右键修复文件夹中的重定向器,这样才能将引用,资源处理完成。

最后打包项目,运行

最后能看到左上角的打印,前方的带纹理的小方块,终于成功啦,普天同庆,完结撒花。

如果没有看到上述效果,可以将程序附加到VS的进程进行调试,查看各个路径对不对。

有关UE5热更新(Pak包的Cook,打包,加载,踩过的一些坑)的更多相关文章

  1. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  2. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  3. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  4. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  5. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  6. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

  7. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  8. ruby-on-rails - 从应用程序中自定义文件夹内的命名空间自动加载 - 2

    我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty

  9. objective-c - 在设置 Cocoa Pods 和安装 Ruby 更新时出错 - 2

    我正在尝试为我的iOS应用程序设置cocoapods但是当我执行命令时:sudogemupdate--system我收到错误消息:当前已安装最新版本。中止。当我进入cocoapods的下一步时:sudogeminstallcocoapods我在MacOS10.8.5上遇到错误:ERROR:Errorinstallingcocoapods:cocoapods-trunkrequiresRubyversion>=2.0.0.我在MacOS10.9.4上尝试了同样的操作,但出现错误:ERROR:Couldnotfindavalidgem'cocoapods'(>=0),hereiswhy:U

  10. ruby - 找一些句子 - 2

    我想找到在某些文本中找到一些(让它是两个)句子的好方法。什么会更好-使用正则表达式或拆分方法?你的想法?应JeremyStein的要求-有一些例子示例:输入:ThefirstthingtodoistocreatetheCommentmodel.We’llcreatethisinthenormalway,butwithonesmalldifference.IfwewerejustcreatingcommentsforanArticlewe’dhaveanintegerfieldcalledarticle_idinthemodeltostoretheforeignkey,butinthis

随机推荐