Loading

【UE4】加载资源的方式(一)使用引用进行简单加载

【UE4】加载资源的方式(一)使用引用进行简单加载

参考资料&原文链接

UE4资源加载方式

UE4中资源的引用

UE4资源加载方式

UE4的资源管理

UE4资源简介

以下内容来自于UE4的资源管理

UE4的资源,就是在工程文件夹下的那些非代码文件,比如Content下面的网格,材质,蓝图等这些文件,大部分资源是以uasset作为后缀的,也有其他后缀如地图关卡的umap等。在打包时,这些文件可能会根据平台需要,被cook成更小的平台专用文件,然后被放在后缀是pak的压缩包里。游戏运行时,程序就会挂载解压这些pak包,然后加载包中的资源文件来使用。打个不是很合适的比方,Pak包就类似于Unity的AssetBundle包,uasset文件就类似于unity的.meta管理的那些资源文件。

程序在用资源的时候,并不是直接在用这些文件。而是要把这些文件转化为UObject或其他程序可以用的内存对象。比如网格资源文件,程序用的实际是UStaticMesh对象。而把资源文件,转变为内存里的UObject对象,就是资源管理做的事情。

UE4资源索引简介

UE4是通过路径来关联索引资源的,就跟操作系统下面的文件一样,每个资源都有他的唯一路径。但是这个路径又和文件路径稍微有点区别。

v2-3d01f0faa8a4949ac97c924d7d29aad4_720w

比如像上图这样的一个静态网格资源,点击Copy Reference,再粘贴到文本,可以看到他们路径是这样的:StaticMesh'/Game/Geometry/Meshes/1M_Cube.1M_Cube'

v2-85372c13737c85fa7ade48123dddebcc_720w

我把路径用颜色标了一下方便描述:

  • 红色部分:(StaticMesh)

是资源的类型,平时加载资源的时候也可以不要,加载函数内部会截掉这部分只留单引号里面的路径,所以其实前面这个StaticMesh或Blueprint可以不写,在编辑器Content Browser里右键点资源拷路径是有这部分的,我猜引擎留着这部分可能是为了用户清楚他的类型是什么,更方便识别一些,这个只是给人看的,程序不看这个。

  • 绿色部分:(/Game)

是资源的分区。大部分资源的路径都是以/Game开头,这个其实表示这个资源是在游戏的Content目录下面,也可以是/Engine就表示引擎下面的,比如下图,或者对应插件目录

  • 蓝色部分:(/Geometry/Meshes)

就跟操作系统文件管理器中的一样,表示资源在哪个文件夹下面

  • 黄色部分:(1M_Cube)

资源的包(Package)名,也就是这个资源所在的真实物理资源文件(uasset/umap)的名字,包其实就是UE4将对象按照自己的规则序列化到磁盘上的文件,在Content Browser里看到的每一个文件都是一个包

  • 紫色部分:(1M_Cube)

资源的对象名,因为物理的资源文件里面可能有多个对象,这个名字可以唯一标识包的内部每个对象的唯一名字。比如蓝图资源里有多个UObject,一个关卡文件里有多个Actor,一个UI蓝图里有多个控件。如果不写,UE4的某些接口会默认以包名补充到后面,也就是说默认使用和包名相同的对象名,但有的接口又可能不做处理,所以还是建议写。如果用的不是默认对象,而是资源对象的类,就要在后面加一个_C,如果是CDO对象,就要在前面加Default__

  • 冒号后面的部分

有些资源路径后面会带冒号:接一个文件名,这种其实是对象的子对象名,有的资源对象内部有子对象,比如C++类里的子类,这个不常用,知道即可。

v2-0df11bbde02ad5d25212947179372fac_720w

资源加载的大致步骤

程序在用资源的时候,并不是直接在用这些文件。而是要把这些文件转化为UObject或其他程序可以用的内存对象。比如网格资源文件,程序用的实际是UStaticMesh对象。而把资源文件,转变为内存里的UObject对象,就是资源管理做的事情。

对于UE4来说,这个过程大概有这几个步骤:

  1. 读取资源文件的数据到内存。
  2. 根据内存的二进制数据,把空壳对象反序列化成实际的对象。
  3. 如果这个对象有依赖其他对象,就递归的去做1和2的操作,直到这个对象完整可用。
  4. 调用对象的初始化函数,并将对象加入到引擎的对象管理中。

资源的卸载

默认情况下,加载中的资源由引擎持有引用,不会被卸载,加载完成后的资源会依赖引擎的gc卸载。如果没有被使用到,会在下次gc的时候释放掉。如果需要立即释放可以手动强制引擎gc。

但是可能有时候由于内存或其他各种原因,只想立即释放掉指定资源的内存,不想调用全局gc导致游戏产生卡顿,这时候要怎么办呢?我查了引擎官方文档以及网上各种文章,基本没有明确给出答案,下面我会根据以往我的经验介绍一些做法(野路子),不代表正确,但实测确实能立即释放掉内存,只是要自己额外花一些功夫保证安全。

  1. 大部分UObject,可以手动调用ConditionalBeginDestory,这里会主动先把对象的资源清理掉,留下一个空壳UObject。可以看引擎源码,这个函数相当于主动调用BeginDestroy,在对象gc时候也会调用,但是因为是Conditional的,如果之前已经调用过,在gc的时候就不会再次调用BeginDestroy从而避免了逻辑错误。通过这种方式,业务需要自行保证对象不再被使用,否则会出BUG。
  2. 如果是贴图,材质或Mesh等资源,可以直接调用ReleaseResource,这里内部会先把贴图或网格内存先释放,执行结束之后留下一个空壳UObject,对于空壳UObject其实就不怎么占用内存了,等待gc的时候销毁即可。另外因为有的ReleaseResource会Flush渲染命令队列可能产生卡顿,所以可以尽量把ReleaseResource这个函数提交到渲染命令队列,虽然内存释放不那么及时,但也比gc快很多,大部分情况一帧内就会释放掉。通过这种方式,业务也需要自行保证对象不再被使用,否则会出BUG。
  3. 如果是RenderTarget,引擎有提供专门的池,可以回收复用。
  4. 如果是动态材质,也有专门的函数可以删除。
  5. 如果是Actor或ActorComponent等,有专门的删除函数,可以调用DestroyActor或DestroyComponent,基本上能释放掉大部分资源。对于SceneComponent可以主动调用DestroyPhysicsState和DestroyRenderState释放掉物理或场景中的对象,也能立即释放一些内存。

硬性引用、软性引用、构造时引用

ue4提供了许多种机制来控制引用资产的方式并通过扩展将其装入内存。

这些引用分为两种方式:

硬性引用:即对象 A 引用对象 B,并导致对象 B 在对象 A 加载时加载。例如:被“硬”指针指向的资源都会被UE4在启动的时候进行载入,要注意性能问题。有关硬性引用的一个注意事项是,当对象加载并实例化时,以硬性方式引用的资产会自动加载,可能导致内存使用量迅速增加。

软性引用:即对象 A 通过间接机制(例如字符串形式的对象路径)来引用对象 B,软引用的好处是可以按照需要加载。

构造时引用:在构造时加载资产并赋给变量。

直接属性引用

这是最常见的资产引用情况,并通过 UPROPERTY 宏公开,面板会公开一个UPROPERTY。

EditDefaultsOnly说明此属性可通过属性窗口进行编辑,但只能在原型上进行。此说明符与所有"可见"说明符均不兼容。

UPROPERTY(EditDefaultsOnly, Category = "Test Texture")
TSoftObjectPtr<UTexture2D> TestImg;

image-20211103150434741

间接属性引用

间接引用有两种方式:FStringAssetReferencesTAssetPtr

美术师或设计师引用资源的最简单的方法是,创建一个UProperty 强指针,并赋予其一个类目。在虚幻引擎4中,如果您使用强指针UObject 属性引用一个资源,那么当加载包含该属性的对象时将会加载那个资源(通过把对象放置在地图中,或者通过从类似于游戏信息这样的东西中进行引用该对象)。

FStringAssetReferences

FStringAssetReferences是一个简单的结构体,包含了一个具有资源完整名称的字符串。如果在类中创建一个那种类型的属性,那么它将会显示在编辑器中。

在Editor中,他的表现就是一个允许异步加载的UObject*。它还可以正确的处理烘焙和重定向。

示例:

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test")
	FStringAssetReference SAR;

image-20211103115749978

可以看出这个的确是支持UObject的,什么都能选。

或者你也可以在代码里面这样写,动态加载。

void UWC_TestUI::Test()
{
	FStringAssetReference MyCharacterBP = "Blueprint'/Game/Core/BP_MyCharacter.BP_MyCharacter'";
	//尝试从路径中查找资源
	UObject* MyCharacterObj = MyCharacterBP.ResolveObject();
	//转成蓝图类型
	UBlueprint* MyCharacterPtr = Cast<UBlueprint>(MyCharacterObj);
	if (MyCharacterPtr != nullptr)
	{
		GetWorld()->SpawnActor<AMyCharacter>(MyCharacterPtr->GeneratedClass);
	}
}

FStringAssetReference类的作用主要是通过一个字符串,找到该字符串所对应的资源。或者通过给定的资源,找到该资源所对应的在项目中的路径,也就是前面所说的字符串。

其中,asset.ResolveObject就是查找字符串对应的资源,返回一个UObejct,我们通过将其转化成UBlueprint类型然后再去的他的GenerateClass即可。

UObject常用的函数有:

	/**
	 * Attempts to find a currently loaded object that matches this path
	 *
	 * @return Found UObject, or nullptr if not currently in memory
	 */
	UObject* ResolveObject() const;

尝试查找当前加载的与此路径匹配的对象。

返回找到的UObject,如果当前不在内存中则返回nullptr。

	/**
	 * Attempts to load the asset, this will call LoadObject which can be very slow
	 * @param InLoadContext Optional load context when called from nested load callstack
	 * @return Loaded UObject, or nullptr if the reference is null or the asset fails to load
	 */
	UObject* TryLoad(FUObjectSerializeContext* InLoadContext = nullptr) const;

尝试加载资产,这将调用LoadObject,这可能是非常慢的(LoadObject是同步加载)。

InLoadContext:可选加载上下文时,从嵌套的加载callstack调用。

返回一个已经加载的UObject的引用,或者是空或资产加载失败。

	/** Returns string representation of reference, in form /package/path.assetname[:subpath] */
	FString ToString() const;

返回引用的字符串表示形式,格式为/package/path.assetname[:subpath]

TAssetPtr

TAssetPtr基本上就是一个封装了FStringAssetReferencesTWeakObjectPtr,它使用一个特定的类作为模板,以便限制编辑器用户界面,使策划仅能选择特定的类。

使用IsPendding()方法可检查资产是否已准备好可供访问。

如果所引用资源存在于内存中,那么TAssetPtr.Get()将返回该资源。如果该资源不在内存中,那么可以调用ToStringReference()来查找它引用的资源,并可使用模板化LoadObject<>()方法、StaticLoadObject()FStreamingManager来加载对象。

前两个方法以同步方式加载资产,这可能会导致帧速率突降。再次调用TAssetPtr.Get()可以解除该资源的引用。

示例:

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test")
	TAssetPtr<UTexture2D> AP;

image-20211103115810254

如果你非要写代码以实现按需加载的话可以继续往下看。

FStringAssetReference和TAssetPtr的按需加载

前面已经用过它们来作为一个简单引用以加载资源,现在再来看一下它的定义:

可以看到FStringAssetReference的本质是FSoftObjectPath

// Not deprecating these yet as it will lead to too many warnings in games
//UE_DEPRECATED(4.18, "FStringAssetReference was renamed to FSoftObjectPath as it is now not always a string and can also refer to a subobject")
typedef FSoftObjectPath FStringAssetReference;

//UE_DEPRECATED(4.18, "FStringClassReference was renamed to FSoftClassPath")
typedef FSoftClassPath FStringClassReference;

TAssetPtr的本质是TSoftObjectPtr

// Not deprecating these yet as it will lead to too many warnings in games
//UE_DEPRECATED(4.18, "TAssetPtr was renamed to TSoftObjectPtr as it is not necessarily an asset")
template<class T=UObject>
using TAssetPtr = TSoftObjectPtr<T>;

//UE_DEPRECATED(4.18, "TAssetSubclassOf was renamed to TSoftClassPtr")
template<class TClass = UObject>
using TAssetSubclassOf = TSoftClassPtr<TClass>;

以下内容来自官方文档 - FSoftObjectPath

FSoftObjectPath

A struct that contains a string reference to an object, either a top level asset or a subobject.

包含对对象(顶级资产或子对象)的字符串引用的结构。

Remarks

A struct that contains a string reference to an object, either a top level asset or a subobject. This can be used to make soft references to assets that are loaded on demand. This is stored internally as an FName pointing to the top level asset (/package/path.assetname) and an option a string subobject path. If the MetaClass metadata is applied to a FProperty with this the UI will restrict to that type of asset.

包含对对象(顶级资产或子对象)的字符串引用的结构。这可以用于对按需加载的资产进行软引用。它在内部存储为FName,指向顶级资产(/package/path.assetname)和一个选项,一个字符串子对象路径。如果MetaClass元数据被应用到FProperty, UI将限制在该类型的资产。

以下内容来自官方文档 - TSoftObjectPtr

TSoftObjectPtr

TSoftObjectPtr is templatized wrapper of the generic FSoftObjectPtr, it can be used in UProperties

TSoftObjectPtr是通用的模板化包装器,它可以用于UProperties。

以下内容来自官方文档 - 异步资源加载

FSoftObjectPaths和TSoftObjectPtr

让美术或设计师引用资源的最简单方法是创建硬指针的UProperty并为它指定一个类别。在UE4中,如果有一个硬UObject指针属性引用了一个资源,则加载包含这个属性的对象(放在贴图中,或者从gameinfo等引用)时,就会加载这个资源。如果处理不当,就会在游戏开始时加载全部资源。如果您希望美术/设计师能够使用同一个UI作为硬指针来引用特定资源,而不必总是加载被引用资源,可以使用FSoftObjectPathTSoftObjectPtr

FSoftObjectPath是一个简单的结构体,其中有一个字符串包含资源的完整名称。如果您在类中添加这个类型的属性,它就会像UObject *属性一样显示在编辑器中。它还会正确处理烘焙和重定向,因此如果您使用SoftObjectPath,就一定能在设备上正确工作。

TSoftObjectPtr基本上是包含了FSoftObjectPathTWeakObjectPtr,将用于设置特定类的模板,这样就可以限制编辑器UI仅允许选择特定类。如果被引用资源存在于内存中,则TSoftObjectPtr.Get()将返回这个资源。如果不存在,可以调用ToSoftObjectPath()来找出它引用的资源,使用下述方法加载这个资源,然后再次调用TSoftObjectPtr.Get()来取消引用。

如果美术或设计师要手动设置引用,则TSoftObjectPtrs和Soft Object Paths十分有用,但如果想要通过查询等功能来查找符合特定要求的资源,而不加载所有资源,则可以使用资源注册表和对象库。

总结:

FSoftObjectPath(FStringAssetReference ):包含Asset位置字符串的结构体,可以指向UObject*的资源类型。

TSoftObjectPtr(TAssetPtr):使用FSoftObjectPath构造出来的结构体,用于监视资源状态,指向尚未加载但可以根据请求加载的资产的指针。

TAssetSubclassOf :指向已定义的基类的子类的指针,该子类尚未加载,但可以根据请求加载。用于指向蓝图而不是基本component(大概是因为蓝图是Asset)。

在C++里面使用:

//.h
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test")
	FStringAssetReference SAR;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test")
	TAssetPtr<UTexture2D> AP;

//.cpp
void UWC_TestUI::OnBtnClickCommonBtn_SARLoad()
{
	FStringAssetReference SAR_SoUnrealPath;
	//检查是否指向一个有效的UObject,返回false则不是一个有效的UObject或者被初始化为null
	if (SAR.IsValid())
	{
		UE_LOG(LogTemp, Warning, TEXT("SAR.AssetName:%s"), *SAR.GetAssetName());
		UE_LOG(LogTemp, Warning, TEXT("SAR.AssetPathName:%s"), *(SAR.GetAssetPathName().ToString()));
		UE_LOG(LogTemp, Warning, TEXT("SAR.AssetPathString:%s"), *SAR.GetAssetPathString());
		SAR_SoUnrealPath = SAR.GetAssetPathString();
	}else
	{
		//指定路径
		SAR_SoUnrealPath = TEXT("Texture2D'/Game/UI/Images/SoUnreal.SoUnreal'");
	}
	//资源同步加载
	UObject * UE4PathObj = UAssetManager::GetStreamableManager().LoadSynchronous(SAR_SoUnrealPath);
	//强转一下
	UTexture2D* UE4PathPtr = Cast<UTexture2D>(UE4PathObj);
	if (UE4PathPtr != nullptr)
	{
		if (Img_SAR != nullptr)
		{
			Img_SAR->SetBrushFromTexture(UE4PathPtr);
		}
	}
}

void UWC_TestUI::OnBtnClickCommonBtn_APLoad()
{
	FStringAssetReference AP_UE4Path;
	//检查是否指向一个有效的UObject,返回false则不是一个有效的UObject或者被初始化为null
	if (AP.IsValid())
	{
		AP_UE4Path = AP.ToSoftObjectPath();
	}else
	{
		//指定路径
		AP_UE4Path = TEXT("Texture2D'/Game/UI/Images/GetUnreal.GetUnreal'");
	}
	//注意这里可以传模板进去,下面就不用再强转了
	UTexture2D * UE4PathPtr = UAssetManager::GetStreamableManager().LoadSynchronous<UTexture2D>(AP_UE4Path);
	//强转一下
	//UTexture2D* UE4PathPtr = Cast<UTexture2D>(UE4PathObj);
	if (UE4PathPtr != nullptr)
	{
		if (Img_AP != nullptr)
		{
			Img_AP->SetBrushFromTexture(UE4PathPtr);
		}
	}
}

image-20211104213618250

注意AP是一个TAssetPtr,是一个用UTexture2D包起来的模板,所以在加载的时候已经有了它的类型,就不用强转了。

特点

此方法的特点是:

  • 简单、对美术或者UI设计师十分友好,可以不动代码而预览效果。
  • 在使用之前需要先进行手动指定引用,并且若是没有手动指定引用则可以用代码来指定。代码指定的时候注意资源名或者路径变更的时候会发生错误,因为是写死在代码里面的。这个时候维护就要花额外的时间。

本文标签

游戏开发游戏开发基础Unreal EngineUE资源加载

posted @ 2021-11-02 22:16  多思考多实践同等重要  阅读(8106)  评论(0编辑  收藏  举报