本篇文章纯属笔记,记录UE4里CPP的一些用法、区别、原理和最佳实践等。(持续更新中)
.generated.h
当声明的类型需要反射(需要UCLASS(), USTRUCT()等等)时,需要在头文件包含这个文件,UHT在分析头文件内容的时候会生成相应的反射代码。
MODULENAME_API
这个声明在class后的宏是负责由UBT分析,然后生成DLL的时候,将当前模块的类导出给其他模块使用,如果你明确不需要导出给其他模块使用,那么不需要这个宏。
类的前缀
前缀A,意味着这个类从AActor继承下来,意味着这是个可以直接在world里spawn出来的东西。
前缀U,意味着这个类从UObject继承下来,不能直接在world里spawn,必须从属于某个Actor。
GENERATED_BODY() vs GENERATED_UCLASS_BODY()
后者是比较旧的版本引擎才使用的,现在一般不论是UCLASS还是USTRUCT都推荐使用前者。还有一个区别,前者是不自动包含public的,所有在下面的成员都是private,所以一般都会先在开头写上宏,然后后面立马加上public。
构造函数的选择
UClass的类构造函数有两种选择,最基本的和带参数的:(二选一)
UMyObject::UMyObject() {}
UMyObject::UMyObject(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {}
那么我们怎么知道改使用哪个呢?一是先看基类是怎么选择的,比如有一些类不推荐你用不带参数的构造函数:
/** Initialization constructor. */
TOctree(const FVector& InOrigin,float InExtent);
/** DO NOT USE. This constructor is for internal usage only for hot-reload purposes. */
TOctree();
二是看需求,比如基类在构造函数里面创建了一些组件,但是你不需要这些组件或者你想用其他组件替换之,那么使用带参的构造函数:
AUDKEmitterPool::AUDKEmitterPool(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.DoNotCreateDefaultSubobject(TEXT("SomeComponent")).DoNotCreateDefaultSubobject(TEXT("SomeOtherComponent")))
{}
AMyAIController::AMyAIController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UMyPathFollowingComponent>(TEXT("PathFollowingComponent")))
{}
另外还有一个小细节,构造函数一般直接写在对应的cpp文件里就好了,header里不需要声明。如果你非要在header里写构造函数,那么需要UCLASS加宏:UCLASS(CustomConstructor)
,来告诉UHT不用自动生成的构造函数。
UHT无视.h文件里的宏
不要在头文件里这样用宏:
#if WITH_EDITOR
UFUNCTION(BlueprintCallable)
void K2_Foo();
#endif
UHT会无视掉 WITH_EDITOR 宏,并且在.h相应的.generated.h反射文件里声明所有的函数。
这是有道理的,因为在蓝图或者Lua开发中,开发者一般会直接使用所有在Editor里可以看到的函数,如果UHT允许 WITH_EDITOR 宏产生作用,那么在其他平台(比如DSLinux)编译的时候会直接挂了。
编写只在编辑器生效的 UPROPERTY()
推荐写法:
#if WITH_EDITORONLY_DATA
UPROPERTY()
USkeletalMeshComponent* PreviewMesh;
#endif
另外要注意不能在蓝图里尝试使用这个 UPROPERTY() ,否则在正式打包之后一定会找不到这个变量。
meta里不能有多余的分隔符
无论是在写 UFUNCTION 宏还是 UCLASS 宏,在 meta = (xxx,xxx) 里不能在最后一个宏后面写多余的逗号,比如错误的用法 meta = (DeprecatedFunction, DeprecationMessage="Message Text", )。
如果在子类覆盖父类在构造函数中实例化的组件
比如你自己继承AAIController写了自己的AMyAIController,你想在这个类里使用自己写的UMyPathFollowingComponent,一个比较麻烦的点是AAIController在构造函数里实例化了原生的寻路组件。
AAIController::AAIController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bSetControlRotationFromPawnOrientation = true;
PathFollowingComponent = CreateDefaultSubobject<UPathFollowingComponent>(TEXT("PathFollowingComponent"));
}
替换掉寻路组件需要这样干:(注意一下,组件名字必须一样是父类写的"PathFollowingComponent")
AMyAIController::AMyAIController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UMyPathFollowingComponent>(TEXT("PathFollowingComponent"))
{
}