Aery的UE4 C++游戏开发之旅(4)加载资源&创建对象

目录

  • 资源的硬引用

    • 硬指针
    • FObjectFinder<T> / FClassFinder<T>
  • 资源的软引用
    • FSoftObjectPaths、FStringAssetReference
    • TSoftObjectPtr<T>
  • 同步加载资源
    • LoadObject/LoadClass
    • TryLoad/LoadSynchronous
  • 异步加载资源
    • FStreamableManager.RequestAsyncLoad()
  • 卸载资源
  • 创建对象
    • 创建一般对象
    • 创建Actor派生类对象
    • 创建Component派生类对象
    • 创建蓝图对象
  • 参考

在UE4中,项目中的所有资源都是存储在硬盘中,当需要用到资源时,则需要将其加载进入内存中使用。为了更好的表示(引用)资源,UE4提供了两种引用资源的方式——硬引用、软引用。

资源的硬引用



硬性引用,即对象 A 引用对象 B,并导致对象 B 在对象 A 加载时加载。通俗点说,硬引用所表示的资源在引用初始化时就加载进内存,因此硬引用的资源几乎不需要加载方法。

硬指针

引用资源的最简单方法是创建指针UProperty并为它指定一个类别,这种称为硬指针。
在UE4中,如果有一个硬UObject指针属性引用了一个资源(往往在蓝图上设置引用),则加载包含这个属性的对象(放在贴图中,或者从gameinfo等引用)时,就会加载这个资源。

UPROPERTY(EditDefaultsOnly, Category=Building)
USoundCue* ConstructionStartStinger;

FObjectFinder<T> / FClassFinder<T>

若需要用C++代码而非蓝图来设置引用,则往往需要FObjectFinder、FClassFinder。

#include "UObject/ConstructorHelpers.h" \\需要include的头文件

在UE4源码里面,FObjectFinder构造函数里通过调用LoadObject()来加载资源,而FClassFinder构造函数里调用的也是LoadObject()。

注意在使用它们的时候还得遵守如下规则:

  • 只能在类的构造函数中使用,如果在普通的逻辑代码中嵌套这份代码,会引起整个编译器的crash。(实际上里面代码就有检查是否在构造函数里,否则crash)
  • 其次,FObjectFinder/FClassFinder变量必须是static的,从而保证只有一份资源实例。
  1. FObjectFinder<T>:一般用来加载非蓝图资源,比如StaticMesh、Material、SoundWave、ParticlesSystem、AnimSequence、SkeletalMesh等资源:
static ConstructorHelpers::FObjectFinder<UTexture2D> ObjectFinder(TEXT("Texture2D'/Game/Textures/tex1.tex1'"));
UTexture2D* Texture2D = ObjectFinder.Object;
  1. FClassFinder<T>:一般用来加载蓝图资源并获取蓝图Class。这是因为如果C++要用蓝图创建对象,必须先获取蓝图的Class,然后再通过Class生成蓝图对象:
static ConstructorHelpers::FClassFinder<AActor> BPClassFinder(TEXT("/Game/Blueprints/MyBP"));
TSubclassOf<AActor> BPClass = BPClassFinder.Class;
...//利用Class生成蓝图对象
  • FClassFinder的模版名不能直接写UBlueprint,例如:FClassFinder<UBlueprint>是错误的。创建蓝图时选择的是什么父类,则写对应的父类名,假如是Actor,那么要写成:FClassFinder<AActor>,否则无法加载成功。
  • FClassFinder的模版名必须和TSubclassOf变量的模版名一致,当然也可使用UClass*代替TSubclassOf<T>。实际上TSubclassOf<T>也是UClass*,只是更加强调这个Class是从T派生出来的。
  • 在启动游戏时若报错提示找不到文件而崩溃(例如:Default property warnings and errors:Error: COD Constructor (MyGameMode): Failed to find /Game/MyProject/MyBP.MyBP)
    这是因为UE4资源路径的一个规范问题,解决办法有两种:

    1. 在copy reference出来的文件路径后面加_C,例如:"Blueprint‘/Game/Blueprints/MyBP.MyBP_C‘"(_C可以理解为获取Class的意思)。
    2. 去掉路径前缀,例如:"/Game/Blueprints/MyBP"

资源的软引用



软性引用,即对象 A 通过间接机制(例如字符串形式的对象路径)来引用对象 B。

硬引用的问题是在容易一开始就加载全部硬引用表示的资源,这可能导致资源载入时间过长。而软引用则是可随时灵活加载资源的一种引用,而不用硬性地一开始就加载。

FSoftObjectPaths、FStringAssetReference

  1. FSoftObjectPath:是一个简单的结构体,其中包含了资源的完整名称(一个字符串)。它实质就是用一个字符串来表示对应的资源,从而可以随时通过字符串找到硬盘上的目标资源,将其载入进内存。

    FSoftObjectPath.SolveObject() 可以检查其引用的资源是否已经载入在内存中,若载入则返还资源对象指针,否则返还空。
    FSoftObjectPath.IsPending() 可检查资源是否已准备好可供访问。而如何利用FSoftObjectPath加载资源进内存,后面还会说到。

  2. FStringAssetReference:其实只是一个听起来更容易理解的别名,它实际在UE4源码里是这样的:
typedef FSoftObjectPath FStringAssetReference;

TSoftObjectPtr<T>

TSoftObjectPtr是包含了FSoftObjectPath的TWeakObjectPtr,可通过模板参数来设置特定资源类型,这样就可以限制编辑器UI仅允许选择特定的资源种类。

TSoftObjectPtr.Get() 可以检查其引用的资源是否已经载入在内存中,若已载入则返还资源对象指针,否则返还空。想要资源加载进内存,则可以调用ToSoftObjectPath()来得到FSoftObjectPaths用于加载。

同步加载资源


LoadObject/LoadClass

  1. LoadObject<T>():加载UObject,一般用来加载非蓝图资源。
UTexture2D* Texture2D  = LoadObject<UTexture2D>(nullptr,TEXT("Texture2D'/Game/Textures/tex1.tex1'"));
  1. LoadClass<T>():加载UClass,一般用来加载蓝图资源并获取蓝图Class。实际上源码里LoadClass的实现是调用LoadObject并获取类型。

    • LoadClass的模版名称,和上面FClassFinder一样,不能直接写UBlueprint。
    • LoadClass路径规范也和上面的FClassFinder一样,带_C后缀或去掉前缀。

另外有两个函数叫:StaticLoadObject()和StaticLoadClass(),是LoadObject()和LoadClass()的早期版本,前两者需要手动强转和填写冗杂参数,后两者则是前两者的封装,使用更方便,推荐使用后者。

TSubclassOf<AActor> BPClass = LoadClass<AActor>(nullptr, TEXT("/Game/Blueprints/MyBP"));

此外一提,还有一个可能常用的全局函数FindObject(),用来查询资源是否载入进内存,若存在则返还资源对象指针,否则返还空。但是我们不用先查询再使用LoadXXX,因为LoadXXX里本身就有用到FindObject来检查存在性。

TryLoad/LoadSynchronous

  1. TryLoad():FSoftObjectPaths的方法,直接根据路径加载资源。
  2. LoadSynchronous():TSoftObjectPtr<T>的方法,也是直接根据路径加载资源。

由于软引用里包含资源完整路径名,因此无需再写一次路径名,而是调用如上成员方法来加载资源进内存。而软引用的作用不仅如此,它还可以用于下面要介绍的资源异步加载方式。

异步加载资源



即使可以控制加载资源的时机,但如果加载的资源对象很大(或者同一时刻加载多个资源),还是会造成卡顿,为了避免阻塞主线程,异步加载的方式必不可少。

FStreamableManager.RequestAsyncLoad()

首先,需要创建FStreamableManager,官方建议将它放在某类全局游戏单例对象中,例如使用GameSingletonClassName在DefaultEngine.ini中指定的对象。

  1. FStreamableManager.RequestAsyncLoad():将异步加载一组资源并在完成后调用委托。
void UGameCheatManager::GrantItems()
{
       //获取 FStreamableManager的单例对象引用
       FStreamableManager& Streamable = ...;

       //得到一组软引用
       TArray<FSoftObjectPath> ItemsToStream;
       for(int32 i = 0; i < ItemList.Num(); ++i)
       ItemsToStream.AddUnique(ItemList[i].ToStringReference());

       //根据一组软引用来异步加载一组资源,加载完后调用委托
       Streamable.RequestAsyncLoad(ItemsToStream, FStreamableDelegate::CreateUObject(this, &UGameCheatManager::GrantItemsDeferred));
}

void UGameCheatManager::GrantItemsDeferred()
{
       //do something....
}

FStreamableManager其实也有同步加载的方法:SynchronousLoad()方法将进行一次简单的块加载并返回对象。

卸载资源



如果资源永不再使用,想将资源对象从内存上卸载,代码如下:

Texture2D* mytex; //这里假设mytex合法有效  

mytex->ConditionalBeginDestroy();
mytex = NULL;
GetWorld()->ForceGarbageCollection(true); 

创建对象



UE4的对象(即从UObject派生出来的类对象)最好不要用C++的new/delete,而应使用UE4提供的对象生成方法,要不然继承UObject的垃圾回收能力就无从用处。

创建一般对象

如果有UObject的派生类(非Actor、非Component),那么可使用NewObject()模板函数来创建其实例对象:

UMyObject* MyObject = NewObject<UMyObject>();

创建Actor派生类对象

生成AActor派生类对象不要用NewObject或new,而要用UWorld::SpawnActor()

UWorld* World = GetWorld();
FVector pos(150, 0, 20);
AMyActor* MyActor = World->SpawnActor<AMyActor>(pos,FRotator::ZeroRotator);

注意SpawnActor不能放在构造函数,但是可以放在其他时期的函数里,例如BeginPlay()、Tick()...否则可能会编译后就crash。

创建Component派生类对象

为Actor创建组件,可使用UObject::CreateDefaultSubobject()模板函数

UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera0"));
  • CreateDefaultSubobject必须写在Actor的无参构造函数中,否则crash。
  • CreateDefaultSubobject中的TEXT或者FName参数在同一个Actor中不能重复,否则crash。
  • 一定要添加RegisterComponent(),否则编辑器不会显示。

创建蓝图对象

蓝图由于本质是一种脚本,不是直接的C++类,因此往往需要借助动态类型来生成蓝图对象。所有的加载资源并创建到场景中的方式都离不开SpawnActor这一句代码。

1.通过已确定的父类来生成蓝图对象

AMyActor* spawnActor = GetWorld()->SpawnActor<AMyActor>(AMyActor::StaticClass());  

如果你的蓝图派生于某个C++类,那么可以直接访问该类的StaticClass()并用于SpawnActor来创建蓝图对象。

2.通过UClass生成蓝图对象

    UClass* BPClass = LoadClass<AActor>(nullptr, TEXT("/Game/Blueprints/MyBP"));    //TSubclassOf<AActor>同理
    AActor*?spawnActor?=?GetWorld()->SpawnActor<AActor>(BPClass);

3.通过UObject生成蓝图对象

若得到UObject则需要先转换成UBlueprint,再通过GeneratedClass获取UClass来生成蓝图对象

    FStringAssetReference?asset?=?"Blueprint'/Game/BluePrint/TestObj.TestObj'";??
????UObject*?itemObj?=?asset.ResolveObject();??
????UBlueprint*?gen?=?Cast<UBlueprint>(itemObj);??
????if?(gen?!=?NULL)???
????{??
????????AActor*?spawnActor?=?GetWorld()->SpawnActor<AActor>(gen->GeneratedClass);??
????}

参考



虚幻引擎4 官方文档 | 引用资源

虚幻引擎4 官方文档 | 异步资源加载

UE4:四种加载资源的方式

[UE4]C++实现动态加载的问题:LoadClass()和LoadObject()

Unreal Cook Book:创建对象的的几种姿势(C++)

ue4 c++加载蓝图类,资源方式

[UE4]C++实现动态加载的问题

系列其他文章:Aery的UE4 C++开发之旅系列文章

原文地址:https://www.cnblogs.com/KillerAery/p/12031057.html

时间: 2024-10-12 08:17:18

Aery的UE4 C++游戏开发之旅(4)加载资源&创建对象的相关文章

Aery的UE4 C++游戏开发之旅(3)蓝图

目录 蓝图 蓝图命名规范 蓝图优化 暴露C++至蓝图 暴露C++类 暴露C++属性 暴露C++函数 暴露C++结构体/枚举 暴露C++接口 蓝图和C++的结合方案 使用继承重写蓝图 使用组合重写蓝图 方案比较 参考 蓝图 大家都知道,蓝图是UE4提供的极其容易上手的一种可视化脚本,更具体的就不说了. 纯靠蓝图搭建的UE4游戏是存在的,但是这类游戏往往优化很差(除非游戏玩法本身的性能需求不高).更合适的流程往往需要程序员编写C++代码创建一些蓝图可用元素,而设计师再通过蓝图快速搭建游戏. 蓝图命名

Aery的UE4 C++游戏开发之旅(2)编码规范

目录 C++基础类型规范 命名规范 头文件规范 字符串规范 字符集规范 参考 C++基础类型规范 由于PC.XBOX.PS4等各平台的C++基础类型大小可能不同(实际上绝大部分都是整型类型的大小不同),因此UE4提供了如下可移植基础类型的别名来统一规范类型大小: bool 代表布尔值(不会假定布尔尺寸). TCHAR 代表字符(不会假定TCHAR尺寸). uint8 代表无符号字节(1字节). int8 代表带符号字节(1字节). uint16 代表无符号"短"字符(2字节). int

iOS和tvOS游戏按需加载资源简介

摘要 与iOS 9和watchOS 2一起,苹果引入了一套新的内容分发API,以便节约设备空间,这就是按需加载资源.通过使用按需加载资源,我们可以将特定的应用程序资源托管在苹果的服务器上,然后在需要的时候进行加载.在这个教程中,我将通过开发一个图片查看应用介绍一下按需加载资源的基本用法. tvOS On Demand Reourse 按需加载 iOS开发 目录[-] 介绍 准备工作 1. 按需加载资源 益处 类别 限制 应用分片 删除按需加载资源 2. 分配和指定Tag 3. 访问按需请求资源

iOS开发UI篇—懒加载

iOS开发UI篇—懒加载 1.懒加载基本 懒加载——也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了,如果没有那么再去进行实例化 2.使用懒加载的好处: (1)不必将创建对象的代码全部写在viewDidLoad方法中,代码的可读性更强 (2)每个控件的getter方法中分别负责各自的实例化处理,代码彼此之间的独立性强,松耦合 3.代码示例 1 // 2 // YYViewController.m 3

seajs实现JavaScript 的 模块开发及按模块加载

seajs实现了JavaScript 的 模块开发及按模块加载.用来解决繁琐的js命名冲突,文件依赖等问题,其主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载. 官方文档:http://seajs.org/docs/#docs 首先看看seajs是怎么进行模块开发的.使用seajs基本上只有一个函数"define" fn.define = function(id, deps, factory) { //code of function- } 使用define函数来进行定

iOS开发UI基础—懒加载

iOS开发UI基础-懒加载 1.懒加载基本 懒加载--也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了,如果没有那么再去进行实例化 2.使用懒加载的好处: (1)不必将创建对象的代码全部写在viewDidLoad方法中,代码的可读性更强 (2)每个控件的getter方法中分别负责各自的实例化处理,代码彼此之间的独立性强,松耦合 3.代码示例 1 // 2 // YYViewController.m

iOS开发网络篇 —— OC加载HTML代码

html代码 图1 样式一:"<p><img src=\"/upload/image/20170609/1496978712941664.jpg\" title=\"1496978712941664.jpg\" alt=\"7.jpg\"/>测试内容信息无错</p>" 样式二:<h1 style=\"font-size: 32px; font-weight: bold; bo

【前端开发】提高网站加载速度

尊重原创:但是出处不明...... YSlow是yahoo美国开发的一个页面评分插件,非常的棒,从中我们可以看出我们页面上的很多不足,并且可以知道我们改怎么却改进和优化. 仔细研究了下YSlow跌评分规则主要有12条: 1. Make fewer HTTP requests 尽可能少的http请求..我们有141个请求(其中15个JS请求,3个CSS请求,47个CSS background images请求),多的可怕.思考了下,为什么把这个三种请求过多列为对页面加载的重要不利因素呢,而过多的I

iOS开发UI中懒加载的使用方法

1.懒加载基本 懒加载——也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其getter方法.说的通俗一点,就是在开发中,当程序中需要利用的资源时.在程序启动的时候不加载资源,只有在运行当需要一些资源时,再去加载这些资源. 我们知道iOS设备的内存有限,如果在程序在启动后就一次性加载将来会用到的所有资源,那么就有可能会耗尽iOS设备的内存.这些资源例如大量数据,图片,音频等等,所以我们在使用懒加载的时候一定要注意先判断是否已经有了,如果没有那么再去进行实例化 2.使