For posterity: I didn’t want to give up on CreateDefaultSubobject, and decided I’d rather have 1 hardcoded asset reference than N hardcoded asset references, so this is what I did. It works but it’s really janky.
hvConfig.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Logging/LogMacros.h"
#include "hvConfig.generated.h"
HVCOMMON_API DECLARE_LOG_CATEGORY_EXTERN(HvConfig, Log, All);
UCLASS(BlueprintType, ClassGroup = (Vacui))
class HVCOMMON_API UhvConfig : public UDataAsset
{
GENERATED_BODY()
private:
static TObjectPtr<UhvConfig> CachedConfig;
static UhvConfig* GetSingleton();
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TMap<FSoftClassPath, FSoftClassPath> HvSubobjectClassOverrides;
template<typename NativeUClass>
TSoftClassPtr<NativeUClass> GetOverrideClassImpl() {
static_assert(std::is_base_of<UObject, NativeUClass>::value, "NativeUClass must derive from UObject");
UClass* nativeClass = NativeUClass::StaticClass();
auto it = HvSubobjectClassOverrides.Find(nativeClass);
if (!it) {
UE_LOG(HvConfig, Display, TEXT("No override class registered for %s"), *nativeClass->GetName());
return nativeClass;
}
UClass* overrideClass = it->TryLoadClass<NativeUClass>();
if (!overrideClass) {
UE_LOG(HvConfig, Fatal, TEXT("Failed to load override class %s"), *it->GetAssetPathString());
return nativeClass;
}
UE_LOG(HvConfig, Display, TEXT("Found override class %s -> %s"), *nativeClass->GetName(), *it->GetAssetPathString());
return overrideClass;
}
template<typename NativeUClass>
static TSoftClassPtr<NativeUClass> GetOverrideClass() {
return GetSingleton()->GetOverrideClassImpl<NativeUClass>();
}
};
hvConfig.cpp
#include "hvConfig.h"
HVCOMMON_API DEFINE_LOG_CATEGORY(HvConfig);
// note: For non-Uobjects, you can also use TSharedPtr<>
TObjectPtr<UhvConfig> UhvConfig::CachedConfig = nullptr;
UhvConfig* UhvConfig::GetSingleton() {
if (CachedConfig) {
return CachedConfig.Get();
}
const TCHAR* configInstanceName = TEXT("hvConfig'/hvCore/hvConfig.hvConfig'");
ConstructorHelpers::FObjectFinderOptional<UhvConfig> ConfigFinder(configInstanceName, LOAD_FindIfFail);
UhvConfig* config = ConfigFinder.Get();
if (!config) {
UE_LOG(HvConfig, Error, TEXT("Failed to find config asset. Using a transitory one."));
config = NewObject<UhvConfig>();
}
CachedConfig = config;
return config;
}
myActor.cpp
AMyActor::AMyActor() {
struct FConstructorStatics
{
TSoftClassPtr<UMyBaseComponent> DerivedClass;
FConstructorStatics()
: DerivedClass(UhvConfig::GetOverrideClass<UMyBaseComponent>())
{}
};
static FConstructorStatics ConstructorStatics;
UObject* subobj = CreateDefaultSubobject(FName("MyComponent"), UMyBaseComponent::StaticClass(), ConstructorStatics.DerivedClass.Get(), true, false);
mMyComponent = static_cast<UMyBaseComponent*>(subobj);
}
Note: this code is inside a module named hvCommon
, inside a plugin named hvCore
. This is reflected in the HVCOMMON_API
macro (which resolves to DLLIMPORT
/ DLLEXPORT
), and the asset path for the singleton object hvConfig'/hvCore/hvConfig.hvConfig'