之前说过,UE4对C++进行了扩展增强,使其便于游戏开发。下面就简要介绍增加的特性。
反射系统(Unreal Reflection System)
内置的逻辑类利用了特殊的标记,所以先概览一下Unreal的原型系统。UE4自己实现了一套反射系统,动态支持诸如垃圾回收、序列化、网络复制、Blueprint/C++通讯等特性。这些特性是可选的,意味着你必须先给你的类型加上标记才能使用,不然Unreal不会给它们生成反射数据。下面列了几个基本的标记:
- UCLASS() - 为class生成反射数据。该类必须是派生于
UObject
。 - USTRUCT() - 为struct生成反射数据。
- GENERATED_BODY() - UE4会在此换成为该类型生成的模版代码。
- UPROPERTY() - 让UCLASS/USTRUCT中的成员变量作为UPROPERTY。UPROPERTY可让变量用于复制、序列化并从Blueprint中可访问,同时受垃圾回收器追踪。
- UFUNCTION() - 让UCLASS/USTRUCT中的成员方法作为UFUNCTION。UFUNCTION可让方法从Blueprint中调用,用作RPCs等等。
举个代码示例:
#include "MyObject.generated.h"
UCLASS(Blueprintable)
class UMyObject : public UObject
{
GENERATED_BODY()
public:
MyUObject();
UPROPERTY(BlueprintReadOnly, EditAnywhere)
float ExampleProperty;
UFUNCTION(BlueprintCallable)
void ExampleFunction();
};
先注意头文件"MyObject.generated.h"
,Unreal生成的所有反射数据都在此文件里。需要把该文件包括在头文件列表最末。
标记里带有说明符,用于明确指定类型的行为。
- Blueprintable - 该类暴露给 Blueprint
- BlueprintReadOnly - 该属性在Blueprint中只读不可写
- Category - 确定该属性在编辑器的详细面板中显示在哪个分类下,方便管理
- BlueprintCallable - Blueprint中可调用该函数
引擎里说明符非常多,分类查阅参考:
至于反射系统更详细的解读,比如其实现原理之类的,参阅官方博客
Object/Actor 迭代器
object迭代器用于遍历一个UObject类型的所有实例(子类)时还是很方便有用的。
// Will find ALL current UObjects instances
for (TObjectIterator<UObject> It; It; ++It)
{
UObject* CurrentObject = *It;
UE_LOG(LogTemp, Log, TEXT("Found UObject named: %s"), *CurrentObject.GetName());
}
给迭代器提供范围时,注意该类型需要派生自UObject
,假设你有个派生于UObject的类叫UMyClass
,使用迭代器,可以遍历其所有实例(与子类):
for (TObjectIterator<UMyClass> It; It; ++It)
{
// ...
}
actor迭代器用法和object迭代器一样,但只能用于派生自AActor
的对象。返回的只有当前游戏实例中使用的对象。所以,使用actor迭代器时,需要提供一个UWorld
实例。大部分UObject类比如APlayerController
都提供GetWorld
方法来方便获取。如果不确定,还可以先用ImplementsGetWorld
方法检查一下该UObject对象是否实现了GetWorld
方法。
APlayerController* MyPC = GetMyPlayerControllerFromSomewhere();
UWorld* World = MyPC->GetWorld();
// Like object iterators, you can provide a specific class to get only objects that are
// or derive from that class
for (TActorIterator<AEnemy> It(World); It; ++It)
{
// ...
}
内存管理与垃圾回收
介绍点UE4中基本的内存管理机制和垃圾回收系统。
UObjects与垃圾回收
UE4使用反射系统实现垃圾回收系统。受助于垃圾回收,你不再手动管理删除各类UObject,只需维持他们的合法引用即可。只有派生自UObject
的类才能利用垃圾回收系统。
UCLASS()
class MyGCType : public UObject
{
GENERATED_BODY()
};
在垃圾回收器中,有个叫根集合
的概念。根集合中包含一系列回收器永远不会回收的对象。只要某个子对象能通过引用路径上溯到根集合中的对象,则该子对象也不会被回收。反之,该子对象处于不可及
的状态,将会在下一个垃圾回收周期中被回收(删除)。垃圾回收器会在固定间隔内运行一次。
何为“引用”计数?就是任何UObject指针必须存储在UPROPERTY中。举个简单的例子。
void CreateDoomedObject()
{
MyGCType* DoomedObject = NewObject<MyGCType>();
}
当调用上述函数时,新建了一个UObject,但我们并没有将其指针存储在某个UPROPERTY中,意味着该对象不属于根集合。最终,垃圾回收器将检测到该对象不可及并销毁它。
Actors与垃圾回收
actor不完全受垃圾回收控制。播种(spawn)出来的actor必须手动调用其Destroy()
函数。调用之后也不会立即删除,而是在下一个垃圾回收阶段进行清理。
下面列一个常见的情况,actor拥有部分UObject属性。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY()
MyGCType* SafeObject;
MyGCType* DoomedObject;
AMyActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SafeObject = NewObject<MyGCType>();
DoomedObject = NewObject<MyGCType>();
}
};
void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
World->SpawnActor<AMyActor>(Location, Rotation);
}
调用上述函数,将在游戏世界诞生一个actor。该actor构造函数中会创建两个对象。一个分配了UPROPERTY,另一个没有。由于actor会自动加入根集合,SafeObject
因为可以关联到根集合,将不被当作垃圾而回收;而DoomedObject
就不行。我们没有用UPROPERTY
标记它(DoomedObject),垃圾回收器不知道它被引用,于是销毁该对象。
当某个UObject回收后,所有引用该对象的UPROPERTY会置空(nullptr)。这样可以安全检测某对象是否已被回收。
if (MyActor->SafeObject != nullptr)
{
// Use SafeObject
}
特别注意前面一段提醒:actor调用Destroy()函数后不会立即清除,而是在下一次垃圾回收器工作时才清理。可以使用IsPendingKill()
函数来检测某对象是否处于等待清理的状态。函数返回true的话,就不要使用它了。
UStructs
UStruct可以看成轻量级UObject,但不受垃圾回收管理。如果要使用动态UStruct实例,最好利用智能指针。
非UObject引用
一般来说,非UObject对象可以添加一个引用来阻止被垃圾回收掉。前提是,你的类要继承自FGCObject
并重载AddReferencedObjects
。
class FMyNormalClass : public FGCObject
{
public:
UObject* SafeObject;
FMyNormalClass(UObject* Object)
: SafeObject(Object)
{
}
void AddReferencedObjects(FReferenceCollector& Collector) override
{
Collector.AddReferencedObject(SafeObject);
}
};
使用FReferenceCollector
手动添加一条对UObject的引用以拒绝垃圾回收。当该对象被删除,调用析构函数时,对象会自动清理身上所有的引用。
总结
- UE4的反射系统实现原理暂且不表,使用方式还是简洁明了的。简单加上宏标记即可。
- 通过反射系统,C++、编辑器、Blueprint之间协同工作。
- 内置object迭代器(TObjectIterator)和actor迭代器(TActorIterator),用来遍历对象很方便。
- 派生自UObject的类自动支持垃圾回收;非UObject类可通过其它方式避免被回收;UStruct不支持垃圾回收,其他与UObject无二。
- 垃圾回收机制是简单的标记-清除模式,以根集合为起点,遍历所有对象,不能达到(没标记)的就回收掉。(和Lua的垃圾回收机制很像。)