My plebian opinion on Unreal constructors is that you should only use them to initialise values to avoid null references.
What I mean by this is that suppose you have a class that you know you will be extending other classes from, and this base class has some non-basic properties (basic here refers to float/int/Vector etc. and non-basic refers to objects and actors), you should do the instantiation of these “non-basic” properties somewhere in the Spawn callback process (functions like BeginPlay/OnConstruction/OnComponentInitialize).
This is due to the fact that you cannot call virtual methods from a constructor, or more accurately, it will only call the base class’ version of the virtual function from the constructor.
I was also a bit irritated with this state of things at first, as it also prevents you from using ConstructorHelpers. So the way I currently do it is as follows:
Please note that the variable declarations in my header are as follows:
UPROPERTY()
FString ConstRefArmMeshLeftName;
// Arm Component
UPROPERTY()
TSubobjectPtr<USkeletalMeshComponent> Arm;
// Skeletal Mesh
UPROPERTY()
USkeletalMesh* ArmMesh;
Cpp files:
My Weapon Constructor:
APWNWeapon::APWNWeapon(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
// Arm Comp
Arm = PCIP.CreateDefaultSubobject<USkeletalMeshComponent>(this, TEXT("Arm"));
}
Beginplay:
void APWNWeapon::BeginPlay()
{
Super::BeginPlay();
SetAssetReferences();
SpawnAssetReferences();
}
Set Asset references:
void APWNWeapon::SetAssetReferences_Base()
{
// Arm
ConstRefArmMeshLeftName = TEXT("SkeletalMesh'/Game/PWNGame/FPSArms/Mesh/FPSArm_Left.FPSArm_Left'");
}
Spawn Assets:
void APWNWeapon::SpawnAssets_Base()
{
/***********/
/*** Arm ***/
/***********/
// Arm Mesh
if (ConstRefArmMeshLeftName.IsEmpty() || ConstRefArmMeshRightName.IsEmpty())
UPWNGameGlobals::PrintOnscreenString("No ConstRefArmMeshLeftName mesh specified.");
else
{
// Load arm mesh asset from path
ArmMesh = LoadSkeletalMeshFromPath(IsInLeftHand ? ConstRefArmMeshLeftName : ConstRefArmMeshRightName);
if(ArmMesh)
Arm->SetSkeletalMesh(ArmMesh);
}
}
USkeletalMesh* APWNWeapon::LoadSkeletalMeshFromPath(FString path)
{
return Cast<USkeletalMesh>(StaticLoadObject(USkeletalMesh::StaticClass(), NULL, *path));
}
Also note that SetAssetReferences_Base() is declared as virtual, which allows child classes to entirely override the reference values, without the need to override the spawn process.
The base class flow will then be executed as expected, but child classes have an “entry point” to set their own references before spawning the actual objects.
Please note that I have no idea whether this is a good approach or not, but it works for me.