【UE4】加载资源的方式(一)使用引用进行简单加载
【UE4】加载资源的方式(一)使用引用进行简单加载
参考资料&原文链接
UE4资源简介
以下内容来自于UE4的资源管理:
UE4的资源,就是在工程文件夹下的那些非代码文件,比如Content下面的网格,材质,蓝图等这些文件,大部分资源是以uasset作为后缀的,也有其他后缀如地图关卡的umap等。在打包时,这些文件可能会根据平台需要,被cook成更小的平台专用文件,然后被放在后缀是pak的压缩包里。游戏运行时,程序就会挂载解压这些pak包,然后加载包中的资源文件来使用。打个不是很合适的比方,Pak包就类似于Unity的AssetBundle包,uasset文件就类似于unity的.meta管理的那些资源文件。
程序在用资源的时候,并不是直接在用这些文件。而是要把这些文件转化为UObject或其他程序可以用的内存对象。比如网格资源文件,程序用的实际是UStaticMesh对象。而把资源文件,转变为内存里的UObject对象,就是资源管理做的事情。
UE4资源索引简介
UE4是通过路径来关联索引资源的,就跟操作系统下面的文件一样,每个资源都有他的唯一路径。但是这个路径又和文件路径稍微有点区别。
比如像上图这样的一个静态网格资源,点击Copy Reference,再粘贴到文本,可以看到他们路径是这样的:StaticMesh'/Game/Geometry/Meshes/1M_Cube.1M_Cube'
我把路径用颜色标了一下方便描述:
- 红色部分:(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++类里的子类,这个不常用,知道即可。
资源加载的大致步骤
程序在用资源的时候,并不是直接在用这些文件。而是要把这些文件转化为UObject或其他程序可以用的内存对象。比如网格资源文件,程序用的实际是UStaticMesh对象。而把资源文件,转变为内存里的UObject对象,就是资源管理做的事情。
对于UE4来说,这个过程大概有这几个步骤:
- 读取资源文件的数据到内存。
- 根据内存的二进制数据,把空壳对象反序列化成实际的对象。
- 如果这个对象有依赖其他对象,就递归的去做1和2的操作,直到这个对象完整可用。
- 调用对象的初始化函数,并将对象加入到引擎的对象管理中。
资源的卸载
默认情况下,加载中的资源由引擎持有引用,不会被卸载,加载完成后的资源会依赖引擎的gc卸载。如果没有被使用到,会在下次gc的时候释放掉。如果需要立即释放可以手动强制引擎gc。
但是可能有时候由于内存或其他各种原因,只想立即释放掉指定资源的内存,不想调用全局gc导致游戏产生卡顿,这时候要怎么办呢?我查了引擎官方文档以及网上各种文章,基本没有明确给出答案,下面我会根据以往我的经验介绍一些做法(野路子),不代表正确,但实测确实能立即释放掉内存,只是要自己额外花一些功夫保证安全。
- 大部分UObject,可以手动调用ConditionalBeginDestory,这里会主动先把对象的资源清理掉,留下一个空壳UObject。可以看引擎源码,这个函数相当于主动调用BeginDestroy,在对象gc时候也会调用,但是因为是Conditional的,如果之前已经调用过,在gc的时候就不会再次调用BeginDestroy从而避免了逻辑错误。通过这种方式,业务需要自行保证对象不再被使用,否则会出BUG。
- 如果是贴图,材质或Mesh等资源,可以直接调用ReleaseResource,这里内部会先把贴图或网格内存先释放,执行结束之后留下一个空壳UObject,对于空壳UObject其实就不怎么占用内存了,等待gc的时候销毁即可。另外因为有的ReleaseResource会Flush渲染命令队列可能产生卡顿,所以可以尽量把ReleaseResource这个函数提交到渲染命令队列,虽然内存释放不那么及时,但也比gc快很多,大部分情况一帧内就会释放掉。通过这种方式,业务也需要自行保证对象不再被使用,否则会出BUG。
- 如果是RenderTarget,引擎有提供专门的池,可以回收复用。
- 如果是动态材质,也有专门的函数可以删除。
- 如果是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;
间接属性引用
间接引用有两种方式:FStringAssetReferences
和TAssetPtr
。
美术师或设计师引用资源的最简单的方法是,创建一个UProperty 强指针,并赋予其一个类目。在虚幻引擎4中,如果您使用强指针UObject 属性引用一个资源,那么当加载包含该属性的对象时将会加载那个资源(通过把对象放置在地图中,或者通过从类似于游戏信息这样的东西中进行引用该对象)。
FStringAssetReferences
FStringAssetReferences
是一个简单的结构体,包含了一个具有资源完整名称的字符串。如果在类中创建一个那种类型的属性,那么它将会显示在编辑器中。
在Editor中,他的表现就是一个允许异步加载的UObject*
。它还可以正确的处理烘焙和重定向。
示例:
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test")
FStringAssetReference SAR;
可以看出这个的确是支持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
基本上就是一个封装了FStringAssetReferences
的TWeakObjectPtr
,它使用一个特定的类作为模板,以便限制编辑器用户界面,使策划仅能选择特定的类。
使用IsPendding()
方法可检查资产是否已准备好可供访问。
如果所引用资源存在于内存中,那么TAssetPtr.Get()
将返回该资源。如果该资源不在内存中,那么可以调用ToStringReference()
来查找它引用的资源,并可使用模板化LoadObject<>()
方法、StaticLoadObject()
或FStreamingManager
来加载对象。
前两个方法以同步方式加载资产,这可能会导致帧速率突降。再次调用TAssetPtr.Get()
可以解除该资源的引用。
示例:
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test")
TAssetPtr<UTexture2D> AP;
如果你非要写代码以实现按需加载的话可以继续往下看。
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作为硬指针来引用特定资源,而不必总是加载被引用资源,可以使用
FSoftObjectPath
或TSoftObjectPtr
。
FSoftObjectPath
是一个简单的结构体,其中有一个字符串包含资源的完整名称。如果您在类中添加这个类型的属性,它就会像UObject *
属性一样显示在编辑器中。它还会正确处理烘焙和重定向,因此如果您使用SoftObjectPath,就一定能在设备上正确工作。
TSoftObjectPtr
基本上是包含了FSoftObjectPath
的TWeakObjectPtr
,将用于设置特定类的模板,这样就可以限制编辑器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);
}
}
}
注意AP是一个TAssetPtr,是一个用UTexture2D包起来的模板,所以在加载的时候已经有了它的类型,就不用强转了。
特点
此方法的特点是:
- 简单、对美术或者UI设计师十分友好,可以不动代码而预览效果。
- 在使用之前需要先进行手动指定引用,并且若是没有手动指定引用则可以用代码来指定。代码指定的时候注意资源名或者路径变更的时候会发生错误,因为是写死在代码里面的。这个时候维护就要花额外的时间。
本文标签
游戏开发
、游戏开发基础
、Unreal Engine
、UE资源加载
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了