虚幻引擎版本: Unreal Engine 5.6
编辑器: Rider For Unreal
在阅读本文之前希望你对虚幻引擎以及蓝图有基本的了解

0. 引言

当我们打开几乎任意一个虚幻项目的C++代码(如下),都可以看到一些很“奇怪”的宏,诸如:UCLASSGENERATED_BODYUPROPERTY以及UFUNCTION。它们当中一些还可以携带参数,这些宏在虚幻(UE C++)中到底起了什么作用呢?这篇文章会带大家了解这些宏背后的内容,以及它们的常见用法。这是我们开启UE C++ 旅程的必备知识。

UCLASS(Abstract, Blueprintable)
class PARROT_API AParrotPlayerCharacter : public AParrotCharacterBase
{
	GENERATED_BODY()
public:

	// When true, the player wants to jump
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Parrot|Input")
	bool IsJumpInputActive() const { return bPressedJump; }
	virtual void CheckJumpInput(float DeltaTime) override; 
	virtual bool CanJumpInternal_Implementation() const override;

	UFUNCTION(BlueprintCallable)
	bool IsEnemyJumpValid(UBoxComponent* HurtBox); 

	// Executes a jump from an enemy's hurt box
	void JumpFromEnemyHurtBox(); 
public:
	UPROPERTY(BlueprintAssignable)
	FOnHitpointsAdded OnHitpointsAdded; 
protected:
	UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, meta = (ClampMin = "0.0", UIMin = "0.0", Category = "Parrot|Character|Stats"))
	float HitStunDuration = 0.0f; 

	// When true, allows the player to be stunned in mid-air 
	UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, meta = (EditConditionHides, EditCondition = "HitStunDuration > 0", Category = "Parrot|Character|Stats"))
	bool bStunMidAir = false; 

	// A duration that the player is invulnerable after being hit
	UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, meta = (ClampMin = "0.0", UIMin = "0.0", Category = "Parrot|Character|Stats"))
	float HitInvulnerabilityDuration = 0.0f; 
	UPROPERTY(BlueprintReadOnly, Category = "Parrot|Player|Powerups")
	bool bIsSpeedPowerupActive;  
	FTimerHandle TimerHandle_SpeedPowerup; 
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	virtual void CharacterDeath() override;
	void StopSpeedPowerup();

	// Called when the hit stun timer has completed
	void StopHitStun();
	void StopHitInvulnerability();
	UFUNCTION(BlueprintCallable, Category = "Parrot|Player|Powerups")
	void AddHitpoints(int32 PointsToAdd); 
	UFUNCTION(BlueprintCallable, Category = "Parrot|Player|Powerups")
	void ActivateSpeedPowerup(float Duration, float MaxSpeedMultiplier); 
	UFUNCTION(BlueprintImplementableEvent, Category = "Parrot|Player|Powerups")
	void OnSpeedPowerupActivated(float Duration); 
	UFUNCTION(BlueprintImplementableEvent, Category = "Parrot|Player|State")
	void OnEnemyJump(); 
};

1. 反射系统(Reflection System)和 UHT(Unreal Header Tool)系统

文章标题中列举的宏实际上隶属于两个系统:

  • 反射系统(Reflection System):UCLASSUPROPERTYUFUNCTION
  • UHT(Unreal Header Tool)系统:GENERATED_BODY

1.1 反射系统(Reflection System)

UCLASS(Abstract, Blueprintable)
class PARROT_API AParrotPlayerCharacter : public AParrotCharacterBase
{
	UPROPERTY(BlueprintReadOnly, Category = "Parrot|Player|Powerups")
	bool bIsSpeedPowerupActive;  
	UFUNCTION(BlueprintImplementableEvent, Category = "Parrot|Player|State")
	void OnEnemyJump(); 
public:
	...
}

再聊宏之前,不得不先说说UE C++的反射系统(Reflection System)。虚幻引擎的设计初衷是面向AAA级甚至元宇宙级的超大型游戏项目,它之所以选择C++作为其最核心也是最底层的开发语言,其根本原因就是“快”!这个快是针对程序运行时(Runtime)而言的,在极致压榨机器性能,让程序跑得“快”这方面,称C++为程序语言之王是当之无愧的。但凡事皆有利弊,相对于诸如C#,Python等很多更现代的语言来讲,C++损失了很多使用上的便利性以及增加了开发难度和门槛,比如:垃圾回收(GC)、元数据(Metadata)访问等等方面,并且虚幻还需要让C++代码定义的属性以及函数,可以在关卡编辑器中显示,以及和蓝图的各种交互等,这一切都是纯C++很难完成的。

于是,为了虚幻引擎团队为了降低开发难度并提高安全性、稳定性,在纯C++基础上借助C++的宏机制构建了一套反射系统。有了它,UE C++在保持运行时高效率的同时大大提升了开发体验。因此,当初学者看到那些奇怪的宏,没有必要感到压力,相反再后续的开发中这些宏会给我们提供很大的便利。如果你使用过C#,这些反射宏的使用体验很类似于C#中的Attribute

在文章的后半部分,我会列举出常用的反射宏使用方法。

1.2 UHT(Unreal Header Tool)系统

虚幻头文件分析工具(UHT) 是虚幻引擎的一种自定义解析和代码生成工具。UHT可为虚幻引擎(UE)的 UObject 系统解析并生成代码。虚幻引擎中的代码编译分两个阶段进行:虚幻编译工具(Unreal Build Tool (UBT)) 会调用UHT,后者将解析C++头文件,获取与虚幻引擎相关的类元数据,并生成自定义代码,以实现各种与UObject相关的功能。UBT会调用配置的C++编译器来编译结果。总的来说,对于初学者,我们把UHT理解为一中帮我们生成必要的C++代码的工具就可以了。

UCLASS(Abstract, Blueprintable)
class PARROT_API AParrotPlayerCharacter : public AParrotCharacterBase
{
	GENERATED_BODY()
public:
	...
}

这里的宏GENERATED_BODY()就是告诉UHT,让它来为我们生成一些必要的辅助代码。

2. 属性说明符UPROPERTY

参考官方文档:UPROPERTY

属性标签 效果
AdvancedDisplay 该属性将被置于显示属性的任何面板的高级(下拉)分段中。
AssetRegistrySearchable AssetRegistrySearchable 说明符表示,此属性及其值将被自动添加到将此属性纳为成员变量的资产类实例的
BlueprintAssignable 仅适用于组播委托。公开该属性,以便在蓝图中指定。
BlueprintAuthorityOnly 此属性必须是组播委托。在蓝图中,此属性只接受标记为 BlueprintAuthorityOnly 的事件。
BlueprintCallable 仅限组播委托。应公开属性以便在蓝图代码中调用。
BlueprintGetter=GetterFunctionName 此属性指定自定义访问函数。如果此属性不带 BlueprintSetter 或 BlueprintReadWrite 标记,则暗指
BlueprintReadOnly 蓝图可以读取此属性,但无法修改。此说明符与 BlueprintReadWrite 说明符不兼容。
BlueprintReadWrite 可以从蓝图读取或写入此属性。此说明符与 BlueprintReadOnly 说明符不兼容。
BlueprintSetter=SetterFunctionName 此属性具有自定义变异函数,并被隐式标记为 BlueprintReadWrite 。请注意,变异函数必须被命名并且属于同一个类。
Category=“TopCategory|SubCategory|…” 指定在蓝图编辑工具中显示的属性类别。使用
Config 此属性将变为可配置。当前值可以保存到与类关联的 .ini 文件中,并在创建时加载。无法在默认属性中被赋值。暗指 BlueprintReadOnly 。
DuplicateTransient 表示在任何类型的复制(复制/粘贴、二进制复制等)过程中,应将属性的值重置为类默认值。
EditAnywhere 表示可以通过属性窗口在原型和实例上编辑此属性。此说明符与任何"Visible"类说明符都不兼容。
EditDefaultsOnly 表示该属性可以通过属性窗口编辑,但仅限于在原型上编辑。此说明符与任何"Visible"类说明符都不兼容。
EditFixedSize 仅对动态数组有用。这将阻止用户通过虚幻编辑器属性窗口更改数组的长度。
EditInstanceOnly 表示此属性可以通过属性窗口编辑,但仅限于实例,而不能在原型上编辑。此说明符与任何"Visible"类说明符都不兼容。
Export 仅对对象属性(或对象数组)有用。表示在复制对象(例如复制/粘贴操作)时,应将分配给此属性的对象作为子对象块完整导出,而不是仅输出对象引用本身。
GlobalConfig 其运行方式与 Config 类似,只是你不能在子类中重载它。无法在默认属性中被赋值。暗指 BlueprintReadOnly .
Instanced 仅限对象(UCLASS)属性。当创建此类的实例时,将被赋予默认情况下分配给此属性的对象的唯一副本。用于实例化类默认属性中定义的子对象。暗指 EditInline 和 Export 。
Interp 表示该值可由Sequencer中的轨道随着时间的推移而驱动。
Localized 此属性的值将具有定义的本地化值。主要用于字符串。暗指 ReadOnly 。
Native 属性是原生的:C++代码负责将其序列化并公开给垃圾回收。
NoClear 防止编辑器将此对象引用设置为无。隐藏编辑器中的清除(和浏览)按钮。
NoExport 仅对原生类有用。此属性不应包含在自动生成的类声明中。
NonPIEDuplicateTransient 该属性将在复制期间重置为默认值,除非属性是为"在编辑器中运行(PIE)"会话而复制。
NonTransactional 表示对此属性值的更改将不会包含在编辑器的撤消/重做历史记录中。
NotReplicated 跳过复制。这仅适用于服务请求函数中的结构成员和参数。
Replicated 该属性应通过网络复制。
ReplicatedUsing=FunctionName ReplicatedUsing 说明符指定了一个回调函数,当属性通过网络更新时执行该函数。
RepRetry 仅适用于结构体属性。如果无法完全发送(例如,对象引用尚无法通过网络序列化),则重试复制此属性。对于简单引用,这是默认值,但对于结构体,考虑到带宽成本,这通常不可取,因此除非指定此标记,否则它会被禁用。
SaveGame 此说明符可以用来轻松在属性级别显式包含检查点/保存系统的字段。应该在旨在作为已保存游戏一部分的所有字段上设置该标记,然后可以使用代理归档器来读取/写入该标记。
SerializeText 原生属性应该被序列化为文本( ImportText 、 ExportText )。
SkipSerialization 此属性不会被序列化,但仍可以导出为文本格式(例如用于复制/粘贴操作)。
SimpleDisplay 可见或可编辑的属性,显示在 细节(Details) 面板中,无需打开"高级(Advanced)"分段即可看到。
TextExportTransient 此属性不会被导出为文本格式(因此,不能用于复制/粘贴等操作)。
Transient 属性是临时的,这意味着既不会被保存,也不会被加载。以此方式标记的属性将在加载时以零填充。
VisibleAnywhere 表示此属性在所有属性窗口中可见,但不可编辑。此说明符与"Edit"类说明符不兼容。
VisibleDefaultsOnly 表示此属性仅在原型的属性窗口中可见,并且无法编辑。此说明符与任何"Edit"类说明符都不兼容。
VisibleInstanceOnly 表示此属性仅在实例的属性窗口中可见,在原型的属性窗口中不可见,并且不可编辑。此说明符与任何"Edit"类说明符都不兼容。

3. 函数说明符UFunction

参考官方文档:UFunction

函数说明符 效果
BlueprintAuthorityOnly 如果在具有网络权限的机器上运行(服务器、专用服务器或单人游戏),此函数将仅从蓝图代码执行。
BlueprintCallable 此函数可在蓝图或关卡蓝图图表中执行。
BlueprintCosmetic 此函数为修饰性的,无法在专用服务器上运行。
BlueprintImplementableEvent 此函数可在蓝图或关卡蓝图图表中实现。
BlueprintNativeEvent 此函数旨在被蓝图覆盖掉,但是也具有默认原生实现。用于声明名称与主函数相同的附加函数,但是末尾添加了_Implementation,是写入代码的位置。如果未找到任何蓝图覆盖,该自动生成的代码将调用 _Implementation 方法。
BlueprintPure 此函数不对拥有它的对象产生任何影响,可在蓝图或关卡蓝图图表中执行。早默认情况下,带有 const 标记的函数将作为纯函数公开。要将常量函数变成非纯函数,你可以做以下声明:BlueprintPure=false
CallInEditor 可通过细节(Details)面板`中的按钮在编辑器中的选定实例上调用此函数。
Category = “TopCategory|SubCategory|Etc” 在蓝图编辑工具中显示时指定函数的类别。使用
Client 此函数仅在拥有在其上调用此函数的对象的客户端上执行。用于声明名称与主函数相同的附加函数,但是末尾添加了_Implementation。必要时,此自动生成的代码将调用 _Implementation 方法。
CustomThunk UnrealHeaderTool 代码生成器将不为此函数生成thunk,用户需要自己通过 DECLARE_FUNCTION 或 DEFINE_FUNCTION 宏来提供thunk。
Exec 此函数可从游戏内控制台执行。仅在特定类中声明时,Exec命令才有效。
NetMulticast 此函数将在服务器上本地执行,也将复制到所有客户端上,无论该Actor的 NetOwner 为何。
Reliable 此函数将通过网络复制,并且一定会到达,即使出现带宽或网络错误。仅在与Client或Server配合使用时才有效。
SealedEvent 无法在子类中覆盖此函数。SealedEvent关键词只能用于事件。对于非事件函数,请将它们声明为static或final,以密封它们。
ServiceRequest 此函数为RPC(远程过程调用)服务请求。这意味着 NetMulticast 和 Reliable。
ServiceResponse 此函数为RPC服务响应。这意味着 NetMulticast 和 Reliable。
Server 此函数仅在服务器上执行。用于声明名称与主函数相同的附加函数,但是末尾添加了 _Implementation,是写入代码的位置。必要时,此自动生成的代码将调用 _Implementation 方法。
Unreliable 此函数将通过网络复制,但是可能会因带宽限制或网络错误而失败。仅在与Client或Server配合使用时才有效。
WithValidation 用于声明名称与主函数相同的附加函数,但是末尾需要添加_Validate。此函数使用相同的参数,但是会返回bool,以指示是否应继续调用主函数。
Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐