大家好! 我的名字叫亚历山大(Alexander),我从事虚幻引擎(Unreal Engine)的工作已经超过5年了,并且几乎一直都在从事网络项目。
由于网络项目的开发和性能要求不同,因此通常有必要使用更简单的对象(例如UObject类),但是它们的功能最初会被截断,这会创建一个强大的框架。 在本文中,我将讨论如何在虚幻引擎4的UObject基类中激活各种功能。

实际上,我写这篇文章更多是作为参考。 大多数信息在文档或社区中都很难找到,在这里您可以快速打开链接并复制所需的代码。 我决定同时与您分享! 本文针对那些已经稍微熟悉UE4的人。 将考虑使用C ++代码,尽管没有必要知道它。 如果您需要谈论什么,可以按照说明进行操作。 而且,没有必要复制所有内容,您可以使用必要的属性粘贴该部分中的代码,它应该可以工作。
关于UObject的一些知识
UObject是虚幻引擎4中几乎所有事物的基类。在您的世界或仅在内存中创建的绝大多数对象都继承自它:舞台上的对象(AActor),组件(UActorComponent),用于处理的不同类型数据和其他。
尽管类本身比派生类更容易,但它同时具有相当的功能。 例如,它包含许多有用的事件,例如更改编辑器中的变量值和网络的基本功能,这些事件默认情况下不处于活动状态。
此类创建的对象不能在舞台上,并且只能存在于内存中。 它们不能作为组件添加到Actor,但是如果您自己实现必要的功能,它们可以是一种组件。
如果AActor已经支持我需要的一切,为什么我需要UObject? 通常,有很多使用示例。 最简单的是库存物品。 在舞台上的天空中,将它们存储是不切实际的,因此您可以将它们存储在内存中而无需加载渲染,也无需创建不必要的属性。 对于喜欢技术比较的人,AActor占用了一个千字节(1016字节),而空的UObject只有56个字节。
什么是UObject问题?
总体上没有问题,很好,或者我只是没有遇到过。 使UObject烦恼的是缺少AActor或组件中默认可用的各种功能。 这是我为自己的实践发现的问题:
- UObject不能通过网络复制;
- 由于第一点,我们无法触发RPC事件;
- 您不能使用需要链接到“蓝图”中的世界的广泛功能;
- 他们没有像BeginPlay和Tick这样的标准事件;
- 您不能将组件从UObjects添加到Blueprints中的AActor。
大多数事情都可以轻松解决。 但是有些将不得不修补。
创建UObject
在扩展具有功能的类之前,我们需要创建它。 让我们使用编辑器,以便生成器自动将工作所需的所有内容写入标头(.h)。
我们可以在Content Browser编辑器中通过单击
New按钮并选择
New C ++ Class创建一个新类。

接下来,我们需要选择类本身。 它可能不在常规列表中,因此,将其打开并选择UObject。

为您的班级命名,然后选择将其存储在哪个文件夹中。 当我们创建类时,您可以进入工作室,在那里找到它并开始嵌入所有必要的功能。
初学者请注意,将创建两个文件:.h和.ccp。 在.h中,您将声明变量和函数,在.cpp中,您将定义它们的逻辑。 在您的项目中找到两个文件。 如果您没有更改路径,则它们应该位于Project / Source / Project /中。在继续之前,让我们在类声明上方的UCLASS()宏中写入
Blueprintable参数。 您应该得到这样的内容:
.hUCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() }
因此,您可以创建蓝图,该蓝图将继承我们对该对象所做的所有操作。
UObject复制
默认情况下,不通过网络复制UObject。 如上所述,当您需要在各方之间同步数据或逻辑但不将垃圾存储在世界上时,会创建许多限制。
在虚幻引擎4中,复制正是由于世界对象而发生的。 这意味着仅在内存中创建对象并对其进行复制将失败。 无论如何,您都需要一个所有者来管理服务器和客户端之间的对象数据传输。 例如,如果您的对象是角色的技能,则角色本身应成为所有者。 他还将成为通过网络传输信息的指挥。
准备要复制的对象。 到目前为止,在标头中,我们只需要设置一个函数:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() public: virtual bool IsSupportedForNetworking () const override { return true; }; }
IsSupportedForNetworking()将确定该对象支持网络并且可以复制。
但是,并非一切都那么简单。 正如我在上面所写,您需要一个所有者来控制对象的传输。 为了保证实验的纯度,请创建一个将其复制的AActor。 这可以通过与UObject完全相同的方式来完成,只有父类(自然是AActor)可以完成。
初学者,如果您需要在字符,控制器或其他地方复制对象,请通过编辑器创建适当的基类,向其添加必要的逻辑,并已从Blueprints中的该类继承。在内部,我们需要3个函数:一个构造函数,一个用于复制子对象的函数,一个确定在此AActor内部复制的对象的函数(变量,对象引用等)以及创建对象的位置。
不要忘记创建一个变量来存储我们的对象:
.h class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: AMyActor(); virtual bool ReplicateSubobjects (class UActorChannel *Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; void GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) const override; virtual void BeginPlay (); UPROPERTY(Replicated, BlueprintReadOnly, Category="Object") class UMyObject* MyObject; }
在源文件中,我们必须编写所有内容:
.cpp
现在,您的对象将与此Actor复制。 您可以在对勾上显示他的名字,但已经在客户端上显示了。 请注意,在开始播放时,对象不太可能在客户端之前到达,因此在该对象上写日志是没有意义的。
在UObject中复制变量
在大多数情况下,如果对象不包含也会在服务器和客户端之间同步的信息,则复制对象是没有意义的。 由于我们的对象已被复制,因此传递变量并不困难。 这可以通过与Actor内部相同的方式完成:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY() public: virtual bool IsSupportedForNetworking () const override { return true; }; void GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) const override; UPROPERTY(Replicated, BlueprintReadWrite, Category="Object") int MyInteger;
.cpp
通过添加变量并将其标记为要复制,我们可以复制它。 一切都很简单,与AActor中的相同。
但是,存在一个小的陷阱,它不会立即可见,但可能会引起误解。 如果您创建的UObject不是在C ++中工作,而是在继承中进行准备并在Blueprints中工作,则这将特别值得注意。
最重要的是,将不会复制在蓝图继承人中创建的变量。 引擎不会自动标记它们,并且在BP中更改服务器上的参数不会更改客户端上的值。 但是有解决办法。 为了正确复制BP变量,您需要预先标记它们。 在GetLifetimeReplicatedProps()中添加几行:
.cpp void UMyObject ::GetLifetimeReplicatedProps (TArray<FLifetimeProperty>& OutLifetimeProps) { Super::GetLifetimeReplicatedProps(OutLifetimeProps);
现在,子级Blueprint类中的变量将按预期复制。
UObject中的RPC事件
RPC(远程过程调用)事件是在项目网络交互的另一端调用的特殊功能。 使用它们,您可以从其他客户端上的服务器以及服务器上的客户端调用该函数。 编写网络项目时非常有用,经常使用。
如果您不熟悉它们,建议阅读一篇文章。 它描述了C ++和Blueprints中的用法 。尽管Actor或组件的调用没有问题,但在UObject事件中,它们在被调用的那一侧触发,这使得在需要时无法进行远程调用。
查看组件代码(UActorComponent),我们可以找到几个函数,使您可以通过网络转移呼叫。 由于UActorComponent是从UObject继承的,因此我们可以简单地复制必要的代码部分并将其粘贴到我们的对象中,以便它可以正常工作:
.h
.cpp
使用这些功能,我们不仅可以在代码中而且可以在蓝图中触发RPC事件。
请注意,为了触发客户端或服务器事件,您需要一个拥有者,其拥有者是我们的播放器。 例如,对象由用户角色拥有,或者所有者是播放器的播放器控制器的对象拥有。
蓝图的全局功能
如果您曾经创建过一个对象蓝图,您可能已经注意到,您无法调用其他类中可用的全局函数(静态的,但是为了清楚起见,我们称之为全局函数),例如GetGamemode()。 看来您根本无法在Object类中执行类,因此您要么必须在创建时传递所有链接,要么以某种方式变态,有时选择完全取决于在舞台上创建的Actor类,并且支持一切。
但是,在C ++中,当然没有此类问题。 但是,玩这些设置并添加其他小东西的游戏设计人员不能说您需要打开Visual Studio,找到适当的类并通过更改其中的点在doSomething()函数中获得游戏模式。 因此,设计师必须登录到Bluprint,然后单击两次即可完成其工作。 节省他和你的时间。 但是,是为此发明了蓝图。
最重要的是,当您在Bluprint的上下文菜单中查找或调用函数时,那些需要引用世界的相同全局函数会尝试在引用该函数的对象内部调用一个函数。 并且,如果编辑者发现没有功能,则表示他无法使用该功能,因此不会在列表中显示。

但是,有解决办法。 甚至两个。
首先让我们考虑一个在编辑器中更方便使用的选项。 我们将需要重新定义一个返回到世界的链接的函数,然后编辑器将理解在游戏本身中它可以工作:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY()
.cpp UWorld* UMyObject::GetWorld() const {
现在定义好了,编辑器将理解,通常该对象能够获得所需的指针(尽管它是无效的)并在BP中使用全局函数。
请注意,所有者(GetOuter())也必须具有访问世界的权限。 它可以是场景中具有特定GetWorld(),组件或Actor对象的另一个UObject。
还有另一种方式。 在声明类将WorldContextObject参数添加到BP中的静态函数时,只需在UCLASS()宏上添加标签就足够了,任何充当“世界”和引擎的全局函数的对象都将馈入该对象。 此选项适用于那些在项目中可以同时拥有多个世界的人(例如,游戏世界和旁观者的世界):
.h
如果在BP的搜索中输入GetGamemode,它将像其他类似函数一样出现在列表中,并且参数将是WorldContextObject,您需要在其中传递指向Actor的链接。

顺便说一句,您可以在此处向我们的财产所有人提交文件。 我建议在Actor上创建一个函数,它将对对象始终有用:
.h UCLASS(Blueprintable, meta=(ShowWorldContextPin)) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY()
现在,您可以简单地将全局函数与我们的Pure函数结合使用以获得所有者。

如果您也像在第一个变量中一样在第二个变量中声明GetWorld(),则可以在WorldContextObject参数中提交对您自己的引用(Self或This)。

BeginPlay和Tick事件
蓝图开发人员可能遇到的另一个问题是Object类中没有BeginPlay和Tick事件。 当然,您可以自己创建它们,并从另一个类进行调用。 但是您必须承认,开箱即用时它更加方便。
让我们首先了解如何制作“开始播放”。 我们可以创建一个可用于在BP中重写的函数,并在类的构造函数中调用它,但是存在许多问题,因为在构造函数时,您的对象尚未完全初始化。
在所有类中,都有PostInitProperties()函数,该函数在大多数参数初始化和对象在各种内部系统(例如,垃圾收集器)中的注册之后被调用。 在其中,您可以调用我们的事件,该事件将在蓝图中使用:
.h UCLASS(Blueprintable) class MYPROPJECT_API UMyObject : public UObject { GENERATED_BODY()
.cpp void UMyObject::PostInitProperties() { Super::PostInitProperties();
如果您已经重新定义了if(GetWorld()),则可以简单地放置if(GetOuter()&& GetOuter()-> GetWorld())。
小心点! 默认情况下,在编辑器中也会调用PostInitProperties()。
现在,我们可以进入BP对象并调用BeginPlay事件。 创建对象时将调用它。
让我们继续进行事件标记。 我们没有简单的功能。 引擎中的滴答对象会呼叫一个特殊管理器,您需要以某种方式将其拾取。 但是,这里有一个非常方便的把戏-FTickableGameObject的其他继承。 这将使您能够自动执行所需的一切,然后仅需选择必要的功能就足够了:
.h
.cpp void UMyObject::Tick(float DeltaTime) {
如果从对象继承并创建BP类,则EventTick事件将可用,这将导致每个帧的逻辑。
从UObjects添加组件
在UObject蓝图中,不能为Actor生成组件。 ActorComponent的蓝图固有的问题是相同的。 Epic Games的逻辑不是很清楚,因为在C ++中可以做到这一点。 此外,您可以通过简单地指定链接将Actor中的组件添加到另一个Actor对象中。 但这无法完成。
不幸的是,我无法弄清楚这个项目。 如果有人对如何操作有指示,我将很高兴在此处发布。
目前,我唯一可以提供的选择是在UObject类中包装一个包装,从而提供对简单添加组件的访问。 因此,可以将组件添加到Actor,但是您将没有动态创建生成的输入参数。 通常,这可以忽略。
通过编辑器设置实例
在UE4中,还有另一个方便的“功能”用于处理对象-这是一种在初始化期间创建实例并通过编辑器更改其参数,从而设置其属性的能力,而无需仅出于设置目的而创建子类。 对游戏设计师特别有用。
假设您有一个角色的修饰符管理器,修饰符本身由描述叠加效果的类表示。 游戏设计师创建了一对修饰符,并在管理器中指示使用了哪些修饰符。
在正常情况下,它将如下所示:
.h class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: UPROPERTY(EditAnywhere) TSubclassOf<class UMyObject> MyObjectClass; }

但是,存在一个问题,它无法配置修饰符,并且您必须为其他值创建一个附加类。 同意,在内容浏览器中有数十个仅值不同的类不是很方便。 解决这个问题很容易。 您可以在USTRUCT()中添加几个字段,还可以在容器对象中指示我们的对象将是实例,而不仅仅是对不存在的对象或类的引用:
.h UCLASS(Blueprintable, DefaultToInstanced, EditInlineNew)
仅此一项是不够的,现在必须指出与该类相同的变量将是一个实例。 例如,在字符修饰符管理器中,存储对象的位置已经完成:
.h class MYPROPJECT_API AMyActor : public AActor { GENERATED_BODY() public: UPROPERTY(EditAnywhere, Instanced)
请注意,我们使用的是对对象的引用,而不是对类的引用,因为实例将在初始化后立即创建。 现在,我们可以进入编辑器窗口来选择一个类并调整实例中的值。 它更加方便和灵活。

资讯
虚幻引擎中还有另一个有趣的类。 这是AInfo。 从AActor继承的类,在世界上没有视觉表示。 信息使用诸如游戏模式,GameState,PlayerState等类。 就是说,支持与AActor不同的芯片的类(例如,复制),但不在现场。
如果您需要创建一个额外的全局管理器来支持网络和所有由此产生的Actor类,则可以使用它。 您不必如上所述操纵UObject类来强制它,例如,复制数据。
但是,请记住,尽管该对象没有坐标,没有视觉组件,也无法在屏幕上呈现,但它仍然是Actor类的后代,这意味着它与父级一样重。 合理地少量使用和方便使用。
结论
UObject经常需要使用,我建议您在不需要Actor的任何时候使用它。 遗憾的是它有点有限,但是它也是一个优点。 有时,您需要修改使用自定义模板的时间,但最重要的是,可以消除所有主要限制。
如果您经常使用蓝图中的对象,但又不想不断创建类并向其中添加这些功能,则可以简单地创建一个UObject类,并支持项目中可能需要的所有内容,然后从中创建子打印并工作 .
, , Unreal Engine 4. - , . , - , UObject.