^ Do what you said in your post. Create a ‘UUnitComponent’ which you add to all of your various actors that support all that “unit-y stuff”, then implement the interface if you need to define custom behavior. Here’s an example from my project:
These two seperate classes inherit from different engines types, but share things like Health, Ammo, Weapons, they are detectable on radar etc.
ABZGame_Vehicle.h
UCLASS()
class BZGAME_API ABZGame_Vehicle : public APawn, public IBZGame_GameObjectInterface
{
GENERATED_UCLASS_BODY()
public:
FORCEINLINE UBZGame_GameObjectComponent* GetGameObjectComp() const { return GameObjectComponent; }
// Begin GOC Interface
void KillObject_Implementation(FDamageEvent const& DamageEvent, AController* InInstigator, AActor* DamageCauser);
USkeletalMeshComponent* GetRootMesh_Implementation() { return VehicleMesh; }
USkeletalMesh* GetFirstPersonMeshAsset_Implementation() { return CockpitAsset; }
// End GOC Interface
protected:
UPROPERTY(VisibleDefaultsOnly, Category = "Components")
UBZGame_GameObjectComponent* GameObjectComponent;
}
ABZGame_Character.h
UCLASS()
class BZGAME_API ABZGame_Character : public ACharacter, public IBZGame_GameObjectInterface
{
GENERATED_UCLASS_BODY()
public:
/* Begin GOC Interface */
void KillObject_Implementation(FDamageEvent const& DamageEvent, AController* InInstigator, AActor* DamageCauser);
USkeletalMeshComponent* GetRootMesh_Implementation() { return Mesh1P; }
USkeletalMesh* GetFirstPersonMeshAsset_Implementation() { return nullptr; }
FORCEINLINE UBZGame_GameObjectComponent* GetGameObjectComponent() const { return GameObjectData; }
protected:
UPROPERTY(VisibleDefaultsOnly, Category = "Stuff")
UBZGame_GameObjectComponent* GameObjectData;
They both create the component as a sub-object, and initialize as required.
ABZGame_Vehicle::ABZGame_Vehicle(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
GameObjectComponent = ObjectInitializer.CreateDefaultSubobject<UBZGame_GameObjectComponent>(this, TEXT("GameObjectComponent"));
GetGameObjectComp()->bGenerateMaterialInstances = true;
GetGameObjectComp()->SetNetAddressable();
GetGameObjectComp()->SetIsReplicated(true);
}
void ABZGame_Vehicle::PostInitializeComponents()
{
Super::PostInitializeComponents();
if (GetWorld() && GetWorld()->IsGameWorld())
{
if (Role == ROLE_Authority)
{
GetGameObjectComp()->SetPawnOwner(this);
GetGameObjectComp()->SpawnDefaultHardpoints();
GetGameObjectComp()->InitHealthAndAmmo();
//etc.
This is my ‘GameObjectComponent’ (your UnitComponent). It garauntees that the owner supports the “IBZGameObjectInterface” when registered, so that the required functions are garaunteed to be there.
“ASSERTV” is from my assert library, but it just soft-crashes the game if it can’t find the component.
void UBZGame_GameObjectComponent::OnRegister()
{
Super::OnRegister();
ASSERTV(GetOwner()->GetClass()->ImplementsInterface(UBZGame_GameObjectInterface::StaticClass()), *FString::Printf(TEXT("Game Object %s Does not Implement a Game Object Interface!"), *GetNameSafe(GetOwner())));
}
And it calls the appropriate functions on the owner through the interface, as and when it needs to:
void UBZGame_GameObjectComponent::KillObject(FDamageEvent const& DamageEvent, AController* InInstigator, AActor* DamageCauser)
{
// Assert if this is ever called Client-Side
ASSERTV(GetOwner()->Role == ROLE_Authority, *FString::Printf(TEXT("GOC %s Owned by %s is trying to call KillObject Client-Side"), *GetNameSafe(this), *GetNameSafe(GetOwner())));
// Ensure Health is Zero & Mark All Weapons for Destroy
CurrentHealth = 0.f;
DestroyInventory();
// Run the Type-Specific KillObject functionality for any owning class that implements the GOC Interface.
// If any 'Score' is to be added, it should also be done in the override interface function.
IBZGame_GameObjectInterface::Execute_KillObject(GetOwner(), DamageEvent, InInstigator, DamageCauser);
}
EDIT: The interface:
UINTERFACE()
class BZGAME_API UBZGame_GameObjectInterface : public UInterface
{
GENERATED_UINTERFACE_BODY()
};
class IBZGame_GameObjectInterface
{
GENERATED_IINTERFACE_BODY()
public:
UFUNCTION(BlueprintNativeEvent, Category = "Game Object")
void KillObject(FDamageEvent const& DamageEvent, AController* InInstigator, AActor* DamageCauser);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Game Object")
USkeletalMeshComponent* GetRootMesh();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Game Object")
USkeletalMesh* GetFirstPersonMeshAsset();
};